[OSK] Make the buttons themed
[reactos.git] / base / applications / osk / main.c
index 7f5a5e9..6f04d78 100644 (file)
@@ -1,9 +1,9 @@
 /*
  * PROJECT:         ReactOS On-Screen Keyboard
  * LICENSE:         GPL - See COPYING in the top level directory
- * FILE:            base/applications/osk/main.c
  * PURPOSE:         On-screen keyboard.
- * PROGRAMMERS:     Denis ROBERT
+ * COPYRIGHT:       Denis ROBERT
+ *                  Copyright 2019 BiČ™oc George (fraizeraust99 at gmail dot com)
  */
 
 /* INCLUDES *******************************************************************/
@@ -24,7 +24,8 @@ BOOL OSK_DlgCommand(WPARAM wCommand, HWND hWndControl);
 BOOL OSK_ReleaseKey(WORD ScanCode);
 
 INT_PTR APIENTRY OSK_DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam);
-int WINAPI _tWinMain(HINSTANCE, HINSTANCE, LPTSTR, int);
+LRESULT APIENTRY OSK_ThemeHandler(HWND hDlg, NMCUSTOMDRAW *pNmDraw);
+int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int);
 
 /* FUNCTIONS ******************************************************************/
 
@@ -39,8 +40,8 @@ int OSK_SetImage(int IdDlgItem, int IdResource)
     HICON hIcon;
     HWND hWndItem;
 
-    hIcon = (HICON)LoadImage(Globals.hInstance, MAKEINTRESOURCE(IdResource),
-                             IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR);
+    hIcon = (HICON)LoadImageW(Globals.hInstance, MAKEINTRESOURCEW(IdResource),
+                              IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR);
     if (hIcon == NULL)
         return FALSE;
 
@@ -51,39 +52,13 @@ int OSK_SetImage(int IdDlgItem, int IdResource)
         return FALSE;
     }
 
-    SendMessage(hWndItem, BM_SETIMAGE, (WPARAM)IMAGE_ICON, (LPARAM)hIcon);
+    SendMessageW(hWndItem, BM_SETIMAGE, (WPARAM)IMAGE_ICON, (LPARAM)hIcon);
 
     /* The system automatically deletes these resources when the process that created them terminates (MSDN) */
 
     return TRUE;
 }
 
