Allow match to be started from WB menu
[xboard.git] / winboard / winboard.c
index 0958d00..9cc79da 100644 (file)
@@ -5,7 +5,7 @@
  * Massachusetts. \r
  *\r
  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,\r
- * 2007, 2008, 2009 Free Software Foundation, Inc.\r
+ * 2007, 2008, 2009, 2010 Free Software Foundation, Inc.\r
  *\r
  * Enhancements Copyright 2005 Alessandro Scotti\r
  *\r
@@ -108,7 +108,7 @@ int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
 void AnimateAtomicCapture(int fromX, int fromY, int toX, int toY, int nFrames);\r
 void DisplayMove P((int moveNumber));\r
 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));\r
-void ChatPopUp P(());\r
+void ChatPopUp P((char *s));\r
 typedef struct {\r
   ChessSquare piece;  \r
   POINT pos;      /* window coordinates of current pos */\r
@@ -134,6 +134,8 @@ typedef struct {
 \r
 static HighlightInfo highlightInfo        = { {{-1, -1}, {-1, -1}} };\r
 static HighlightInfo premoveHighlightInfo = { {{-1, -1}, {-1, -1}} };\r
+static HighlightInfo partnerHighlightInfo = { {{-1, -1}, {-1, -1}} };\r
+static HighlightInfo oldPartnerHighlight  = { {{-1, -1}, {-1, -1}} };\r
 \r
 typedef struct { // [HGM] atomic\r
   int fromX, fromY, toX, toY, radius;\r
@@ -836,8 +838,6 @@ InitInstance(HINSTANCE hInstance, int nCmdShow, LPSTR lpCmdLine)
       EngineOutputPopUp();\r
   }\r
 \r
-  InitBackEnd2();\r
-\r
   /* Make the window visible; update its client area; and return "success" */\r
   EnsureOnScreen(&wpMain.x, &wpMain.y, minX, minY);\r
   wp.length = sizeof(WINDOWPLACEMENT);\r
@@ -850,6 +850,8 @@ InitInstance(HINSTANCE hInstance, int nCmdShow, LPSTR lpCmdLine)
   wp.rcNormalPosition.bottom = wpMain.y + wpMain.height;\r
   SetWindowPlacement(hwndMain, &wp);\r
 \r
+  InitBackEnd2(); // [HGM] moved until after all windows placed, to save correct position if fatal error on engine start\r
+\r
   if(!appData.noGUI) SetWindowPos(hwndMain, alwaysOnTop ? HWND_TOPMOST : HWND_NOTOPMOST,\r
                0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);\r
 \r
@@ -859,6 +861,16 @@ InitInstance(HINSTANCE hInstance, int nCmdShow, LPSTR lpCmdLine)
                  0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);\r
 #endif\r
     ShowWindow(hwndConsole, nCmdShow);\r
+    if(appData.chatBoxes) { // [HGM] chat: open chat boxes\r
+      char buf[MSG_SIZ], *p = buf, *q;\r
+      strcpy(buf, appData.chatBoxes);\r
+      do {\r
+       q = strchr(p, ';');\r
+       if(q) *q++ = 0;\r
+       if(*p) ChatPopUp(p);\r
+      } while(p=q);\r
+    }\r
+    SetActiveWindow(hwndConsole);\r
   }\r
   if(!appData.noGUI)   UpdateWindow(hwnd);  else ShowWindow(hwnd, SW_MINIMIZE);\r
   if(gameListDialog) SetFocus(gameListDialog); // [HGM] jaws: for if we clicked multi-game game file\r
@@ -1210,7 +1222,14 @@ PrintCommPortSettings(FILE *f, char *name)
 int\r
 MySearchPath(char *installDir, char *name, char *fullname)\r
 {\r
-  char *dummy;\r
+  char *dummy, buf[MSG_SIZ];\r
+  if(name[0] == '%' && strchr(name+1, '%')) { // [HGM] recognize %*% as environment variable\r
+    strcpy(buf, name+1);\r
+    *strchr(buf, '%') = 0;\r
+    installDir = getenv(buf);\r
+    sprintf(fullname, "%s\\%s", installDir, strchr(name+1, '%')+1);\r
+    return strlen(fullname);\r
+  }\r
   return (int) SearchPath(installDir, name, NULL, MSG_SIZ, fullname, &dummy);\r
 }\r
 \r
@@ -1896,6 +1915,7 @@ ResizeBoard(int newSizeX, int newSizeY, int flags)
 }\r
 \r
 \r
+extern Boolean twoBoards, partnerUp; // [HGM] dual\r
 \r
 VOID\r
 InitDrawingSizes(BoardSize boardSize, int flags)\r
@@ -2031,6 +2051,7 @@ InitDrawingSizes(BoardSize boardSize, int flags)
   winW = 2 * GetSystemMetrics(SM_CXFRAME) + boardRect.right + OUTER_MARGIN;\r
   winH = 2 * GetSystemMetrics(SM_CYFRAME) + GetSystemMetrics(SM_CYMENU) +\r
     GetSystemMetrics(SM_CYCAPTION) + boardRect.bottom + OUTER_MARGIN;\r
+  winW *= 1 + twoBoards;\r
   if(suppressVisibleEffects) return; // [HGM] when called for filling sizeInfo only\r
   wpMain.width = winW;  // [HGM] placement: set through temporary which can used by initial sizing choice\r
   wpMain.height = winH; //       without disturbing window attachments\r
@@ -2477,23 +2498,14 @@ DrawHighlightOnDC(HDC hdc, BOOLEAN on, int x, int y, int pen)
 }\r
 \r
 VOID\r
