changes from Alessandro Scotti from 20051231
[xboard.git] / winboard / whistory.c
diff --git a/winboard/whistory.c b/winboard/whistory.c
new file mode 100644 (file)
index 0000000..ba69a63
--- /dev/null
@@ -0,0 +1,453 @@
+/*
+ * Move history for WinBoard
+ *
+ * Author: Alessandro Scotti
+ *
+ * ------------------------------------------------------------------------
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ * ------------------------------------------------------------------------
+ */
+#include "config.h"
+
+#include <windows.h> /* required for all Windows applications */
+#include <richedit.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <malloc.h>
+#include <commdlg.h>
+#include <dlgs.h>
+
+#include "common.h"
+#include "winboard.h"
+#include "frontend.h"
+#include "backend.h"
+
+#include "wsnap.h"
+
+VOID MoveHistorySet( char movelist[][2*MOVE_LEN], int first, int last, int current, ChessProgramStats_Move * pvInfo );
+VOID MoveHistoryPopUp();
+VOID MoveHistoryPopDown();
+BOOL MoveHistoryIsUp();
+
+/* Imports from backend.c */
+char * SavePart(char *str);
+
+/* Imports from winboard.c */
+extern HWND moveHistoryDialog;
+extern BOOLEAN moveHistoryDialogUp;
+
+extern HINSTANCE hInst;
+extern HWND hwndMain;
+
+extern WindowPlacement wpMoveHistory;
+
+/* Module globals */
+typedef char MoveHistoryString[ MOVE_LEN*2 ];
+
+static int lastFirst = 0;
+static int lastLast = 0;
+static int lastCurrent = -1;
+
+static MoveHistoryString * currMovelist;
+static ChessProgramStats_Move * currPvInfo;
+static int currFirst = 0;
+static int currLast = 0;
+static int currCurrent = -1;
+
+typedef struct {
+    int memoOffset;
+    int memoLength;
+} HistoryMove;
+
+static HistoryMove histMoves[ MAX_MOVES ];
+
+#define WM_REFRESH_HISTORY  (WM_USER+4657)
+
+#define DEFAULT_COLOR       0xFFFFFFFF
+
+#define H_MARGIN            2
+#define V_MARGIN            2
+
+/* Note: in the following code a "Memo" is a Rich Edit control (it's Delphi lingo) */
+
+static VOID HighlightMove( int index, BOOL highlight )
+{
+    if( index >= 0 && index < MAX_MOVES ) {
+        CHARFORMAT cf;
+        HWND hMemo = GetDlgItem( moveHistoryDialog, IDC_MoveHistory );
+
+        SendMessage( hMemo,
+            EM_SETSEL,
+            histMoves[index].memoOffset,
+            histMoves[index].memoOffset + histMoves[index].memoLength );
+
+
+        /* Set style */
+        ZeroMemory( &cf, sizeof(cf) );
+
+        cf.cbSize = sizeof(cf);
+        cf.dwMask = CFM_BOLD | CFM_COLOR;
+
+        if( highlight ) {
+            cf.dwEffects |= CFE_BOLD;
+            cf.crTextColor = RGB( 0x00, 0x00, 0xFF );
+        }
+        else {
+            cf.dwEffects |= CFE_AUTOCOLOR;
+        }
+
+        SendMessage( hMemo, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM) &cf );
+    }
+}
+
+static BOOL OnlyCurrentPositionChanged()
+{
+    BOOL result = FALSE;
+
+    if( lastFirst >= 0 &&
+        lastLast >= lastFirst &&
+        lastCurrent >= lastFirst &&
+        currFirst == lastFirst &&
+        currLast == lastLast &&
+        currCurrent >= 0 &&
+        TRUE )
+    {
+        result = TRUE;
+    }
+
+    return result;
+}
+
+static BOOL OneMoveAppended()
+{
+    BOOL result = FALSE;
+
+    if( lastCurrent >= 0 && lastCurrent >= lastFirst && lastLast >= lastFirst &&
+        currCurrent >= 0 && currCurrent >= currFirst && currLast >= currFirst &&
+        lastFirst == currFirst &&
+        lastLast == (currLast-1) &&
+        lastCurrent == (currCurrent-1) &&
+        currCurrent == (currLast-1) &&
+        TRUE )
+    {
+        result = TRUE;
+    }
+
+    return result;
+}
+
+static VOID ClearMemo()
+{
+    SendDlgItemMessage( moveHistoryDialog, IDC_MoveHistory, WM_SETTEXT, 0, (LPARAM) "" );
+}
+
+static int AppendToMemo( char * text, DWORD flags, DWORD color )
+{
+    CHARFORMAT cf;
+
+    HWND hMemo = GetDlgItem( moveHistoryDialog, IDC_MoveHistory );
+
+    /* Select end of text */
+    int cbTextLen = (int) SendMessage( hMemo, WM_GETTEXTLENGTH, 0, 0 );
+
+    SendMessage( hMemo, EM_SETSEL, cbTextLen, cbTextLen );
+
+    /* Set style */
+    ZeroMemory( &cf, sizeof(cf) );
+
+    cf.cbSize = sizeof(cf);
+    cf.dwMask = CFM_BOLD | CFM_ITALIC | CFM_COLOR | CFM_UNDERLINE;
+    cf.dwEffects = flags;
+
+    if( color != DEFAULT_COLOR ) {
+        cf.crTextColor = color;
+    }
+    else {
+        cf.dwEffects |= CFE_AUTOCOLOR;
+    }
+
+    SendMessage( hMemo, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM) &cf );
+
+    /* Append text */
+    SendMessage( hMemo, EM_REPLACESEL, (WPARAM) FALSE, (LPARAM) text );
+
+    /* Return offset of appended text */
+    return cbTextLen;
+}
+
+static VOID AppendMoveToMemo( int index )
+{
+    char buf[64];
+    DWORD flags = 0;
+    DWORD color = DEFAULT_COLOR;
+
+    if( index < 0 || index >= MAX_MOVES ) {
+        return;
+    }
+
+    buf[0] = '\0';
+
+    /* Move number */
+    if( (index % 2) == 0 ) {
+        sprintf( buf, "%d.%s ", (index / 2)+1, index & 1 ? ".." : "" );
+        AppendToMemo( buf, CFE_BOLD, DEFAULT_COLOR );
+    }
+
+    /* Move text */
+    strcpy( buf, SavePart( currMovelist[index] ) );
+    strcat( buf, " " );
+
+    histMoves[index].memoOffset = AppendToMemo( buf, flags, color );
+    histMoves[index].memoLength = strlen(buf)-1;
+
+    /* PV info (if any) */
+    if( appData.showEvalInMoveHistory && currPvInfo[index].depth > 0 ) {
+        sprintf( buf, "%{%s%.2f/%d} ",
+            currPvInfo[index].score >= 0 ? "+" : "",
+            currPvInfo[index].score / 100.0,
+            currPvInfo[index].depth );
+
+        AppendToMemo( buf, flags,
+            color == DEFAULT_COLOR ? GetSysColor(COLOR_GRAYTEXT) : color );
+    }
+}
+
+static void RefreshMemoContent()
+{
+    int i;
+
+    ClearMemo();
+
+    for( i=currFirst; i<currLast; i++ ) {
+        AppendMoveToMemo( i );
+    }
+}
+
+static void MemoContentUpdated()
+{
+    int caretPos;
+
+    HighlightMove( lastCurrent, FALSE );
+    HighlightMove( currCurrent, TRUE );
+
+    lastFirst = currFirst;
+    lastLast = currLast;
+    lastCurrent = currCurrent;
+
+    /* Deselect any text, move caret to end of memo */
+    if( currCurrent >= 0 ) {
+        caretPos = histMoves[currCurrent].memoOffset + histMoves[currCurrent].memoLength;
+    }
+    else {
+        caretPos = (int) SendDlgItemMessage( moveHistoryDialog, IDC_MoveHistory, WM_GETTEXTLENGTH, 0, 0 );
+    }
+
+    SendDlgItemMessage( moveHistoryDialog, IDC_MoveHistory, EM_SETSEL, caretPos, caretPos );
+
+    SendDlgItemMessage( moveHistoryDialog, IDC_MoveHistory, EM_SCROLLCARET, 0, 0 );
+}
+
+int FindMoveByCharIndex( int char_index )
+{
+    int index;
+
+    for( index=currFirst; index<currLast; index++ ) {
+        if( char_index >= histMoves[index].memoOffset &&
+            char_index <  (histMoves[index].memoOffset + histMoves[index].memoLength) )
+        {
+            return index;
+        }
+    }
+
+    return -1;
+}
+
+LRESULT CALLBACK HistoryDialogProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
+{
+    static SnapData sd;
+
+    switch (message) {
+    case WM_INITDIALOG:
+        if( moveHistoryDialog == NULL ) {
+            moveHistoryDialog = hDlg;
+
+            /* Enable word wrapping and notifications */
+            SendDlgItemMessage( moveHistoryDialog, IDC_MoveHistory, EM_SETTARGETDEVICE, 0, 0 );
+
+            SendDlgItemMessage( moveHistoryDialog, IDC_MoveHistory, EM_SETEVENTMASK, 0, ENM_MOUSEEVENTS );
+
+            /* Restore window placement */
+            RestoreWindowPlacement( hDlg, &wpMoveHistory );
+        }
+
+        /* Update memo */
+        RefreshMemoContent();
+
+        MemoContentUpdated();
+
+        return FALSE;
+
+    case WM_COMMAND:
+        switch (LOWORD(wParam)) {
+        case IDOK:
+          EndDialog(hDlg, TRUE);
+          return TRUE;
+
+        case IDCANCEL:
+          EndDialog(hDlg, FALSE);
+          return TRUE;
+
+        default:
+          break;
+        }
+
+        break;
+
+    case WM_NOTIFY:
+        if( wParam == IDC_MoveHistory ) {
+            MSGFILTER * lpMF = (MSGFILTER *) lParam;
+
+            if( lpMF->msg == WM_LBUTTONDBLCLK && (lpMF->wParam & (MK_CONTROL | MK_SHIFT)) == 0 ) {
+                POINTL pt;
+                LRESULT index;
+
+                pt.x = LOWORD( lpMF->lParam );
+                pt.y = HIWORD( lpMF->lParam );
+
+                index = SendDlgItemMessage( hDlg, IDC_MoveHistory, EM_CHARFROMPOS, 0, (LPARAM) &pt );
+
+                index = FindMoveByCharIndex( index );
+
+                if( index >= 0 ) {
+                    ToNrEvent( index + 1 );
+                }
+
+                /* Zap the message for good: apparently, returning non-zero is not enough */
+                lpMF->msg = WM_USER;
+
+                return TRUE;
+            }
+        }
+        break;
+
+    case WM_REFRESH_HISTORY:
+        /* Update the GUI */
+        if( OnlyCurrentPositionChanged() ) {
+            /* Only "cursor" changed, no need to update memo content */
+        }
+        else if( OneMoveAppended() ) {
+            AppendMoveToMemo( currCurrent );
+        }
+        else {
+            RefreshMemoContent();
+        }
+
+        MemoContentUpdated();
+
+        break;
+
+    case WM_SIZE:
+        SetWindowPos( GetDlgItem( moveHistoryDialog, IDC_MoveHistory ),
+            HWND_TOP,
+            H_MARGIN, V_MARGIN,
+            LOWORD(lParam) - 2*H_MARGIN,
+            HIWORD(lParam) - 2*V_MARGIN,
+            SWP_NOZORDER );
+        break;
+
+    case WM_GETMINMAXINFO:
+        {
+            MINMAXINFO * mmi = (MINMAXINFO *) lParam;
+
+            mmi->ptMinTrackSize.x = 100;
+            mmi->ptMinTrackSize.y = 100;
+        }
+        break;
+
+    case WM_CLOSE:
+        MoveHistoryPopDown();
+        break;
+
+    case WM_ENTERSIZEMOVE:
+        return OnEnterSizeMove( &sd, hDlg, wParam, lParam );
+
+    case WM_SIZING:
+        return OnSizing( &sd, hDlg, wParam, lParam );
+
+    case WM_MOVING:
+        return OnMoving( &sd, hDlg, wParam, lParam );
+
+    case WM_EXITSIZEMOVE:
+        return OnExitSizeMove( &sd, hDlg, wParam, lParam );
+    }
+
+    return FALSE;
+}
+
+VOID MoveHistoryPopUp()
+{
+  FARPROC lpProc;
+
+  CheckMenuItem(GetMenu(hwndMain), IDM_ShowMoveHistory, MF_CHECKED);
+
+  if( moveHistoryDialog ) {
+    SendMessage( moveHistoryDialog, WM_INITDIALOG, 0, 0 );
+
+    if( ! moveHistoryDialogUp ) {
+        ShowWindow(moveHistoryDialog, SW_SHOW);
+    }
+  }
+  else {
+    lpProc = MakeProcInstance( (FARPROC) HistoryDialogProc, hInst );
+
+    /* Note to self: dialog must have the WS_VISIBLE style set, otherwise it's not shown! */
+    CreateDialog( hInst, MAKEINTRESOURCE(DLG_MoveHistory), hwndMain, (DLGPROC)lpProc );
+
+    FreeProcInstance(lpProc);
+  }
+
+  moveHistoryDialogUp = TRUE;
+}
+
+VOID MoveHistoryPopDown()
+{
+  CheckMenuItem(GetMenu(hwndMain), IDM_ShowMoveHistory, MF_UNCHECKED);
+
+  if( moveHistoryDialog ) {
+      ShowWindow(moveHistoryDialog, SW_HIDE);
+  }
+
+  moveHistoryDialogUp = FALSE;
+}
+
+VOID MoveHistorySet( char movelist[][2*MOVE_LEN], int first, int last, int current, ChessProgramStats_Move * pvInfo )
+{
+    /* [AS] Danger! For now we rely on the movelist parameter being a static variable! */
+
+    currMovelist = movelist;
+    currFirst = first;
+    currLast = last;
+    currCurrent = current;
+    currPvInfo = pvInfo;
+
+    if( moveHistoryDialog ) {
+        SendMessage( moveHistoryDialog, WM_REFRESH_HISTORY, 0, 0 );
+    }
+}
+
+BOOL MoveHistoryIsUp()
+{
+    return moveHistoryDialogUp;
+}