-/***********************************************************************
- *
- *           OSK_SetAppIcon
- *
- *  Set the application's icon
- */
-BOOL OSK_SetAppIcon()
-{
-    HICON hIconSmall;
-
-    /* Load the icon */
-    hIconSmall = LoadImageW(Globals.hInstance, MAKEINTRESOURCEW(IDI_OSK), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);
-
-    /* Send a message window indicating that the icon has to be set */
-    SendMessageW(Globals.hMainWnd, WM_SETICON, ICON_SMALL, (LPARAM)hIconSmall);
-
-    if (!hIconSmall)
-    {
-        /* If we fail then return FALSE and bail out */
-        DestroyIcon(hIconSmall);
-        return FALSE;
-    }
-
-    return TRUE;
-}
-
 /***********************************************************************
  *
  *          OSK_WarningProc
@@ -125,6 +100,32 @@ INT_PTR CALLBACK OSK_WarningProc(HWND hDlg, UINT Msg, WPARAM wParam, LPARAM lPar
     return FALSE;
 }
 
+/***********************************************************************
+ *
+ *          OSK_About
+ *
+ *  Initializes the "About" dialog box
+ */
+VOID OSK_About(VOID)
+{
+    WCHAR szTitle[MAX_BUFF];
+    WCHAR szAuthors[MAX_BUFF];
+    HICON OSKIcon;
+
+    /* Load the icon */
+    OSKIcon = LoadImageW(Globals.hInstance, MAKEINTRESOURCEW(IDI_OSK), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);
+
+    /* Load the strings into the "About" dialog */
+    LoadStringW(Globals.hInstance, STRING_OSK, szTitle, countof(szTitle));
+    LoadStringW(Globals.hInstance, STRING_AUTHORS, szAuthors, countof(szAuthors));
+
+    /* Finally, execute the "About" dialog by using the Shell routine */
+    ShellAboutW(Globals.hMainWnd, szTitle, szAuthors, OSKIcon);
+
+    /* Once done, destroy the icon */
+    DestroyIcon(OSKIcon);
+}
+
 
 /***********************************************************************
  *
@@ -134,36 +135,86 @@ INT_PTR CALLBACK OSK_WarningProc(HWND hDlg, UINT Msg, WPARAM wParam, LPARAM lPar
  */
 int OSK_DlgInitDialog(HWND hDlg)
 {
-    HMONITOR  monitor;
+    HICON hIcon, hIconSm;
+    HMONITOR monitor;
     MONITORINFO info;
     POINT Pt;
-    RECT rcWindow;
+    RECT rcWindow, rcDlgIntersect;
 
     /* Save handle */
     Globals.hMainWnd = hDlg;
 
-    /* Load the settings from the registry hive */
-    LoadDataFromRegistry();
+    /* Check the checked menu item before displaying the modal box */
+    if (Globals.bIsEnhancedKeyboard)
+    {
+        /* Enhanced keyboard dialog chosen, set the respective menu item as checked */
+        CheckMenuItem(GetMenu(hDlg), IDM_ENHANCED_KB, MF_BYCOMMAND | MF_CHECKED);
+        CheckMenuItem(GetMenu(hDlg), IDM_STANDARD_KB, MF_BYCOMMAND | MF_UNCHECKED);
+    }
+    else
+    {
+        /* Standard keyboard dialog chosen, set the respective menu item as checked */
+        CheckMenuItem(GetMenu(hDlg), IDM_STANDARD_KB, MF_BYCOMMAND | MF_CHECKED);
+        CheckMenuItem(GetMenu(hDlg), IDM_ENHANCED_KB, MF_BYCOMMAND | MF_UNCHECKED);
+    }
+
+    /* Check if the "Click Sound" option was chosen before (and if so, then tick the menu item) */
+    if (Globals.bSoundClick)
+    {
+        CheckMenuItem(GetMenu(hDlg), IDM_CLICK_SOUND, MF_BYCOMMAND | MF_CHECKED);
+    }
 
     /* Set the application's icon */
-    OSK_SetAppIcon();
+    hIcon = LoadImageW(Globals.hInstance, MAKEINTRESOURCEW(IDI_OSK), IMAGE_ICON, 0, 0, LR_SHARED | LR_DEFAULTSIZE);
+    hIconSm = CopyImage(hIcon, IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), LR_COPYFROMRESOURCE);
+    if (hIcon || hIconSm)
+    {
+        /* Set the window icons (they are deleted when the process terminates) */
+        SendMessageW(Globals.hMainWnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
+        SendMessageW(Globals.hMainWnd, WM_SETICON, ICON_SMALL, (LPARAM)hIconSm);
+    }
 
     /* Get screen info */
     memset(&Pt, 0, sizeof(Pt));
-    monitor = MonitorFromPoint(Pt, MONITOR_DEFAULTTOPRIMARY );
+    monitor = MonitorFromPoint(Pt, MONITOR_DEFAULTTOPRIMARY);
     info.cbSize = sizeof(info);
     GetMonitorInfoW(monitor, &info);
-
-    /* Move the dialog on the bottom of main screen */
     GetWindowRect(hDlg, &rcWindow);
-    MoveWindow(hDlg,
-               (info.rcMonitor.left + info.rcMonitor.right) / 2 - // Center of screen
-                   (rcWindow.right - rcWindow.left) / 2,          // - half size of dialog
-               info.rcMonitor.bottom -               // Bottom of screen
-                   (rcWindow.bottom - rcWindow.top), // - size of window
-               rcWindow.right - rcWindow.left,     // Width
-               rcWindow.bottom - rcWindow.top,     // Height
-               TRUE);
+
+    /* 
+        If the coordination values are default then re-initialize using the specific formulas
+        to move the dialog at the bottom of the screen.
+    */
+    if (Globals.PosX == CW_USEDEFAULT && Globals.PosY == CW_USEDEFAULT)
+    {
+        Globals.PosX = (info.rcMonitor.left + info.rcMonitor.right - (rcWindow.right - rcWindow.left)) / 2;
+        Globals.PosY = info.rcMonitor.bottom - (rcWindow.bottom - rcWindow.top);
+    }
+
+    /*  
+        Calculate the intersection of two rectangle sources (dialog and work desktop area).
+        If such sources do not intersect, then the dialog is deemed as "off screen".
+    */
+    if (IntersectRect(&rcDlgIntersect, &rcWindow, &info.rcWork) == 0)
+    {
+        Globals.PosX = (info.rcMonitor.left + info.rcMonitor.right - (rcWindow.right - rcWindow.left)) / 2;
+        Globals.PosY = info.rcMonitor.bottom - (rcWindow.bottom - rcWindow.top);
+    }
+    else
+    {
+        /*
+            There's still some intersection but we're not for sure if it is sufficient (the dialog could also be partially hidden).
+            Therefore, check the remaining intersection if it's enough.
+        */
+        if (rcWindow.top < info.rcWork.top || rcWindow.left < info.rcWork.left || rcWindow.right > info.rcWork.right || rcWindow.bottom > info.rcWork.bottom)
+        {
+            Globals.PosX = (info.rcMonitor.left + info.rcMonitor.right - (rcWindow.right - rcWindow.left)) / 2;
+            Globals.PosY = info.rcMonitor.bottom - (rcWindow.bottom - rcWindow.top);
+        }
+    }
+
+    /* Move the dialog according to the placement coordination */
+    SetWindowPos(hDlg, HWND_TOP, Globals.PosX, Globals.PosY, 0, 0, SWP_NOSIZE);
 
     /* Set icon on visual buttons */
     OSK_SetImage(SCAN_CODE_15, IDI_BACK);
@@ -189,12 +240,6 @@ int OSK_DlgInitDialog(HWND hDlg)
     /* Set a timer for periodics tasks */
     Globals.iTimer = SetTimer(hDlg, 0, 200, NULL);
 
-    /* If the member of the struct (bShowWarning) is set then display the dialog box */
-    if (Globals.bShowWarning)
-    {
-        DialogBox(Globals.hInstance, MAKEINTRESOURCE(IDD_WARNINGDIALOG_OSK), Globals.hMainWnd, OSK_WarningProc);
-    }
-
     return TRUE;
 }
 