-DrawHighlightsOnDC(HDC hdc)\r
+DrawHighlightsOnDC(HDC hdc, HighlightInfo *h, int pen)\r
 {\r
   int i;\r
   for (i=0; i<2; i++) {\r
-    if (highlightInfo.sq[i].x >= 0 && highlightInfo.sq[i].y >= 0) \r
+    if (h->sq[i].x >= 0 && h->sq[i].y >= 0) \r
       DrawHighlightOnDC(hdc, TRUE,\r
-                       highlightInfo.sq[i].x, highlightInfo.sq[i].y,\r
-                       HIGHLIGHT_PEN);\r
-  }\r
-  for (i=0; i<2; i++) {\r
-    if (premoveHighlightInfo.sq[i].x >= 0 && \r
-       premoveHighlightInfo.sq[i].y >= 0) {\r
-       DrawHighlightOnDC(hdc, TRUE,\r
-                         premoveHighlightInfo.sq[i].x, \r
-                         premoveHighlightInfo.sq[i].y,\r
-                         PREMOVE_PEN);\r
-    }\r
+                       h->sq[i].x, h->sq[i].y,\r
+                       pen);\r
   }\r
 }\r
 \r
@@ -2510,7 +2522,7 @@ DrawPieceOnDC(HDC hdc, ChessSquare piece, int color, int sqcolor, int x, int y,
   if (appData.blindfold) return;\r
 \r
   /* [AS] Use font-based pieces if needed */\r
-  if( fontBitmapSquareSize >= 0 && squareSize > 32 ) {\r
+  if( fontBitmapSquareSize >= 0 && (squareSize > 32 || gameInfo.variant >= VariantShogi)) {\r
     /* Create piece bitmaps, or do nothing if piece set is up to date */\r
     CreatePiecesFromFont();\r
 \r
@@ -2519,6 +2531,9 @@ DrawPieceOnDC(HDC hdc, ChessSquare piece, int color, int sqcolor, int x, int y,
 \r
         SelectObject( tmphdc, hPieceMask[ index ] );\r
 \r
+      if(appData.upsideDown ? color==flipView : (flipView && gameInfo.variant == VariantShogi))\r
+        StretchBlt(hdc, x+squareSize, y+squareSize, -squareSize, -squareSize, tmphdc, 0, 0, squareSize, squareSize, SRCAND);\r
+      else\r
         BitBlt( hdc,\r
             x, y,\r
             squareSize, squareSize,\r
@@ -2528,6 +2543,9 @@ DrawPieceOnDC(HDC hdc, ChessSquare piece, int color, int sqcolor, int x, int y,
 \r
         SelectObject( tmphdc, hPieceFace[ index ] );\r
 \r
+      if(appData.upsideDown ? color==flipView : (flipView && gameInfo.variant == VariantShogi))\r
+        StretchBlt(hdc, x+squareSize, y+squareSize, -squareSize, -squareSize, tmphdc, 0, 0, squareSize, squareSize, SRCPAINT);\r
+      else\r
         BitBlt( hdc,\r
             x, y,\r
             squareSize, squareSize,\r
@@ -2637,7 +2655,13 @@ VOID RebuildTextureSquareInfo()
             if( (col + row) & 1 ) {\r
                 /* Lite square */\r
                 if( lite_w >= squareSize && lite_h >= squareSize ) {\r
+                  if( lite_w >= squareSize*BOARD_WIDTH )\r
+                    backTextureSquareInfo[row][col].x = (2*col+1)*lite_w/(2*BOARD_WIDTH) - squareSize/2;  /* [HGM] cut out of center of virtual square */\r
+                  else\r
                     backTextureSquareInfo[row][col].x = col * (lite_w - squareSize) / (BOARD_WIDTH-1);  /* [HGM] divide by size-1 in stead of size! */\r
+                  if( lite_h >= squareSize*BOARD_HEIGHT )\r
+                    backTextureSquareInfo[row][col].y = (2*(BOARD_HEIGHT-row)-1)*lite_h/(2*BOARD_HEIGHT) - squareSize/2;\r
+                  else\r
                     backTextureSquareInfo[row][col].y = (BOARD_HEIGHT-1-row) * (lite_h - squareSize) / (BOARD_HEIGHT-1);\r
                     backTextureSquareInfo[row][col].mode = GetBackTextureMode(liteBackTextureMode);\r
                 }\r
@@ -2645,7 +2669,13 @@ VOID RebuildTextureSquareInfo()
             else {\r
                 /* Dark square */\r
                 if( dark_w >= squareSize && dark_h >= squareSize ) {\r
+                  if( dark_w >= squareSize*BOARD_WIDTH )\r
+                    backTextureSquareInfo[row][col].x = (2*col+1) * dark_w / (2*BOARD_WIDTH) - squareSize/2;\r
+                  else\r
                     backTextureSquareInfo[row][col].x = col * (dark_w - squareSize) / (BOARD_WIDTH-1);\r
+                  if( dark_h >= squareSize*BOARD_HEIGHT )\r
+                    backTextureSquareInfo[row][col].y = (2*(BOARD_HEIGHT-row)-1) * dark_h / (2*BOARD_HEIGHT) - squareSize/2;\r
+                  else\r
                     backTextureSquareInfo[row][col].y = (BOARD_HEIGHT-1-row) * (dark_h - squareSize) / (BOARD_HEIGHT-1);\r
                     backTextureSquareInfo[row][col].mode = GetBackTextureMode(darkBackTextureMode);\r
                 }\r
@@ -3127,8 +3157,14 @@ void DrawSeekText(char *buf, int x, int y)
 \r
 void DrawSeekDot(int x, int y, int color)\r
 {\r
+       int square = color & 0x80;\r
+       color &= 0x7F;\r
            HBRUSH oldBrush = SelectObject(hdcSeek, \r
                        color == 0 ? markerBrush : color == 1 ? darkSquareBrush : explodeBrush);\r
+       if(square)\r
+           Rectangle(hdcSeek, boardRect.left+x - squareSize/9, boardRect.top+y - squareSize/9,\r
+                              boardRect.left+x + squareSize/9, boardRect.top+y + squareSize/9);\r
+       else\r
            Ellipse(hdcSeek, boardRect.left+x - squareSize/8, boardRect.top+y - squareSize/8,\r
                             boardRect.left+x + squareSize/8, boardRect.top+y + squareSize/8);\r
            SelectObject(hdcSeek, oldBrush);\r
@@ -3137,10 +3173,10 @@ void DrawSeekDot(int x, int y, int color)
 VOID\r
 HDCDrawPosition(HDC hdc, BOOLEAN repaint, Board board)\r
 {\r
-  static Board lastReq, lastDrawn;\r
+  static Board lastReq[2], lastDrawn[2];\r
   static HighlightInfo lastDrawnHighlight, lastDrawnPremove;\r
   static int lastDrawnFlipView = 0;\r
-  static int lastReqValid = 0, lastDrawnValid = 0;\r
+  static int lastReqValid[2] = {0, 0}, lastDrawnValid[2] = {0, 0};\r
   int releaseDC, x, y, x2, y2, row, column, num_clips = 0, i;\r
   HDC tmphdc;\r
   HDC hdcmem;\r
@@ -3149,6 +3185,7 @@ HDCDrawPosition(HDC hdc, BOOLEAN repaint, Board board)
   RECT Rect;\r
   HRGN clips[MAX_CLIPS];\r
   ChessSquare dragged_piece = EmptySquare;\r
+  int nr = twoBoards*partnerUp;\r
 \r
   /* I'm undecided on this - this function figures out whether a full\r
    * repaint is necessary on its own, so there's no real reason to have the\r
@@ -3167,13 +3204,13 @@ HDCDrawPosition(HDC hdc, BOOLEAN repaint, Board board)
   }\r
 \r
   if (board == NULL) {\r
-    if (!lastReqValid) {\r
+    if (!lastReqValid[nr]) {\r
       return;\r
     }\r
-    board = lastReq;\r
+    board = lastReq[nr];\r
   } else {\r
-    CopyBoard(lastReq, board);\r
-    lastReqValid = 1;\r
+    CopyBoard(lastReq[nr], board);\r
+    lastReqValid[nr] = 1;\r
   }\r
 \r
   if (doingSizing) {\r
@@ -3219,16 +3256,17 @@ HDCDrawPosition(HDC hdc, BOOLEAN repaint, Board board)
    * newest board with the last drawn board and checking if\r
    * flipping has changed.\r
    */\r
-  if (!fullrepaint && lastDrawnValid && lastDrawnFlipView == flipView) {\r
+  if (!fullrepaint && lastDrawnValid[nr] && (nr == 1 || lastDrawnFlipView == flipView)) {\r
     for (row = 0; row < BOARD_HEIGHT; row++) { /* [HGM] true size, not 8 */\r
       for (column = 0; column < BOARD_WIDTH; column++) {\r
-       if (lastDrawn[row][column] != board[row][column]) {\r
+       if (lastDrawn[nr][row][column] != board[row][column]) {\r
          SquareToPos(row, column, &x, &y);\r
          clips[num_clips++] =\r
            CreateRectRgn(x, y, x + squareSize, y + squareSize);\r
        }\r
       }\r
     }\r
+   if(nr == 0) { // [HGM] dual: no highlights on second board\r
     for (i=0; i<2; i++) {\r
       if (lastDrawnHighlight.sq[i].x != highlightInfo.sq[i].x ||\r
          lastDrawnHighlight.sq[i].y != highlightInfo.sq[i].y) {\r
@@ -3269,6 +3307,30 @@ HDCDrawPosition(HDC hdc, BOOLEAN repaint, Board board)
        }\r
       }\r
     }\r
+   } else { // nr == 1\r
+       partnerHighlightInfo.sq[0].y = board[EP_STATUS-4];\r
+       partnerHighlightInfo.sq[0].x = board[EP_STATUS-3];\r
+       partnerHighlightInfo.sq[1].y = board[EP_STATUS-2];\r
+       partnerHighlightInfo.sq[1].x = board[EP_STATUS-1];\r
+      for (i=0; i<2; i++) {\r
+       if (partnerHighlightInfo.sq[i].x >= 0 &&\r
+           partnerHighlightInfo.sq[i].y >= 0) {\r
+         SquareToPos(partnerHighlightInfo.sq[i].y,\r
+                     partnerHighlightInfo.sq[i].x, &x, &y);\r
+         clips[num_clips++] =\r
+           CreateRectRgn(x - lineGap, y - lineGap, \r
+                         x + squareSize + lineGap, y + squareSize + lineGap);\r
+       }\r
+       if (oldPartnerHighlight.sq[i].x >= 0 && \r
+           oldPartnerHighlight.sq[i].y >= 0) {\r
+         SquareToPos(oldPartnerHighlight.sq[i].y, \r
+                     oldPartnerHighlight.sq[i].x, &x, &y);\r
+         clips[num_clips++] =\r
+           CreateRectRgn(x - lineGap, y - lineGap, \r
+                         x + squareSize + lineGap, y + squareSize + lineGap);\r
+       }\r
+      }\r
+   }\r
   } else {\r
     fullrepaint = TRUE;\r
   }\r
@@ -3328,7 +3390,7 @@ HDCDrawPosition(HDC hdc, BOOLEAN repaint, Board board)
         explodes.  The old and new positions both had an empty square\r
         at the destination, but animation has drawn a piece there and\r
         we have to remember to erase it. [HGM] moved until after setting lastDrawn */\r
-      lastDrawn[animInfo.to.y][animInfo.to.x] = animInfo.piece;\r
+      lastDrawn[0][animInfo.to.y][animInfo.to.x] = animInfo.piece;\r
     }\r
   }\r
 \r
@@ -3358,16 +3420,24 @@ HDCDrawPosition(HDC hdc, BOOLEAN repaint, Board board)
          ExtSelectClipRgn(hdcmem, clips[num_clips++], RGN_OR);\r
        }\r
        DrawGridOnDC(hdcmem);\r
-       DrawHighlightsOnDC(hdcmem);\r
+       DrawHighlightsOnDC(hdcmem, &highlightInfo, HIGHLIGHT_PEN);\r
+       DrawHighlightsOnDC(hdcmem, &premoveHighlightInfo, PREMOVE_PEN);\r
        DrawBoardOnDC(hdcmem, board, tmphdc);\r
        oldBrush = SelectObject(hdcmem, explodeBrush);\r
        Ellipse(hdcmem, x-r, y-r, x+r, y+r);\r
        SelectObject(hdcmem, oldBrush);\r
   } else {\r
     DrawGridOnDC(hdcmem);\r
-    DrawHighlightsOnDC(hdcmem);\r
+    if(nr == 0) { // [HGM] dual: decide which highlights to draw\r
+       DrawHighlightsOnDC(hdcmem, &highlightInfo, HIGHLIGHT_PEN);\r
+       DrawHighlightsOnDC(hdcmem, &premoveHighlightInfo, PREMOVE_PEN);\r
+    } else {\r
+       DrawHighlightsOnDC(hdcmem, &partnerHighlightInfo, HIGHLIGHT_PEN);\r
+       oldPartnerHighlight = partnerHighlightInfo;\r
+    }\r
     DrawBoardOnDC(hdcmem, board, tmphdc);\r
   }\r
+  if(nr == 0) // [HGM] dual: markers only on left board\r
   for (row = 0; row < BOARD_HEIGHT; row++) {\r
     for (column = 0; column < BOARD_WIDTH; column++) {\r
        if (marker[row][column]) { // marker changes only occur with full repaint!\r
@@ -3422,7 +3492,7 @@ HDCDrawPosition(HDC hdc, BOOLEAN repaint, Board board)
 \r
   DrawCoordsOnDC(hdcmem);\r
 \r
-  CopyBoard(lastDrawn, board); /* [HGM] Moved to here from end of routine, */\r
+  CopyBoard(lastDrawn[nr], board); /* [HGM] Moved to here from end of routine, */\r
                  /* to make sure lastDrawn contains what is actually drawn */\r
 \r
   /* Put the dragged piece back into place and draw it (out of place!) */\r
@@ -3459,6 +3529,13 @@ HDCDrawPosition(HDC hdc, BOOLEAN repaint, Board board)
 \r
   /* Set clipping on the target DC */\r
   if (!fullrepaint) {\r
+    if(nr == 1) for (x = 0; x < num_clips; x++) { // [HGM] dual: translate clips\r
+       RECT rect;\r
+       GetRgnBox(clips[x], &rect);\r
+       DeleteObject(clips[x]);\r
+       clips[x] = CreateRectRgn(rect.left + wpMain.width/2, rect.top, \r
+                         rect.right + wpMain.width/2, rect.bottom);\r
+    }\r
     SelectClipRgn(hdc, clips[0]);\r
     for (x = 1; x < num_clips; x++) {\r
       if (ExtSelectClipRgn(hdc, clips[x], RGN_OR) == ERROR)\r
@@ -3470,7 +3547,7 @@ HDCDrawPosition(HDC hdc, BOOLEAN repaint, Board board)
    * This way we avoid any flickering\r
    */\r
   oldBitmap = SelectObject(tmphdc, bufferBitmap);\r
-  BitBlt(hdc, boardRect.left, boardRect.top,\r
+  BitBlt(hdc, boardRect.left + twoBoards*partnerUp*wpMain.width/2, boardRect.top, // [HGM] dual\r
         boardRect.right - boardRect.left,\r
         boardRect.bottom - boardRect.top,\r
         tmphdc, boardRect.left, boardRect.top, SRCCOPY);\r
@@ -3558,7 +3635,7 @@ HDCDrawPosition(HDC hdc, BOOLEAN repaint, Board board)
   if (releaseDC) \r
     ReleaseDC(hwndMain, hdc);\r
   \r
-  if (lastDrawnFlipView != flipView) {\r
+  if (lastDrawnFlipView != flipView && nr == 0) {\r
     if (flipView)\r
       CheckMenuItem(GetMenu(hwndMain),IDM_FlipView, MF_BYCOMMAND|MF_CHECKED);\r
     else\r
@@ -3569,7 +3646,7 @@ HDCDrawPosition(HDC hdc, BOOLEAN repaint, Board board)
   lastDrawnHighlight = highlightInfo;\r
   lastDrawnPremove   = premoveHighlightInfo;\r
   lastDrawnFlipView = flipView;\r
-  lastDrawnValid = 1;\r
+  lastDrawnValid[nr] = 1;\r
 }\r
 \r
 /* [HGM] diag: Save the current board display to the given open file and close the file */\r
@@ -3610,6 +3687,11 @@ PaintProc(HWND hwnd)
        RealizePalette(hdc);\r
       }\r
       HDCDrawPosition(hdc, 1, NULL);\r
+      if(twoBoards) { // [HGM] dual: also redraw other board in other orientation\r
+       flipView = !flipView; partnerUp = !partnerUp;\r
+       HDCDrawPosition(hdc, 1, NULL);\r
+       flipView = !flipView; partnerUp = !partnerUp;\r
+      }\r
       oldFont =\r
        SelectObject(hdc, font[boardSize][MESSAGE_FONT]->hf);\r
       ExtTextOut(hdc, messageRect.left, messageRect.top,\r
@@ -3740,20 +3822,20 @@ MouseEvent(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
       if (PtInRect((LPRECT) &whiteRect, pt)) {\r
         if (gameMode == EditPosition) {\r
          SetWhiteToPlayEvent();\r
+        } else if (gameMode == EditGame || GetKeyState(VK_SHIFT) < 0) {\r
+          AdjustClock(flipClock, -1);\r
        } else if (gameMode == IcsPlayingBlack ||\r
                   gameMode == MachinePlaysWhite) {\r
          CallFlagEvent();\r
-        } else if (gameMode == EditGame) {\r
-          AdjustClock(flipClock, -1);\r
         }\r
       } else if (PtInRect((LPRECT) &blackRect, pt)) {\r
        if (gameMode == EditPosition) {\r
          SetBlackToPlayEvent();\r
+        } else if (gameMode == EditGame || GetKeyState(VK_SHIFT) < 0) {\r
+          AdjustClock(!flipClock, -1);\r
        } else if (gameMode == IcsPlayingWhite ||\r
                   gameMode == MachinePlaysBlack) {\r
          CallFlagEvent();\r
-        } else if (gameMode == EditGame) {\r
-          AdjustClock(!flipClock, -1);\r
        }\r
       }\r
       dragInfo.start.x = dragInfo.start.y = -1;\r
@@ -3773,7 +3855,7 @@ MouseEvent(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
     break;\r
 \r
   case WM_MOUSEMOVE:\r
-    if(SeekGraphClick(Press, pt.x - boardRect.left, pt.y - boardRect.top, TRUE)) break;\r
+    if(SeekGraphClick(Press, pt.x - boardRect.left, pt.y - boardRect.top, 1)) break;\r
     MovePV(pt.x - boardRect.left, pt.y - boardRect.top, boardRect.bottom - boardRect.top);\r
     if ((appData.animateDragging || appData.highlightDragging)\r
        && (wParam & MK_LBUTTON)\r
@@ -3802,12 +3884,12 @@ MouseEvent(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
        /* Mouse Wheel is being rolled forward\r
         * Play moves forward\r
         */\r
-       if((short)HIWORD(wParam) > 0 && currentMove < forwardMostMove) \r
+       if((short)HIWORD(wParam) < 0 && currentMove < forwardMostMove) \r
                { if(lastDir == 1) ForwardEvent(); else lastDir = 1; } // [HGM] suppress first event in direction\r
        /* Mouse Wheel is being rolled backward\r
         * Play moves backward\r
         */\r
-       if((short)HIWORD(wParam) < 0 && currentMove > backwardMostMove) \r
+       if((short)HIWORD(wParam) > 0 && currentMove > backwardMostMove) \r
                { if(lastDir == -1) BackwardEvent(); else lastDir = -1; }\r
     }\r
     break;\r
@@ -3833,10 +3915,11 @@ MouseEvent(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
     if(y == -2) {\r
       /* [HGM] right mouse button in clock area edit-game mode ups clock */\r
       if (PtInRect((LPRECT) &whiteRect, pt)) {\r
-          if (gameMode == EditGame) AdjustClock(flipClock, 1);\r
+          if (gameMode == EditGame || GetKeyState(VK_SHIFT) < 0) AdjustClock(flipClock, 1);\r
       } else if (PtInRect((LPRECT) &blackRect, pt)) {\r
-          if (gameMode == EditGame) AdjustClock(!flipClock, 1);\r
+          if (gameMode == EditGame || GetKeyState(VK_SHIFT) < 0) AdjustClock(!flipClock, 1);\r
       }\r
+      break;\r
     }\r
     DrawPosition(TRUE, NULL);\r
 \r
@@ -4416,7 +4499,7 @@ WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
       break;\r
 \r
     case IDM_NewChat:\r
-      ChatPopUp();\r
+      ChatPopUp(NULL);\r
       break;\r
 \r
     case IDM_CopyPosition:\r
@@ -4472,6 +4555,12 @@ WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
       SAY("computer starts playing black");\r
       break;\r
 \r
+    case IDM_Match: // [HGM] match: flows into next case, after setting Match Mode and nr of Games\r
+      if(gameMode != BeginningOfGame) break; // allow menu item to remain enabled for better mode highligting\r
+      matchMode = 2;// distinguish from command-line-triggered case (matchMode=1)\r
+      appData.matchGames = appData.defaultMatchGames;\r
+      matchGame = 1;\r
+\r
     case IDM_TwoMachines:\r
       TwoMachinesEvent();\r
       /*\r
@@ -4483,7 +4572,7 @@ WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
          TagsPopUp(tags, CmailMsg());\r
          free(tags);\r
       }\r
-      SAY("programs start playing each other");\r
+      SAY("computer starts playing both sides");\r
       break;\r
 \r
     case IDM_AnalysisMode:\r
@@ -4546,7 +4635,7 @@ WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
 \r
     case IDM_EditPosition:\r
       EditPositionEvent();\r
-      SAY("to set up a position type a FEN");\r
+      SAY("enter a FEN string or setup a position on the board using the control R pop up menu");\r
       break;\r
 \r
     case IDM_Training:\r
@@ -4613,6 +4702,10 @@ WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
       StopExaminingEvent();\r
       break;\r
 \r
+    case IDM_Upload:\r
+      UploadGameEvent();\r
+      break;\r
+\r
     case IDM_TypeInMove:\r
       PopUpMoveDialog('\000');\r
       break;\r
@@ -4644,7 +4737,11 @@ WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
       break;\r
 \r
     case IDM_Revert:\r
-      RevertEvent();\r
+      RevertEvent(FALSE);\r
+      break;\r
+\r
+    case IDM_Annotate: // [HGM] vari: revert with annotation\r
+      RevertEvent(TRUE);\r
       break;\r
 \r
     case IDM_TruncateGame:\r
@@ -5831,6 +5928,7 @@ CommentDialog(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
        sizeY = newSizeY;\r
       }\r
     }\r
+    SendDlgItemMessage( hDlg, OPT_CommentText, EM_SETEVENTMASK, 0, ENM_MOUSEEVENTS );\r
     return FALSE;\r
 \r
   case WM_COMMAND: /* message: received a command */\r
@@ -5875,6 +5973,36 @@ CommentDialog(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
     }\r
     break;\r
 \r
+  case WM_NOTIFY: // [HGM] vari: cloned from whistory.c\r
+        if( wParam == OPT_CommentText ) {\r
+            MSGFILTER * lpMF = (MSGFILTER *) lParam;\r
+\r
+            if( lpMF->msg == WM_RBUTTONDOWN && (lpMF->wParam & (MK_CONTROL | MK_SHIFT)) == 0 ) {\r
+                POINTL pt;\r
+                LRESULT index;\r
+\r
+                pt.x = LOWORD( lpMF->lParam );\r
+                pt.y = HIWORD( lpMF->lParam );\r
+\r
+                index = SendDlgItemMessage( hDlg, OPT_CommentText, EM_CHARFROMPOS, 0, (LPARAM) &pt );\r
+\r
+               hwndText = GetDlgItem(hDlg, OPT_CommentText); // cloned from above\r
+               len = GetWindowTextLength(hwndText);\r
+               str = (char *) malloc(len + 1);\r
+               GetWindowText(hwndText, str, len + 1);\r
+               ReplaceComment(commentIndex, str);\r
+               if(commentIndex != currentMove) ToNrEvent(commentIndex);\r
+                LoadVariation( index, str ); // [HGM] also does the actual moving to it, now\r
+               free(str);\r
+\r
+                /* Zap the message for good: apparently, returning non-zero is not enough */\r
+                lpMF->msg = WM_USER;\r
+\r
+                return TRUE;\r
+            }\r
+        }\r
+        break;\r
+\r
   case WM_SIZE:\r
     newSizeX = LOWORD(lParam);\r
     newSizeY = HIWORD(lParam);\r
@@ -6053,6 +6181,12 @@ TypeInNameDialog(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
       GetDlgItemText(hDlg, OPT_Name, move, sizeof(move));\r
       appData.userName = strdup(move);\r
       SetUserLogo();\r
+      SetGameInfo();\r
+      if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack) {\r
+       sprintf(move, "%s vs. %s", gameInfo.white, gameInfo.black);\r
+       DisplayTitle(move);\r
+      }\r
+\r
 \r
       EndDialog(hDlg, TRUE);\r
       return TRUE;\r
@@ -6307,13 +6441,14 @@ LoadIcsTextMenu(IcsTextMenuEntry *e)
   while (e->item) {\r
     if (strcmp(e->item, "-") == 0) {\r
       AppendMenu(h, MF_SEPARATOR, 0, 0);\r
-    } else {\r
+    } else { // [HGM] re-written a bit to use only one AppendMenu call for both cases (| or no |)\r
+      int flags = MF_STRING, j = 0;\r
       if (e->item[0] == '|') {\r
-       AppendMenu(h, MF_STRING|MF_MENUBARBREAK,\r
-                  IDM_CommandX + i, &e->item[1]);\r
-      } else {\r
-       AppendMenu(h, MF_STRING, IDM_CommandX + i, e->item);\r
+       flags |= MF_MENUBARBREAK;\r
+        j++;\r
       }\r
+      if(!strcmp(e->command, "none")) flags |= MF_GRAYED; // [HGM] chatclick: provide inactive dummy\r
+      AppendMenu(h, flags, IDM_CommandX + i, e->item + j);\r
     }\r
     e++;\r
     i++;\r
@@ -6382,6 +6517,7 @@ CommandX(HWND hwnd, char *command, BOOLEAN getname, BOOLEAN immediate)
     SetWindowText(hInput, buf);\r
     SendMessage(hInput, WM_CHAR, '\r', 0);\r
   } else {\r
+    if(!strcmp(command, "chat")) { ChatPopUp(name); return; }\r
     sprintf(buf, "%s %s ", command, name); /* trailing space */\r
     SetWindowText(hInput, buf);\r
     sel.cpMin = 999999;\r
@@ -6434,11 +6570,20 @@ ConsoleTextSubclass(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
     }\r
     return 0;\r
    } // [HGM] navigate: for Ctrl+R, flow into nex case (moved up here) to summon up menu\r
-  case WM_RBUTTONUP:\r
-    if (GetKeyState(VK_SHIFT) & ~1) {\r
-      SendDlgItemMessage(hwndConsole, OPT_ConsoleText, \r
-        WM_COMMAND, MAKEWPARAM(IDM_QuickPaste, 0), 0);\r
-    } else {\r
+  case WM_RBUTTONDOWN:\r
+    if (!(GetKeyState(VK_SHIFT) & ~1)) {\r
+      /* Move selection here if it was empty */\r
+      POINT pt;\r
+      pt.x = LOWORD(lParam);\r
+      pt.y = HIWORD(lParam);\r
+      SendMessage(hwnd, EM_EXGETSEL, 0, (LPARAM)&sel);\r
+      if (sel.cpMin == sel.cpMax) {\r
+        sel.cpMin = SendMessage(hwnd, EM_CHARFROMPOS, 0, (LPARAM)&pt); /*doc is wrong*/\r
+       sel.cpMax = sel.cpMin;\r
+       SendMessage(hwnd, EM_EXSETSEL, 0, (LPARAM)&sel);\r
+      }\r
+      SendMessage(hwnd, EM_HIDESELECTION, FALSE, FALSE);\r
+{ // [HGM] chatclick: code moved here from WM_RBUTTONUP case, to have menu appear on down-click\r
       POINT pt;\r
       HMENU hmenu = LoadIcsTextMenu(icsTextMenuEntry);\r
       SendMessage(hwnd, EM_EXGETSEL, 0, (LPARAM)&sel);\r
@@ -6449,9 +6594,17 @@ ConsoleTextSubclass(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
       if (!IsClipboardFormatAvailable(CF_TEXT)) {\r
         EnableMenuItem(hmenu, IDM_Paste, MF_BYCOMMAND|MF_GRAYED);\r
       }\r
-      pt.x = LOWORD(lParam);\r
-      pt.y = HIWORD(lParam);\r
+      pt.x = LOWORD(lParam)-30; // [HGM] chatclick: make menu pop up with pointer above upper-right item\r
+      pt.y = HIWORD(lParam)-10; //       make it appear as if mouse moved there, so it will be selected on up-click\r
+      PostMessage(hwnd, WM_MOUSEMOVE, wParam, lParam+5);\r
       MenuPopup(hwnd, pt, hmenu, -1);\r
+}\r
+    }\r
+    return 0;\r
+  case WM_RBUTTONUP:\r
+    if (GetKeyState(VK_SHIFT) & ~1) {\r
+      SendDlgItemMessage(hwndConsole, OPT_ConsoleText, \r
+        WM_COMMAND, MAKEWPARAM(IDM_QuickPaste, 0), 0);\r
     }\r
     return 0;\r
   case WM_PASTE:\r
@@ -6460,21 +6613,6 @@ ConsoleTextSubclass(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
     return SendMessage(hInput, message, wParam, lParam);\r
   case WM_MBUTTONDOWN:\r
     return SendMessage(hwnd, WM_COMMAND, MAKEWPARAM(IDM_QuickPaste, 0), 0);\r
-  case WM_RBUTTONDOWN:\r
-    if (!(GetKeyState(VK_SHIFT) & ~1)) {\r
-      /* Move selection here if it was empty */\r
-      POINT pt;\r
-      pt.x = LOWORD(lParam);\r
-      pt.y = HIWORD(lParam);\r
-      SendMessage(hwnd, EM_EXGETSEL, 0, (LPARAM)&sel);\r
-      if (sel.cpMin == sel.cpMax) {\r
-        sel.cpMin = SendMessage(hwnd, EM_CHARFROMPOS, 0, (LPARAM)&pt); /*doc is wrong*/\r
-       sel.cpMax = sel.cpMin;\r
-       SendMessage(hwnd, EM_EXSETSEL, 0, (LPARAM)&sel);\r
-      }\r
-      SendMessage(hwnd, EM_HIDESELECTION, FALSE, FALSE);\r
-    }\r
-    return 0;\r
   case WM_COMMAND:\r
     switch (LOWORD(wParam)) {\r
     case IDM_QuickPaste:\r
@@ -7282,6 +7420,7 @@ GreyRevert(Boolean grey)
 { // [HGM] vari: for retracting variations in local mode\r
   HMENU hmenu = GetMenu(hwndMain);\r
   EnableMenuItem(hmenu, IDM_Revert, MF_BYCOMMAND|(grey ? MF_GRAYED : MF_ENABLED));\r
+  EnableMenuItem(hmenu, IDM_Annotate, MF_BYCOMMAND|(grey ? MF_GRAYED : MF_ENABLED));\r
 }\r
 \r
 VOID\r
@@ -7303,7 +7442,9 @@ Enables gnuEnables[] = {
   { IDM_Adjourn, MF_BYCOMMAND|MF_GRAYED },\r
   { IDM_StopExamining, MF_BYCOMMAND|MF_GRAYED },\r
   { IDM_StopObserving, MF_BYCOMMAND|MF_GRAYED },\r
+  { IDM_Upload, MF_BYCOMMAND|MF_GRAYED },\r
   { IDM_Revert, MF_BYCOMMAND|MF_GRAYED },\r
+  { IDM_Annotate, MF_BYCOMMAND|MF_GRAYED },\r
   { IDM_NewChat, MF_BYCOMMAND|MF_GRAYED },\r
   { -1, -1 }\r
 };\r
@@ -7314,6 +7455,7 @@ Enables icsEnables[] = {
   { IDM_MachineWhite, MF_BYCOMMAND|MF_GRAYED },\r
   { IDM_MachineBlack, MF_BYCOMMAND|MF_GRAYED },\r
   { IDM_TwoMachines, MF_BYCOMMAND|MF_GRAYED },\r
+  { IDM_Match, MF_BYCOMMAND|MF_GRAYED },\r
   { IDM_MachineBoth, MF_BYCOMMAND|MF_GRAYED },\r
   { IDM_AnalysisMode, MF_BYCOMMAND|MF_ENABLED },\r
   { IDM_AnalyzeFile, MF_BYCOMMAND|MF_GRAYED },\r
@@ -7324,10 +7466,11 @@ Enables icsEnables[] = {
   { IDM_IcsOptions, MF_BYCOMMAND|MF_ENABLED },\r
   { IDM_Engine1Options, MF_BYCOMMAND|MF_GRAYED },\r
   { IDM_Engine2Options, MF_BYCOMMAND|MF_GRAYED },\r
+  { IDM_Annotate, MF_BYCOMMAND|MF_GRAYED },\r
   { -1, -1 }\r
 };\r
 \r
-#ifdef ZIPPY\r
+#if ZIPPY\r
 Enables zippyEnables[] = {\r
   { IDM_MoveNow, MF_BYCOMMAND|MF_ENABLED },\r
   { IDM_Hint, MF_BYCOMMAND|MF_ENABLED },\r
@@ -7343,11 +7486,13 @@ Enables ncpEnables[] = {
   { IDM_MachineWhite, MF_BYCOMMAND|MF_GRAYED },\r
   { IDM_MachineBlack, MF_BYCOMMAND|MF_GRAYED },\r
   { IDM_TwoMachines, MF_BYCOMMAND|MF_GRAYED },\r
+  { IDM_Match, MF_BYCOMMAND|MF_GRAYED },\r
   { IDM_AnalysisMode, MF_BYCOMMAND|MF_GRAYED },\r
   { IDM_AnalyzeFile, MF_BYCOMMAND|MF_GRAYED },\r
   { IDM_IcsClient, MF_BYCOMMAND|MF_GRAYED },\r
   { ACTION_POS, MF_BYPOSITION|MF_GRAYED },\r
   { IDM_Revert, MF_BYCOMMAND|MF_GRAYED },\r
+  { IDM_Annotate, MF_BYCOMMAND|MF_GRAYED },\r
   { IDM_MoveNow, MF_BYCOMMAND|MF_GRAYED },\r
   { IDM_RetractMove, MF_BYCOMMAND|MF_GRAYED },\r
   { IDM_TimeControl, MF_BYCOMMAND|MF_GRAYED },\r
@@ -7410,6 +7555,7 @@ Enables machineThinkingEnables[] = {
   { IDM_MachineWhite, MF_BYCOMMAND|MF_GRAYED },\r
   { IDM_MachineBlack, MF_BYCOMMAND|MF_GRAYED },\r
   { IDM_TwoMachines, MF_BYCOMMAND|MF_GRAYED },\r
+  { IDM_Match, MF_BYCOMMAND|MF_GRAYED },\r
   { IDM_TypeInMove, MF_BYCOMMAND|MF_GRAYED },\r
   { IDM_RetractMove, MF_BYCOMMAND|MF_GRAYED },\r
   { -1, -1 }\r
@@ -7429,6 +7575,7 @@ Enables userThinkingEnables[] = {
   { IDM_MachineWhite, MF_BYCOMMAND|MF_ENABLED },\r
   { IDM_MachineBlack, MF_BYCOMMAND|MF_ENABLED },\r
   { IDM_TwoMachines, MF_BYCOMMAND|MF_ENABLED },\r
+  { IDM_Match, MF_BYCOMMAND|MF_ENABLED },\r
   { IDM_TypeInMove, MF_BYCOMMAND|MF_ENABLED },\r
   { IDM_RetractMove, MF_BYCOMMAND|MF_ENABLED },\r
   { -1, -1 }\r
@@ -7470,7 +7617,7 @@ ModeHighlight()
     nowChecked = IDM_MachineWhite;\r
     break;\r
   case TwoMachinesPlay:\r
-    nowChecked = IDM_TwoMachines;\r
+    nowChecked = matchMode ? IDM_Match : IDM_TwoMachines; // [HGM] match\r
     break;\r
   case AnalyzeMode:\r
     nowChecked = IDM_AnalysisMode;\r
@@ -7537,7 +7684,7 @@ SetICSMode()
   SetMenuEnables(hmenu, icsEnables);\r
   EnableMenuItem(GetSubMenu(hmenu, OPTIONS_POS), ICS_POS,\r
     MF_BYPOSITION|MF_ENABLED);\r
-#ifdef ZIPPY\r
+#if ZIPPY\r
   if (appData.zippyPlay) {\r
     SetMenuEnables(hmenu, zippyEnables);\r
     if (!appData.noChessProgram)     /* [DM] icsEngineAnalyze */\r
@@ -7611,7 +7758,7 @@ SetMachineThinkingEnables()
   } else if (gameMode == MachinePlaysWhite) {\r
     (void)EnableMenuItem(hMenu, IDM_MachineWhite, flags);\r
   } else if (gameMode == TwoMachinesPlay) {\r
-    (void)EnableMenuItem(hMenu, IDM_TwoMachines, flags);\r
+    (void)EnableMenuItem(hMenu, matchMode ? IDM_Match : IDM_TwoMachines, flags); // [HGM] match\r
   }\r
 }\r
 \r
@@ -8050,6 +8197,7 @@ ResetFrontEnd()
     ReleaseCapture();\r
     DrawPosition(TRUE, NULL);\r
   }\r
+  TagsPopDown();\r
 }\r
 \r
 \r
@@ -8057,7 +8205,7 @@ VOID
 CommentPopUp(char *title, char *str)\r
 {\r
   HWND hwnd = GetActiveWindow();\r
-  EitherCommentPopUp(0, title, str, FALSE);\r
+  EitherCommentPopUp(currentMove, title, str, FALSE); // [HGM] vari: fake move index, rather than 0\r
   SAY(str);\r
   SetActiveWindow(hwnd);\r
 }\r