@@ -271,19 +316,19 @@ BOOL OSK_DlgCommand(WPARAM wCommand, HWND hWndControl)
         MSG msg;
 
         SetForegroundWindow(Globals.hActiveWnd);
-        while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
+        while (PeekMessageW(&msg, 0, 0, 0, PM_REMOVE))
         {
             TranslateMessage(&msg);
-            DispatchMessage(&msg);
+            DispatchMessageW(&msg);
         }
     }
 
     /* KeyDown and/or KeyUp ? */
-    WindowStyle = GetWindowLong(hWndControl, GWL_STYLE);
+    WindowStyle = GetWindowLongW(hWndControl, GWL_STYLE);
     if ((WindowStyle & BS_AUTOCHECKBOX) == BS_AUTOCHECKBOX)
     {
         /* 2-states key like Shift, Alt, Ctrl, ... */
-        if (SendMessage(hWndControl, BM_GETCHECK, 0, 0) == BST_CHECKED)
+        if (SendMessageW(hWndControl, BM_GETCHECK, 0, 0) == BST_CHECKED)
         {
             bKeyDown = TRUE;
             bKeyUp = FALSE;
@@ -334,6 +379,12 @@ BOOL OSK_DlgCommand(WPARAM wCommand, HWND hWndControl)
         SendInput(1, &Input, sizeof(Input));
     }
 
+    /* Play the sound during clicking event (only if "Use Click Sound" menu option is ticked) */
+    if (Globals.bSoundClick)
+    {
+        PlaySoundW(MAKEINTRESOURCEW(IDI_SOUNDCLICK), GetModuleHandle(NULL), SND_RESOURCE | SND_ASYNC);
+    }
+
     return TRUE;
 }
 
@@ -352,11 +403,11 @@ BOOL OSK_ReleaseKey(WORD ScanCode)
 
     /* Is it a 2-states key ? */
     hWndControl = GetDlgItem(Globals.hMainWnd, ScanCode);
-    WindowStyle = GetWindowLong(hWndControl, GWL_STYLE);
+    WindowStyle = GetWindowLongW(hWndControl, GWL_STYLE);
     if ((WindowStyle & BS_AUTOCHECKBOX) != BS_AUTOCHECKBOX) return FALSE;
 
     /* Is the key down ? */
-    if (SendMessage(hWndControl, BM_GETCHECK, 0, 0) != BST_CHECKED) return TRUE;
+    if (SendMessageW(hWndControl, BM_GETCHECK, 0, 0) != BST_CHECKED) return TRUE;
 
     /* Extended key ? */
     if (ScanCode & 0x0200)
@@ -378,6 +429,58 @@ BOOL OSK_ReleaseKey(WORD ScanCode)
     return TRUE;
 }
 
+/***********************************************************************
+ *
+ *           OSK_ThemeHandler
+ *
+ *  Function helper which handles theme drawing of controls
+ */
+LRESULT APIENTRY OSK_ThemeHandler(HWND hDlg, NMCUSTOMDRAW *pNmDraw)
+{
+    HTHEME hTheme;
+    HWND hDlgButtonCtrl;
+    INT iState = PBS_NORMAL;
+
+    /* Retrieve the theme handle for the button controls */
+    hDlgButtonCtrl = pNmDraw->hdr.hwndFrom;
+    hTheme = GetWindowTheme(hDlgButtonCtrl);
+
+    /*
+        Begin the painting procedures if we retrieved
+        the theme for control buttons of the dialog.
+    */
+    if (hTheme)
+    {
+        /*
+            The button could be either in normal state or pushed.
+            Retrieve its state and save to a variable.
+        */
+        if (pNmDraw->uItemState & CDIS_DEFAULT)
+        {
+            iState = PBS_DEFAULTED;
+        }
+        else if (pNmDraw->uItemState & CDIS_SELECTED)
+        {
+            iState = PBS_PRESSED;
+        }
+        else if (pNmDraw->uItemState & CDIS_HOT)
+        {
+            iState = PBS_HOT;
+        }
+
+        if (IsThemeBackgroundPartiallyTransparent(hTheme, BP_PUSHBUTTON, iState))
+        {
+            /* Draw the application if the theme is transparent */
+            DrawThemeParentBackground(hDlg, pNmDraw->hdc, &pNmDraw->rc);
+        }
+
+        /* Draw it */
+        DrawThemeBackground(hTheme, pNmDraw->hdc, BP_PUSHBUTTON, iState, &pNmDraw->rc, NULL);
+    }
+
+    return CDRF_SKIPDEFAULT;
+}
+
 /***********************************************************************
  *
  *       OSK_DlgProc
@@ -394,6 +497,9 @@ INT_PTR APIENTRY OSK_DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
             OSK_DlgTimer();
             return TRUE;
 
+        case WM_NOTIFY:
+            return OSK_ThemeHandler(hDlg, (LPNMCUSTOMDRAW)lParam);
+
         case WM_CTLCOLORSTATIC:
             if ((HWND)lParam == GetDlgItem(hDlg, IDC_LED_NUM))
             {
@@ -419,10 +525,107 @@ INT_PTR APIENTRY OSK_DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
             break;
 
         case WM_COMMAND:
-            if (wParam == IDCANCEL)
-                EndDialog(hDlg, FALSE);
-            else if (wParam != IDC_STATIC)
-                OSK_DlgCommand(wParam, (HWND) lParam);
+            switch (LOWORD(wParam))
+            {
+                case IDCANCEL:
+                {
+                    EndDialog(hDlg, FALSE);
+                    break;
+                }
+
+                case IDM_EXIT:
+                {
+                    EndDialog(hDlg, FALSE);
+                    break;
+                }
+
+                case IDM_ENHANCED_KB:
+                {
+                    if (!Globals.bIsEnhancedKeyboard)
+                    {
+                        /* 
+                            The user attempted to switch to enhanced keyboard dialog type.
+                            Set the member value as TRUE, destroy the dialog and save the data configuration into the registry.
+                        */
+                        Globals.bIsEnhancedKeyboard = TRUE;
+                        EndDialog(hDlg, FALSE);
+                        SaveDataToRegistry();
+
+                        /* Change the condition of enhanced keyboard item menu to checked */
+                        CheckMenuItem(GetMenu(hDlg), IDM_ENHANCED_KB, MF_BYCOMMAND | MF_CHECKED);
+                        CheckMenuItem(GetMenu(hDlg), IDM_STANDARD_KB, MF_BYCOMMAND | MF_UNCHECKED);
+
+                        /* Finally, display the dialog modal box with the enhanced keyboard dialog */
+                        DialogBoxW(Globals.hInstance,
+                                   MAKEINTRESOURCEW(MAIN_DIALOG_ENHANCED_KB),
+                                   GetDesktopWindow(),
+                                   OSK_DlgProc);
+                    }
+
+                    break;
+                }
+
+                case IDM_STANDARD_KB:
+                {
+                    if (Globals.bIsEnhancedKeyboard)
+                    {
+                        /*
+                            The user attempted to switch to standard keyboard dialog type.
+                            Set the member value as FALSE, destroy the dialog and save the data configuration into the registry.
+                        */
+                        Globals.bIsEnhancedKeyboard = FALSE;
+                        EndDialog(hDlg, FALSE);
+                        SaveDataToRegistry();
+
+                        /* Change the condition of standard keyboard item menu to checked */
+                        CheckMenuItem(GetMenu(hDlg), IDM_ENHANCED_KB, MF_BYCOMMAND | MF_UNCHECKED);
+                        CheckMenuItem(GetMenu(hDlg), IDM_STANDARD_KB, MF_BYCOMMAND | MF_CHECKED);
+
+                        /* Finally, display the dialog modal box with the standard keyboard dialog */
+                        DialogBoxW(Globals.hInstance,
+                                   MAKEINTRESOURCEW(MAIN_DIALOG_STANDARD_KB),
+                                   GetDesktopWindow(),
+                                   OSK_DlgProc);
+                    }
+
+                    break;
+                }
+
+                case IDM_CLICK_SOUND:
+                {
+                    /*
+                        This case is triggered when the user attempts to click on the menu item. Before doing anything,
+                        we must check the condition state of such menu item so that we can tick/untick the menu item accordingly.
+                    */
+                    if (!Globals.bSoundClick)
+                    {
+                        Globals.bSoundClick = TRUE;
+                        CheckMenuItem(GetMenu(hDlg), IDM_CLICK_SOUND, MF_BYCOMMAND | MF_CHECKED);
+                    }
+                    else
+                    {
+                        Globals.bSoundClick = FALSE;
+                        CheckMenuItem(GetMenu(hDlg), IDM_CLICK_SOUND, MF_BYCOMMAND | MF_UNCHECKED);
+                    }
+
+                    break;
+                }
+
+                case IDM_ABOUT:
+                {
+                    OSK_About();
+                    break;
+                }
+
+                default:
+                    OSK_DlgCommand(wParam, (HWND)lParam);
+                    break;
+            }
+            break;
+
+        case WM_THEMECHANGED:
+            /* Redraw the dialog (and its control buttons) using the new theme */
+            InvalidateRect(hDlg, NULL, FALSE);
             break;
 
         case WM_CLOSE:
@@ -437,44 +640,80 @@ INT_PTR APIENTRY OSK_DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
  *
  *       WinMain
  */
-int WINAPI _tWinMain(HINSTANCE hInstance,
-                     HINSTANCE prev,
-                     LPTSTR cmdline,
-                     int show)
+int WINAPI wWinMain(HINSTANCE hInstance,
+                    HINSTANCE prev,
+                    LPWSTR cmdline,
+                    int show)
 {
     HANDLE hMutex;
+    DWORD dwError;
+    INT LayoutResource;
+    INITCOMMONCONTROLSEX iccex;
 
     UNREFERENCED_PARAMETER(prev);
     UNREFERENCED_PARAMETER(cmdline);
     UNREFERENCED_PARAMETER(show);
 
+    /*
+        Obtain a mutex for the program. This will ensure that
+        the program is launched only once.
+    */
+    hMutex = CreateMutexW(NULL, FALSE, L"OSKRunning");
+
+    if (hMutex)
+    {
+        /* Check if there's already a mutex for the program */
+        dwError = GetLastError();
+
+        if (dwError == ERROR_ALREADY_EXISTS)
+        {
+            /*
+                A mutex with the object name has been created previously.
+                Therefore, another instance is already running.
+            */
+            DPRINT("wWinMain(): Failed to create a mutex! The program instance is already running.\n");
+            CloseHandle(hMutex);
+            return 0;
+        }
+    }
+
+    /* Load the common controls */
+    iccex.dwSize = sizeof(INITCOMMONCONTROLSEX);
+    iccex.dwICC = ICC_STANDARD_CLASSES | ICC_WIN95_CLASSES;
+    InitCommonControlsEx(&iccex);
+
     ZeroMemory(&Globals, sizeof(Globals));
     Globals.hInstance = hInstance;
 
-    /* Rry to open a mutex for a single instance */
-    hMutex = OpenMutexA(MUTEX_ALL_ACCESS, FALSE, "osk");
+    /* Load the settings from the registry hive */
+    LoadDataFromRegistry();
 
-    if (!hMutex)
+    /* If the member of the struct (bShowWarning) is set then display the dialog box */
+    if (Globals.bShowWarning)
     {
-        /* Mutex doesn\92t exist. This is the first instance so create the mutex. */
-        hMutex = CreateMutexA(NULL, FALSE, "osk");
-
-        DialogBox(hInstance,
-                  MAKEINTRESOURCE(MAIN_DIALOG),
-                  GetDesktopWindow(),
-                  OSK_DlgProc);
+        DialogBoxW(Globals.hInstance, MAKEINTRESOURCEW(IDD_WARNINGDIALOG_OSK), Globals.hMainWnd, OSK_WarningProc);
+    }
 
-        /* Delete the mutex */
-        if (hMutex) CloseHandle(hMutex);
+    /* Before initializing the dialog execution, check if the chosen keyboard type is standard or enhanced */
+    if (Globals.bIsEnhancedKeyboard)
+    {
+        LayoutResource = MAIN_DIALOG_ENHANCED_KB;
     }
     else
     {
-        /* Programme already launched */
+        LayoutResource = MAIN_DIALOG_STANDARD_KB;
+    }
 
-        /* Delete the mutex */
-        CloseHandle(hMutex);
+    /* Create the modal box based on the configuration registry */
+    DialogBoxW(hInstance,
+               MAKEINTRESOURCEW(LayoutResource),
+               GetDesktopWindow(),
+               OSK_DlgProc);
 
-        ExitProcess(0);
+    /* Delete the mutex */
+    if (hMutex)
+    {
+        CloseHandle(hMutex);
     }
 
     return 0;