From 322b5222093637be231ad0ad1920bd9eb7432524 Mon Sep 17 00:00:00 2001 From: Schmurtz Date: Fri, 5 Dec 2025 13:56:52 +0100 Subject: [PATCH 1/5] Add option to prevent hiding window on focus lost 'Do not hide Ditto window on deactivate' allows to keep the Ditto window visible when it loses focus, controlled via Options in "Advanced" window. --- CP_Main.rc | 4 ++-- resource.h | 3 ++- src/AdvGeneral.cpp | 10 ++++++++++ src/Options.cpp | 29 +++++++++++++++++++++++++++-- src/Options.h | 8 ++++++++ src/QPasteWnd.cpp | 2 +- 6 files changed, 50 insertions(+), 6 deletions(-) diff --git a/CP_Main.rc b/CP_Main.rc index 74a871e6..f8513fa5 100644 --- a/CP_Main.rc +++ b/CP_Main.rc @@ -813,11 +813,11 @@ BEGIN CONTROL "Elevated privileges to paste into elevated apps",IDC_CHECK_ELEVATE_PRIVILEGES, "Button",BS_AUTOCHECKBOX | WS_TABSTOP,25,129,335,10 CONTROL "Show In Taskbar",IDC_CHECK_SHOW_IN_TASKBAR,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,25,140,335,10 + CONTROL "Show indicator a clip has been pasted",IDC_CHECK_SHOW_CLIP_WAS_PASTED, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,25,151,335,10 EDITTEXT IDC_EDIT_DIFF_PATH,109,162,218,14,ES_AUTOHSCROLL LTEXT "Diff Application Path",IDC_STATIC_DIFF,25,164,80,8 PUSHBUTTON "...",IDC_BUTTON_DIFF_BROWSE,332,162,17,14 - CONTROL "Show indicator a clip has been pasted",IDC_CHECK_SHOW_CLIP_WAS_PASTED, - "Button",BS_AUTOCHECKBOX | WS_TABSTOP,25,151,335,10 END IDD_OPTIONS_KEYSTROKES DIALOGEX 0, 0, 373, 278 diff --git a/resource.h b/resource.h index 2fb2e2ce..392d0bb1 100644 --- a/resource.h +++ b/resource.h @@ -648,6 +648,7 @@ #define IDC_RICHEDIT21 2172 #define IDC_MFCLINK2 2172 #define IDC_EDIT_ACTIVE_APP 2173 +#define IDC_CHECK_DO_NOT_HIDE_ON_DEACTIVATE 2174 #define IDC_EDIT_ADV_FILTER 5001 #define IDC_NEXT_MATCH_BUTTON 5002 #define ID_FIRST_OPTION 32771 @@ -877,7 +878,7 @@ #define _APS_3D_CONTROLS 1 #define _APS_NEXT_RESOURCE_VALUE 394 #define _APS_NEXT_COMMAND_VALUE 32999 -#define _APS_NEXT_CONTROL_VALUE 2173 +#define _APS_NEXT_CONTROL_VALUE 2174 #define _APS_NEXT_SYMED_VALUE 104 #endif #endif diff --git a/src/AdvGeneral.cpp b/src/AdvGeneral.cpp index 31bafaae..af159024 100644 --- a/src/AdvGeneral.cpp +++ b/src/AdvGeneral.cpp @@ -159,6 +159,7 @@ END_MESSAGE_MAP() #define SETTING_CLIP_EDIT_SAVE_DELAY_AFTER_LOAD 105 #define SETTING_ClIP_EDIT_SAVE_DELAY_AFTER_SAVE 106 #define SETTING_WEB_SEARCH_URL 107 +#define SETTING_DO_NOT_HIDE_ON_DEACTIVATE 108 BOOL CAdvGeneral::OnInitDialog() { @@ -223,6 +224,8 @@ BOOL CAdvGeneral::OnInitDialog() AddTrueFalse(pGroupTest, _T("Display icon in system tray"), CGetSetOptions::GetShowIconInSysTray(), SETTING_SHOW_TASKBAR_ICON); + AddTrueFalse(pGroupTest, _T("Do not hide Ditto window on deactivate"), CGetSetOptions::GetDoNotHideOnDeactivate(), SETTING_DO_NOT_HIDE_ON_DEACTIVATE); + pGroupTest->AddSubItem(new CMFCPropertyGridProperty(_T("Double shortcut keystroke timeout)"), (long)CGetSetOptions::GetDoubleKeyStrokeTimeout(), _T(""), SETTING_DOUBLE_KEYSTROKE_TIMEOUT)); AddTrueFalse(pGroupTest, _T("Draw swatch for hex, RGB, and HSL colors"), CGetSetOptions::GetDrawCopiedColorCode(), SETTING_DRAW_COPIED_COLOR_CODE); @@ -961,6 +964,13 @@ void CAdvGeneral::OnBnClickedOk() CGetSetOptions::SetWebSearchUrl(pNewValue->bstrVal); } break; + case SETTING_DO_NOT_HIDE_ON_DEACTIVATE: + if (wcscmp(pNewValue->bstrVal, pOrigValue->bstrVal) != 0) + { + BOOL val = wcscmp(pNewValue->bstrVal, L"True") == 0; + CGetSetOptions::SetDoNotHideOnDeactivate(val); + } + break; } } } diff --git a/src/Options.cpp b/src/Options.cpp index 7df83185..c98f5918 100644 --- a/src/Options.cpp +++ b/src/Options.cpp @@ -31,6 +31,7 @@ BOOL CGetSetOptions::m_bAllowDuplicates; BOOL CGetSetOptions::m_bUpdateTimeOnPaste; BOOL CGetSetOptions::m_bSaveMultiPaste; BOOL CGetSetOptions::m_bShowPersistent; +BOOL CGetSetOptions::m_bHideDittoOnPaste; long CGetSetOptions::m_bDescTextSize; BOOL CGetSetOptions::m_bDescShowLeadingWhiteSpace; BOOL CGetSetOptions::m_bAllwaysShowDescription; @@ -90,6 +91,7 @@ BOOL CGetSetOptions::m_refreshViewAfterPasting = TRUE; BOOL CGetSetOptions::m_supportAllTypes = FALSE; int CGetSetOptions::m_clipEditSaveDelayAfterLoadSeconds = 3; int CGetSetOptions::m_clipEditSaveDelayAfterSaveSeconds = 3; +BOOL CGetSetOptions::m_bDoNotHideOnDeactivate = FALSE; CGetSetOptions::CGetSetOptions() @@ -255,6 +257,7 @@ void CGetSetOptions::LoadSettings() m_bUpdateTimeOnPaste = GetUpdateTimeOnPaste(); m_bSaveMultiPaste = GetSaveMultiPaste(); m_bShowPersistent = GetShowPersistent(); + m_bHideDittoOnPaste = GetHideDittoOnPaste(); m_bDescTextSize = GetDescTextSize(); m_bDescShowLeadingWhiteSpace = GetDescShowLeadingWhiteSpace(); m_bAllwaysShowDescription = GetAllwaysShowDescription(); @@ -294,6 +297,7 @@ void CGetSetOptions::LoadSettings() m_maintainSearchView = GetMaintainSearchView(); m_clipEditSaveDelayAfterLoadSeconds = GetClipEditSaveDelayAfterLoadSeconds(); m_clipEditSaveDelayAfterSaveSeconds = GetClipEditSaveDelayAfterSaveSeconds(); + m_bDoNotHideOnDeactivate = GetDoNotHideOnDeactivate(); GetExtraNetworkPassword(true); @@ -1261,6 +1265,16 @@ BOOL CGetSetOptions::GetShowPersistent() return GetProfileLong("ShowPersistent", 0); } +void CGetSetOptions::SetHideDittoOnPaste(BOOL bVal) +{ + SetProfileLong("HideDittoOnPaste", bVal); + m_bHideDittoOnPaste = bVal; +} +BOOL CGetSetOptions::GetHideDittoOnPaste() +{ + return GetProfileLong("HideDittoOnPaste", 1); +} + void CGetSetOptions::SetShowTextForFirstTenHotKeys(BOOL bVal) { SetProfileLong("ShowTextForFirstTenHotKeys", bVal); @@ -2486,12 +2500,12 @@ int CGetSetOptions::GetWindowsResumeDelayReOpenDbMS() BOOL CGetSetOptions::GetShowMsgWndOnCopyToGroup() { - return GetProfileLong(_T("ShowMsgWndOnCopyToGroup"), TRUE); + return GetProfileLong("ShowMsgWndOnCopyToGroup", TRUE); } void CGetSetOptions::SetShowMsgWndOnCopyToGroup(BOOL val) { - SetProfileLong(_T("ShowMsgWndOnCopyToGroup"), val); + SetProfileLong("ShowMsgWndOnCopyToGroup", val); } int CGetSetOptions::GetActionShortCutA(DWORD action, int pos, CString refData) @@ -3224,4 +3238,15 @@ void CGetSetOptions::GetEditWndPoint(CPoint& point) point.x = 100; point.y = 100; } +} + +void CGetSetOptions::SetDoNotHideOnDeactivate(BOOL val) +{ + SetProfileLong("DoNotHideOnDeactivate", val); + m_bDoNotHideOnDeactivate = val; +} + +BOOL CGetSetOptions::GetDoNotHideOnDeactivate() +{ + return GetProfileLong("DoNotHideOnDeactivate", FALSE); } \ No newline at end of file diff --git a/src/Options.h b/src/Options.h index 7ef3e1ac..5986ba99 100644 --- a/src/Options.h +++ b/src/Options.h @@ -199,6 +199,10 @@ class CGetSetOptions static void SetShowPersistent(BOOL bVal); static BOOL GetShowPersistent(); + static BOOL m_bHideDittoOnPaste; + static void SetHideDittoOnPaste(BOOL bVal); + static BOOL GetHideDittoOnPaste(); + static void SetShowTextForFirstTenHotKeys(BOOL bVal); static BOOL GetShowTextForFirstTenHotKeys(); @@ -719,6 +723,10 @@ class CGetSetOptions static void SetClipEditSaveDelayAfterSaveSeconds(int val); static BOOL GetClipEditSaveDelayAfterSaveSeconds(); + static BOOL m_bDoNotHideOnDeactivate; + static void SetDoNotHideOnDeactivate(BOOL val); + static BOOL GetDoNotHideOnDeactivate(); + static BOOL SetEditWndSize(CSize size); static void GetEditWndSize(CSize& size); diff --git a/src/QPasteWnd.cpp b/src/QPasteWnd.cpp index 8c400587..02d33279 100644 --- a/src/QPasteWnd.cpp +++ b/src/QPasteWnd.cpp @@ -756,7 +756,7 @@ void CQPasteWnd::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized) m_bModifersMoveActive = false; - if (!CGetSetOptions::m_bShowPersistent) + if (!CGetSetOptions::m_bShowPersistent && !CGetSetOptions::m_bDoNotHideOnDeactivate) { HideQPasteWindow(false); } From 21de3d621fe83edc56dd1b7e1ad329430bfb902f Mon Sep 17 00:00:00 2001 From: Schmurtz Date: Fri, 5 Dec 2025 14:09:08 +0100 Subject: [PATCH 2/5] Add option to hide taskbar icon when window closes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New setting 'Hide taskbar icon when Ditto window closes' in the advanced general options. “Show in taskbar” is enabled firstly. Closing Ditto with the “X” button or the Escape key will hide the taskbar icon. So the taskbar icon is visible only when the quick paste window is visible (similar behavior to keepass window). Implements corresponding getter and setter in CGetSetOptions, and updates CQPasteWnd to respect this setting when hiding the main window. --- src/AdvGeneral.cpp | 9 +++++++++ src/Options.cpp | 10 ++++++++++ src/Options.h | 3 +++ src/QPasteWnd.cpp | 2 +- 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/AdvGeneral.cpp b/src/AdvGeneral.cpp index af159024..66cd58e9 100644 --- a/src/AdvGeneral.cpp +++ b/src/AdvGeneral.cpp @@ -160,6 +160,7 @@ END_MESSAGE_MAP() #define SETTING_ClIP_EDIT_SAVE_DELAY_AFTER_SAVE 106 #define SETTING_WEB_SEARCH_URL 107 #define SETTING_DO_NOT_HIDE_ON_DEACTIVATE 108 +#define SETTING_HIDE_TASKBAR_ICON_ON_CLOSE 109 BOOL CAdvGeneral::OnInitDialog() { @@ -302,6 +303,7 @@ BOOL CAdvGeneral::OnInitDialog() AddTrueFalse(pGroupTest, _T("Show clips that are in groups in main list"), CGetSetOptions::GetShowAllClipsInMainList(), SETTING_SHOW_GROUP_CLIPS_IN_LIST); AddTrueFalse(pGroupTest, _T("Show leading whitespace"), CGetSetOptions::GetDescShowLeadingWhiteSpace(), SETTING_SHOW_LEADING_WHITESPACE); AddTrueFalse(pGroupTest, _T("Show in taskbar"), CGetSetOptions::GetShowInTaskBar(), SETTTING_SHOW_IN_TASKBAR); + AddTrueFalse(pGroupTest, _T("Hide taskbar icon when Ditto window closes"), CGetSetOptions::GetHideTaskbarIconOnClose(), SETTING_HIDE_TASKBAR_ICON_ON_CLOSE); AddTrueFalse(pGroupTest, _T("Show indicator a clip has been pasted"), CGetSetOptions::GetShowIfClipWasPasted(), SETTING_SHOW_CLIP_PASTED); AddTrueFalse(pGroupTest, _T("Show message that we received a manual sent clip"), CGetSetOptions::GetShowMsgWhenReceivingManualSentClip(), SETTING_SHOW_MSG_WHEN_RECEIVING_MANUAL_SENT_CLIP); @@ -971,6 +973,13 @@ void CAdvGeneral::OnBnClickedOk() CGetSetOptions::SetDoNotHideOnDeactivate(val); } break; + case SETTING_HIDE_TASKBAR_ICON_ON_CLOSE: + if (wcscmp(pNewValue->bstrVal, pOrigValue->bstrVal) != 0) + { + BOOL val = wcscmp(pNewValue->bstrVal, L"True") == 0; + CGetSetOptions::SetHideTaskbarIconOnClose(val); + } + break; } } } diff --git a/src/Options.cpp b/src/Options.cpp index c98f5918..41051aec 100644 --- a/src/Options.cpp +++ b/src/Options.cpp @@ -2375,6 +2375,16 @@ BOOL CGetSetOptions::GetShowInTaskBar() return GetProfileLong(_T("ShowInTaskBar"), FALSE); } +void CGetSetOptions::SetHideTaskbarIconOnClose(BOOL val) +{ + SetProfileLong(_T("HideTaskbarIconOnClose"), val); +} + +BOOL CGetSetOptions::GetHideTaskbarIconOnClose() +{ + return GetProfileLong(_T("HideTaskbarIconOnClose"), TRUE); +} + void CGetSetOptions::SetDiffApp(CString val) { SetProfileString(_T("DiffApp"), val); diff --git a/src/Options.h b/src/Options.h index 5986ba99..6347f846 100644 --- a/src/Options.h +++ b/src/Options.h @@ -485,6 +485,9 @@ class CGetSetOptions static void SetShowInTaskBar(BOOL val); static BOOL GetShowInTaskBar(); + static void SetHideTaskbarIconOnClose(BOOL val); + static BOOL GetHideTaskbarIconOnClose(); + static void SetDiffApp(CString val); static CString GetDiffApp(); diff --git a/src/QPasteWnd.cpp b/src/QPasteWnd.cpp index 02d33279..4bf5a0a0 100644 --- a/src/QPasteWnd.cpp +++ b/src/QPasteWnd.cpp @@ -851,7 +851,7 @@ BOOL CQPasteWnd::HideQPasteWindow(bool releaseFocus, BOOL clearSearchData) //Save the size SaveWindowSize(); - if (CGetSetOptions::GetShowInTaskBar()) + if (CGetSetOptions::GetShowInTaskBar() && !CGetSetOptions::GetHideTaskbarIconOnClose()) { ShowWindow(SW_MINIMIZE); } From 1697bb4c7f8efaab7341e7ff61a1824495e257f2 Mon Sep 17 00:00:00 2001 From: Schmurtz Date: Fri, 5 Dec 2025 14:27:55 +0100 Subject: [PATCH 3/5] Add theme preview to options dialog A 'Preview' button next to the theme selection, allows to preview the selected theme in the Ditto window. Also updates the Ditto window live when the dropdown theme selection changes. --- CP_Main.rc | 3 ++- resource.h | 1 + src/OptionsGeneral.cpp | 58 ++++++++++++++++++++++++++++++++++++++++++ src/OptionsGeneral.h | 3 +++ 4 files changed, 64 insertions(+), 1 deletion(-) diff --git a/CP_Main.rc b/CP_Main.rc index f8513fa5..8f6d1288 100644 --- a/CP_Main.rc +++ b/CP_Main.rc @@ -909,7 +909,8 @@ BEGIN GROUPBOX "Accepted Copy Applications (separate by ;)",IDC_STATIC_APP_SEP_DESC,7,178,361,55 PUSHBUTTON "Advanced",IDC_BUTTON_ADVANCED,318,256,50,14 COMBOBOX IDC_COMBO_THEME,67,82,130,95,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP - PUSHBUTTON "About Theme",IDC_BUTTON_THEME,207,82,106,14 + PUSHBUTTON "Preview",IDC_BUTTON_PREVIEW_THEME,207,82,50,14 + PUSHBUTTON "About Theme",IDC_BUTTON_THEME,263,82,50,14 PUSHBUTTON "Font",IDC_BUTTON_FONT,67,125,130,14 PUSHBUTTON "Default Font",IDC_BUTTON_DEFAULT_FAULT,207,125,106,14 LTEXT "Theme",IDC_STATIC_THEME,9,82,36,12,SS_CENTERIMAGE diff --git a/resource.h b/resource.h index 392d0bb1..277e20db 100644 --- a/resource.h +++ b/resource.h @@ -649,6 +649,7 @@ #define IDC_MFCLINK2 2172 #define IDC_EDIT_ACTIVE_APP 2173 #define IDC_CHECK_DO_NOT_HIDE_ON_DEACTIVATE 2174 +#define IDC_BUTTON_PREVIEW_THEME 2175 #define IDC_EDIT_ADV_FILTER 5001 #define IDC_NEXT_MATCH_BUTTON 5002 #define ID_FIRST_OPTION 32771 diff --git a/src/OptionsGeneral.cpp b/src/OptionsGeneral.cpp index c9239e57..ba1962b3 100644 --- a/src/OptionsGeneral.cpp +++ b/src/OptionsGeneral.cpp @@ -74,6 +74,8 @@ BEGIN_MESSAGE_MAP(COptionsGeneral, CPropertyPage) ON_BN_CLICKED(IDC_BUTTON_ADVANCED, &COptionsGeneral::OnBnClickedButtonAdvanced) ON_WM_CTLCOLOR() ON_BN_CLICKED(IDC_BUTTON_THEME, &COptionsGeneral::OnBnClickedButtonTheme) + ON_BN_CLICKED(IDC_BUTTON_PREVIEW_THEME, &COptionsGeneral::OnBnClickedButtonPreviewTheme) + ON_CBN_SELCHANGE(IDC_COMBO_THEME, &COptionsGeneral::OnCbnSelchangeComboTheme) ON_BN_CLICKED(IDC_BUTTON_DEFAULT_FAULT, &COptionsGeneral::OnBnClickedButtonDefaultFault) ON_BN_CLICKED(IDC_BUTTON_FONT, &COptionsGeneral::OnBnClickedButtonFont) ON_EN_CHANGE(IDC_PATH, &COptionsGeneral::OnEnChangePath) @@ -679,3 +681,59 @@ void COptionsGeneral::OnClickedExpireEntries() m_eExpireAfter.EnableWindow(FALSE); } } + +void COptionsGeneral::OnBnClickedButtonPreviewTheme() +{ + ApplySelectedThemeToPreview(); + + // Show the Ditto window next to the options dialog + if (theApp.m_pMainFrame != NULL) + { + CQPasteWnd* pPasteWnd = theApp.m_pMainFrame->m_quickPaste.m_pwndPaste; + + // If window already exists, just show it and refresh + if (pPasteWnd != NULL && IsWindow(pPasteWnd->m_hWnd)) + { + pPasteWnd->ShowWindow(SW_SHOW); + pPasteWnd->SetForegroundWindow(); + pPasteWnd->Invalidate(); + pPasteWnd->UpdateWindow(); + } + else + { + // Window doesn't exist, create and show it + theApp.m_pMainFrame->m_quickPaste.ShowQPasteWnd(theApp.m_pMainFrame, true, false, TRUE); + } + } +} + +void COptionsGeneral::OnCbnSelchangeComboTheme() +{ + // If the Ditto window is currently visible, update the theme live + if (theApp.m_pMainFrame != NULL && theApp.m_pMainFrame->m_quickPaste.IsWindowVisibleEx()) + { + ApplySelectedThemeToPreview(); + + // Refresh the window to apply the new theme + if (theApp.m_pMainFrame->m_quickPaste.m_pwndPaste != NULL) + { + theApp.m_pMainFrame->m_quickPaste.m_pwndPaste->Invalidate(); + theApp.m_pMainFrame->m_quickPaste.m_pwndPaste->UpdateWindow(); + } + } +} + +void COptionsGeneral::ApplySelectedThemeToPreview() +{ + CString csTheme = _T(""); + if (m_cbTheme.GetCurSel() >= 0) + { + if (m_cbTheme.GetItemData(m_cbTheme.GetCurSel()) == 1) + { + m_cbTheme.GetLBText(m_cbTheme.GetCurSel(), csTheme); + } + } + + // Load the selected theme + CGetSetOptions::m_Theme.Load(csTheme, false, true); +} diff --git a/src/OptionsGeneral.h b/src/OptionsGeneral.h index 2def8042..0fc6e25d 100644 --- a/src/OptionsGeneral.h +++ b/src/OptionsGeneral.h @@ -88,6 +88,9 @@ class COptionsGeneral : public CPropertyPage afx_msg void OnBnClickedButtonAdvanced(); afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor); afx_msg void OnBnClickedButtonTheme(); + afx_msg void OnBnClickedButtonPreviewTheme(); + afx_msg void OnCbnSelchangeComboTheme(); + void ApplySelectedThemeToPreview(); afx_msg void OnBnClickedButtonDefaultFault(); afx_msg void OnBnClickedButtonFont(); CComboBox m_cbTheme; From 400cc1a0886cdafa60934bfa499b2f5bb9a62bfa Mon Sep 17 00:00:00 2001 From: Schmurtz Date: Sat, 6 Dec 2025 17:11:24 +0100 Subject: [PATCH 4/5] Improve theme preview and refresh logic Fix theme preview & caption bar theme apply Refactored the theme preview button to ensure the QPaste window is created and visible before applying the selected theme. --- src/OptionsGeneral.cpp | 27 +++++++++++++-------------- src/QPasteWnd.cpp | 11 +++++++++++ src/QPasteWnd.h | 2 ++ 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/OptionsGeneral.cpp b/src/OptionsGeneral.cpp index ba1962b3..f34b4b23 100644 --- a/src/OptionsGeneral.cpp +++ b/src/OptionsGeneral.cpp @@ -684,26 +684,26 @@ void COptionsGeneral::OnClickedExpireEntries() void COptionsGeneral::OnBnClickedButtonPreviewTheme() { - ApplySelectedThemeToPreview(); - - // Show the Ditto window next to the options dialog if (theApp.m_pMainFrame != NULL) { CQPasteWnd* pPasteWnd = theApp.m_pMainFrame->m_quickPaste.m_pwndPaste; - - // If window already exists, just show it and refresh + + // If the window doesn't exist yet, create/show it first (will load persisted theme) + if (pPasteWnd == NULL || IsWindow(pPasteWnd->m_hWnd) == FALSE) + { + theApp.m_pMainFrame->m_quickPaste.ShowQPasteWnd(theApp.m_pMainFrame, true, false, TRUE); + pPasteWnd = theApp.m_pMainFrame->m_quickPaste.m_pwndPaste; + } + + // Ensure it is visible before applying preview theme if (pPasteWnd != NULL && IsWindow(pPasteWnd->m_hWnd)) { pPasteWnd->ShowWindow(SW_SHOW); pPasteWnd->SetForegroundWindow(); - pPasteWnd->Invalidate(); - pPasteWnd->UpdateWindow(); - } - else - { - // Window doesn't exist, create and show it - theApp.m_pMainFrame->m_quickPaste.ShowQPasteWnd(theApp.m_pMainFrame, true, false, TRUE); } + + // Reuse the dropdown-change logic to load/apply the selected theme + OnCbnSelchangeComboTheme(); } } @@ -717,8 +717,7 @@ void COptionsGeneral::OnCbnSelchangeComboTheme() // Refresh the window to apply the new theme if (theApp.m_pMainFrame->m_quickPaste.m_pwndPaste != NULL) { - theApp.m_pMainFrame->m_quickPaste.m_pwndPaste->Invalidate(); - theApp.m_pMainFrame->m_quickPaste.m_pwndPaste->UpdateWindow(); + theApp.m_pMainFrame->m_quickPaste.m_pwndPaste->RefreshThemeColors(); } } } diff --git a/src/QPasteWnd.cpp b/src/QPasteWnd.cpp index 4bf5a0a0..1d848fbd 100644 --- a/src/QPasteWnd.cpp +++ b/src/QPasteWnd.cpp @@ -7741,6 +7741,17 @@ bool CQPasteWnd::DoActionGmail() return true; } +void CQPasteWnd::RefreshThemeColors() +{ + // Refresh caption bar colors + SetCaptionColorActive(CGetSetOptions::m_bShowPersistent, theApp.GetConnectCV()); + SetCaptionOn(CGetSetOptions::GetCaptionPos(), true, CGetSetOptions::m_Theme.GetCaptionSize(), CGetSetOptions::m_Theme.GetCaptionFontSize()); + + // Force repaint of the entire window including non-client area + SetWindowPos(NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); + RedrawWindow(NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ALLCHILDREN | RDW_FRAME); +} + bool CQPasteWnd::DoActionEmailToAttachExport() { CWaitCursor wait; diff --git a/src/QPasteWnd.h b/src/QPasteWnd.h index 5c67d6ee..4ae3e478 100644 --- a/src/QPasteWnd.h +++ b/src/QPasteWnd.h @@ -322,6 +322,8 @@ class CQPasteWnd: public CWndEx bool OnGlobalHotkyes(); void UpdateMenuShortCut(CCmdUI *pCmdUI, DWORD action); + // Refresh all theme colors (caption, scrollbars, etc.) + void RefreshThemeColors(); bool ShowProperties(int id, int row); bool DeleteClips(CClipIDs &IDs, ARRAY &Indexs); From 118e7107bf332c72fcd23fe9460f3fd851bf86ee Mon Sep 17 00:00:00 2001 From: Schmurtz Date: Sat, 6 Dec 2025 20:50:43 +0100 Subject: [PATCH 5/5] Add modern scrollbars with theme support New modern scrollbars (vertical and horizontal) control with rounded corners and themeable colors. --- CP_Main.vcxproj | 2 + src/AdvGeneral.cpp | 9 + src/ModernScrollBar.cpp | 583 ++++++++++++++++++++++++++++++++++++++++ src/ModernScrollBar.h | 99 +++++++ src/Options.cpp | 13 + src/Options.h | 4 + src/QListCtrl.cpp | 67 ++++- src/QListCtrl.h | 1 + src/QPasteWnd.cpp | 79 +++++- src/QPasteWnd.h | 11 +- src/QuickPaste.cpp | 4 + src/Theme.cpp | 10 + src/Theme.h | 10 + 13 files changed, 875 insertions(+), 17 deletions(-) create mode 100644 src/ModernScrollBar.cpp create mode 100644 src/ModernScrollBar.h diff --git a/CP_Main.vcxproj b/CP_Main.vcxproj index aa80bf10..17f90484 100644 --- a/CP_Main.vcxproj +++ b/CP_Main.vcxproj @@ -608,6 +608,7 @@ + @@ -875,6 +876,7 @@ + diff --git a/src/AdvGeneral.cpp b/src/AdvGeneral.cpp index 66cd58e9..8e8232e0 100644 --- a/src/AdvGeneral.cpp +++ b/src/AdvGeneral.cpp @@ -161,6 +161,7 @@ END_MESSAGE_MAP() #define SETTING_WEB_SEARCH_URL 107 #define SETTING_DO_NOT_HIDE_ON_DEACTIVATE 108 #define SETTING_HIDE_TASKBAR_ICON_ON_CLOSE 109 +#define SETTING_USE_MODERN_SCROLLBAR 110 BOOL CAdvGeneral::OnInitDialog() { @@ -200,6 +201,7 @@ BOOL CAdvGeneral::OnInitDialog() AddTrueFalse(pGroupTest, _T("Allow back to back duplicates (if allowing duplicates)"), CGetSetOptions::GetAllowBackToBackDuplicates(), SETTING_ALOW_BACK_TO_BACK_DUPLICATES); AddTrueFalse(pGroupTest, _T("Always show scroll bar"), CGetSetOptions::GetShowScrollBar(), SETTING_ALWAYS_SHOW_SCROLL_BAR); + AddTrueFalse(pGroupTest, _T("Use modern scroll bar"), CGetSetOptions::GetUseModernScrollBar(), SETTING_USE_MODERN_SCROLLBAR); AddTrueFalse(pGroupTest, _T("Append Computer Name and IP when receiving clips"), CGetSetOptions::GetAppendRemoveComputerNameAndIPToDescription(), SETTING_APPEND_NAME_IP); pGroupTest->AddSubItem(new CMFCPropertyGridProperty(_T("Amount of text to save for description"), CGetSetOptions::m_bDescTextSize, _T(""), SETTING_DESC_SIZE)); @@ -576,6 +578,13 @@ void CAdvGeneral::OnBnClickedOk() CGetSetOptions::SetShowScrollBar(val); } break; + case SETTING_USE_MODERN_SCROLLBAR: + if (wcscmp(pNewValue->bstrVal, pOrigValue->bstrVal) != 0) + { + BOOL val = wcscmp(pNewValue->bstrVal, L"True") == 0; + CGetSetOptions::SetUseModernScrollBar(val); + } + break; case SETTING_PASTE_AS_ADMIN: if (wcscmp(pNewValue->bstrVal, pOrigValue->bstrVal) != 0) { diff --git a/src/ModernScrollBar.cpp b/src/ModernScrollBar.cpp new file mode 100644 index 00000000..d7ffe70e --- /dev/null +++ b/src/ModernScrollBar.cpp @@ -0,0 +1,583 @@ +#include "stdafx.h" +#include "ModernScrollBar.h" +#include "CP_Main.h" +#include "QListCtrl.h" +#include + +#pragma comment(lib, "gdiplus.lib") + +using namespace Gdiplus; + +IMPLEMENT_DYNAMIC(CModernScrollBar, CWnd) + +CModernScrollBar::CModernScrollBar() + : m_pListCtrl(NULL) + , m_pParentWnd(NULL) + , m_pDPI(NULL) + , m_orientation(ScrollBarOrientation::Vertical) + , m_trackColor(RGB(240, 240, 240)) + , m_thumbColor(RGB(180, 180, 180)) + , m_thumbHoverColor(RGB(140, 140, 140)) + , m_scrollBarWidth(8) + , m_scrollBarHoverWidth(12) + , m_cornerRadius(4) + , m_minThumbSize(30) + , m_isMouseOver(false) + , m_isDragging(false) + , m_dragStartPos(0) + , m_dragStartScrollPos(0) + , m_isVisible(false) + , m_trackingMouse(false) +{ +} + +CModernScrollBar::~CModernScrollBar() +{ +} + +BEGIN_MESSAGE_MAP(CModernScrollBar, CWnd) + ON_WM_PAINT() + ON_WM_ERASEBKGND() + ON_WM_MOUSEMOVE() + ON_WM_MOUSELEAVE() + ON_WM_LBUTTONDOWN() + ON_WM_LBUTTONUP() + ON_WM_TIMER() + ON_MESSAGE(WM_MOUSEHOVER, OnMouseHover) +END_MESSAGE_MAP() + +BOOL CModernScrollBar::Create(CWnd* pParentWnd, CListCtrl* pListCtrl, ScrollBarOrientation orientation) +{ + m_pParentWnd = pParentWnd; + m_pListCtrl = pListCtrl; + m_orientation = orientation; + + // Create as a child window with no border + CString className = AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW, + ::LoadCursor(NULL, IDC_ARROW), NULL, NULL); + + DWORD dwStyle = WS_CHILD | WS_CLIPSIBLINGS; + DWORD dwExStyle = 0; + + CRect rect(0, 0, 10, 100); + + if (!CWnd::CreateEx(dwExStyle, className, _T(""), dwStyle, rect, pParentWnd, 0)) + return FALSE; + + return TRUE; +} + +void CModernScrollBar::SetColors(COLORREF trackColor, COLORREF thumbColor, COLORREF thumbHoverColor) +{ + m_trackColor = trackColor; + m_thumbColor = thumbColor; + m_thumbHoverColor = thumbHoverColor; + + if (m_hWnd && IsWindowVisible()) + Invalidate(); +} + +void CModernScrollBar::UpdateScrollBar() +{ + if (!m_pListCtrl || !m_pListCtrl->m_hWnd) + return; + + // Get scroll info for the appropriate orientation + int scrollBarType = (m_orientation == ScrollBarOrientation::Vertical) ? SB_VERT : SB_HORZ; + + SCROLLINFO si = { sizeof(SCROLLINFO) }; + si.fMask = SIF_ALL; + m_pListCtrl->GetScrollInfo(scrollBarType, &si); + + // Check if scrollbar is needed + bool needsScrollBar = (si.nMax > 0 && si.nPage > 0 && (int)si.nPage <= si.nMax); + + if (!needsScrollBar) + { + if (IsWindowVisible()) + ShowWindow(SW_HIDE); + return; + } + + // Get the list control position relative to parent + CRect listRectInParent; + m_pListCtrl->GetWindowRect(&listRectInParent); + m_pParentWnd->ScreenToClient(&listRectInParent); + + // Get parent client rect to ensure we stay within visible area + CRect parentClientRect; + m_pParentWnd->GetClientRect(&parentClientRect); + + // Calculate scrollbar size based on DPI and hover state + int scrollSize = (m_isMouseOver || m_isDragging) ? m_scrollBarHoverWidth : m_scrollBarWidth; + if (m_pDPI) + { + scrollSize = m_pDPI->Scale(scrollSize); + } + + // The list control is clipped by a region to hide the native scrollbar. + // searchRowStart is 33 (the height reserved for search bar and options button). + int searchRowStart = 33; + if (m_pDPI) + searchRowStart = m_pDPI->Scale(33); + + int visibleBottom = parentClientRect.bottom - searchRowStart; + + CRect scrollRect; + + if (m_orientation == ScrollBarOrientation::Vertical) + { + // Position the scrollbar on the right side + int rightEdge = parentClientRect.right; + if (listRectInParent.right < rightEdge) + rightEdge = listRectInParent.right; + + scrollRect.left = rightEdge - scrollSize; + scrollRect.top = listRectInParent.top; + scrollRect.right = rightEdge; + scrollRect.bottom = visibleBottom; + } + else + { + // Position the scrollbar on the bottom + // Leave space for the vertical scrollbar on the right + int vertScrollWidth = m_scrollBarHoverWidth; // Use hover width to ensure no overlap + if (m_pDPI) + vertScrollWidth = m_pDPI->Scale(vertScrollWidth); + + scrollRect.left = listRectInParent.left; + scrollRect.top = visibleBottom - scrollSize; + scrollRect.right = parentClientRect.right - vertScrollWidth; // Stop before vertical scrollbar + scrollRect.bottom = visibleBottom; + } + + // Only move if position changed + CRect currentRect; + GetWindowRect(¤tRect); + m_pParentWnd->ScreenToClient(¤tRect); + + if (currentRect != scrollRect) + { + MoveWindow(&scrollRect); + } + + // Ensure visible + if (!IsWindowVisible()) + ShowWindow(SW_SHOWNA); + + // Redraw + Invalidate(); + + // Bring to front + SetWindowPos(&CWnd::wndTop, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); +} + +CRect CModernScrollBar::GetThumbRect() +{ + CRect thumbRect(0, 0, 0, 0); + + if (!m_pListCtrl || !m_pListCtrl->m_hWnd) + return thumbRect; + + CRect clientRect; + GetClientRect(&clientRect); + + // Get scroll info for appropriate orientation + int scrollBarType = (m_orientation == ScrollBarOrientation::Vertical) ? SB_VERT : SB_HORZ; + + SCROLLINFO si = { sizeof(SCROLLINFO) }; + si.fMask = SIF_ALL; + m_pListCtrl->GetScrollInfo(scrollBarType, &si); + + if (si.nMax <= 0 || si.nPage <= 0) + return thumbRect; + + int trackSize = (m_orientation == ScrollBarOrientation::Vertical) ? clientRect.Height() : clientRect.Width(); + int totalRange = si.nMax - si.nMin + 1; + + // Calculate thumb size proportionally + int thumbSize = (int)((double)si.nPage / totalRange * trackSize); + + // Apply minimum thumb size + int minSize = m_minThumbSize; + if (m_pDPI) + minSize = m_pDPI->Scale(m_minThumbSize); + + if (thumbSize < minSize) + thumbSize = minSize; + + if (thumbSize > trackSize) + thumbSize = trackSize; + + // Calculate thumb position + int scrollableRange = totalRange - si.nPage; + double scrollRatio = 0; + if (scrollableRange > 0) + scrollRatio = (double)si.nPos / scrollableRange; + + int thumbPos = (int)(scrollRatio * (trackSize - thumbSize)); + + // Clamp to valid range + if (thumbPos < 0) thumbPos = 0; + if (thumbPos + thumbSize > trackSize) + thumbPos = trackSize - thumbSize; + + if (m_orientation == ScrollBarOrientation::Vertical) + { + thumbRect.left = -1; + thumbRect.right = clientRect.Width(); + thumbRect.top = thumbPos; + thumbRect.bottom = thumbPos + thumbSize; + } + else + { + thumbRect.left = thumbPos; + thumbRect.right = thumbPos + thumbSize; + thumbRect.top = -1; + thumbRect.bottom = clientRect.Height(); + } + + return thumbRect; +} + +void CModernScrollBar::DrawRoundedRect(CDC* pDC, CRect rect, int radius, COLORREF color) +{ + // Use GDI+ for anti-aliased rounded rectangles + Graphics graphics(pDC->GetSafeHdc()); + graphics.SetSmoothingMode(SmoothingModeAntiAlias); + + // Create solid color (no transparency) + Color gdipColor(255, GetRValue(color), GetGValue(color), GetBValue(color)); + SolidBrush brush(gdipColor); + + // Draw rounded rectangle path + GraphicsPath path; + + int diameter = radius * 2; + + // Handle very small rectangles + if (rect.Width() < diameter || rect.Height() < diameter) + { + // Just draw an ellipse or simple rect + if (rect.Width() <= 0 || rect.Height() <= 0) + return; + path.AddEllipse(rect.left, rect.top, rect.Width(), rect.Height()); + } + else + { + // Top-left corner + path.AddArc(rect.left, rect.top, diameter, diameter, 180, 90); + // Top-right corner + path.AddArc(rect.right - diameter, rect.top, diameter, diameter, 270, 90); + // Bottom-right corner + path.AddArc(rect.right - diameter, rect.bottom - diameter, diameter, diameter, 0, 90); + // Bottom-left corner + path.AddArc(rect.left, rect.bottom - diameter, diameter, diameter, 90, 90); + path.CloseFigure(); + } + + graphics.FillPath(&brush, &path); +} + +void CModernScrollBar::OnPaint() +{ + CPaintDC dc(this); + + CRect clientRect; + GetClientRect(&clientRect); + + // Create memory DC for double buffering + CDC memDC; + memDC.CreateCompatibleDC(&dc); + + CBitmap memBitmap; + memBitmap.CreateCompatibleBitmap(&dc, clientRect.Width(), clientRect.Height()); + CBitmap* pOldBitmap = memDC.SelectObject(&memBitmap); + + // Fill with track color (solid background) + memDC.FillSolidRect(&clientRect, m_trackColor); + + // Get thumb rect + CRect thumbRect = GetThumbRect(); + + if (!thumbRect.IsRectEmpty()) + { + int radius = m_cornerRadius; + if (m_pDPI) + radius = m_pDPI->Scale(m_cornerRadius); + + // Determine thumb color based on state + COLORREF thumbColor = m_isMouseOver || m_isDragging ? m_thumbHoverColor : m_thumbColor; + + // Draw the thumb + DrawRoundedRect(&memDC, thumbRect, radius, thumbColor); + } + + // Copy to screen + dc.BitBlt(0, 0, clientRect.Width(), clientRect.Height(), &memDC, 0, 0, SRCCOPY); + + memDC.SelectObject(pOldBitmap); +} + +BOOL CModernScrollBar::OnEraseBkgnd(CDC* pDC) +{ + // Don't erase - we handle it in OnPaint + return TRUE; +} + +void CModernScrollBar::OnMouseMove(UINT nFlags, CPoint point) +{ + if (!m_trackingMouse) + { + TRACKMOUSEEVENT tme = { sizeof(TRACKMOUSEEVENT) }; + tme.dwFlags = TME_LEAVE | TME_HOVER; + tme.hwndTrack = m_hWnd; + tme.dwHoverTime = 1; // Immediate hover detection + TrackMouseEvent(&tme); + m_trackingMouse = true; + } + + CRect clientRect; + GetClientRect(&clientRect); + bool wasMouseOver = m_isMouseOver; + m_isMouseOver = clientRect.PtInRect(point); + + if (wasMouseOver != m_isMouseOver) + { + // Update scrollbar size when hover state changes + UpdateScrollBar(); + } + + if (m_isDragging && m_pListCtrl) + { + if (m_orientation == ScrollBarOrientation::Vertical) + { + int deltaY = point.y - m_dragStartPos; + ScrollToPosition(m_dragStartScrollPos + deltaY); + } + else + { + int deltaX = point.x - m_dragStartPos; + ScrollToPosition(m_dragStartScrollPos + deltaX); + } + } + + CWnd::OnMouseMove(nFlags, point); +} + +void CModernScrollBar::OnMouseLeave() +{ + m_trackingMouse = false; + + if (m_isMouseOver) + { + m_isMouseOver = false; + // Update scrollbar size when hover state changes (shrink back) + UpdateScrollBar(); + + // If we leave the scrollbar area and we are in auto-hide mode, + // restart the timer to hide it eventually + if (m_isVisible && !CGetSetOptions::m_showScrollBar && !m_isDragging) + { + SetTimer(TIMER_AUTO_HIDE, 800, NULL); + } + } + + CWnd::OnMouseLeave(); +} + +LRESULT CModernScrollBar::OnMouseHover(WPARAM wParam, LPARAM lParam) +{ + return 0; +} + +void CModernScrollBar::OnLButtonDown(UINT nFlags, CPoint point) +{ + CRect thumbRect = GetThumbRect(); + + if (thumbRect.PtInRect(point)) + { + // Start dragging the thumb + m_isDragging = true; + m_dragStartPos = (m_orientation == ScrollBarOrientation::Vertical) ? point.y : point.x; + + // Get current scroll position in thumb coordinates + m_dragStartScrollPos = (m_orientation == ScrollBarOrientation::Vertical) ? thumbRect.top : thumbRect.left; + + SetCapture(); + Invalidate(); + } + else + { + // Click on track - page scroll + if (m_orientation == ScrollBarOrientation::Vertical) + { + if (point.y < thumbRect.top) + { + m_pListCtrl->SendMessage(WM_VSCROLL, SB_PAGEUP, 0); + } + else if (point.y > thumbRect.bottom) + { + m_pListCtrl->SendMessage(WM_VSCROLL, SB_PAGEDOWN, 0); + } + } + else + { + // For horizontal, use Scroll with page width + CRect clientRect; + m_pListCtrl->GetClientRect(&clientRect); + int pageWidth = clientRect.Width(); + + if (point.x < thumbRect.left) + { + m_pListCtrl->Scroll(CSize(-pageWidth, 0)); + } + else if (point.x > thumbRect.right) + { + m_pListCtrl->Scroll(CSize(pageWidth, 0)); + } + } + + UpdateScrollBar(); + } + + CWnd::OnLButtonDown(nFlags, point); +} + +void CModernScrollBar::OnLButtonUp(UINT nFlags, CPoint point) +{ + if (m_isDragging) + { + m_isDragging = false; + ReleaseCapture(); + Invalidate(); + } + + CWnd::OnLButtonUp(nFlags, point); +} + +void CModernScrollBar::ScrollToPosition(int thumbPos) +{ + if (!m_pListCtrl) + return; + + CRect clientRect; + GetClientRect(&clientRect); + + int scrollBarType = (m_orientation == ScrollBarOrientation::Vertical) ? SB_VERT : SB_HORZ; + + SCROLLINFO si = { sizeof(SCROLLINFO) }; + si.fMask = SIF_ALL; + m_pListCtrl->GetScrollInfo(scrollBarType, &si); + + if (si.nMax <= 0 || si.nPage <= 0) + return; + + int trackSize = (m_orientation == ScrollBarOrientation::Vertical) ? clientRect.Height() : clientRect.Width(); + int totalRange = si.nMax - si.nMin + 1; + + // Calculate thumb size + int thumbSize = (int)((double)si.nPage / totalRange * trackSize); + int minSize = m_minThumbSize; + if (m_pDPI) + minSize = m_pDPI->Scale(m_minThumbSize); + if (thumbSize < minSize) + thumbSize = minSize; + + // Clamp thumb position + if (thumbPos < 0) thumbPos = 0; + if (thumbPos > trackSize - thumbSize) + thumbPos = trackSize - thumbSize; + + // Calculate new scroll position + int scrollableTrack = trackSize - thumbSize; + int scrollableRange = totalRange - si.nPage; + + int newPos = 0; + if (scrollableTrack > 0) + newPos = (int)((double)thumbPos / scrollableTrack * scrollableRange); + + if (m_orientation == ScrollBarOrientation::Vertical) + { + // Use Scroll method for smoother scrolling with virtual lists + CQListCtrl* pQListCtrl = (CQListCtrl*)m_pListCtrl; + int rowHeight = pQListCtrl->GetRowHeight(); + int currentTop = m_pListCtrl->GetTopIndex(); + + if (rowHeight > 0) + { + int deltaRows = newPos - currentTop; + int deltaPixels = deltaRows * rowHeight; + + if (deltaPixels != 0) + { + m_pListCtrl->Scroll(CSize(0, deltaPixels)); + } + } + else + { + // Fallback if row height is not available + si.fMask = SIF_POS; + si.nPos = newPos; + m_pListCtrl->SetScrollInfo(SB_VERT, &si); + m_pListCtrl->SendMessage(WM_VSCROLL, MAKEWPARAM(SB_THUMBPOSITION, newPos), 0); + } + } + else + { + // Horizontal scrolling - use Scroll method for more reliable scrolling + int currentScrollPos = m_pListCtrl->GetScrollPos(SB_HORZ); + int deltaPixels = newPos - currentScrollPos; + + if (deltaPixels != 0) + { + m_pListCtrl->Scroll(CSize(deltaPixels, 0)); + } + } + + Invalidate(); +} + +void CModernScrollBar::Show(bool animate) +{ + m_isVisible = true; + ShowWindow(SW_SHOWNA); + + // Kill any pending hide timer + KillTimer(TIMER_AUTO_HIDE); + + // Start auto-hide timer (hide after 800ms of inactivity) + // Only if the option to always show scrollbar is NOT enabled + if (!CGetSetOptions::m_showScrollBar) + { + SetTimer(TIMER_AUTO_HIDE, 800, NULL); + } + + UpdateScrollBar(); +} + +void CModernScrollBar::Hide(bool animate) +{ + m_isVisible = false; + ShowWindow(SW_HIDE); + KillTimer(TIMER_AUTO_HIDE); +} + +void CModernScrollBar::OnTimer(UINT_PTR nIDEvent) +{ + if (nIDEvent == TIMER_AUTO_HIDE) + { + // Don't hide if mouse is over or dragging + if (!m_isMouseOver && !m_isDragging) + { + // Don't auto-hide if the option to always show scrollbar is enabled + if (!CGetSetOptions::m_showScrollBar) + { + Hide(true); + } + } + KillTimer(TIMER_AUTO_HIDE); + } + + CWnd::OnTimer(nIDEvent); +} diff --git a/src/ModernScrollBar.h b/src/ModernScrollBar.h new file mode 100644 index 00000000..85cf7af5 --- /dev/null +++ b/src/ModernScrollBar.h @@ -0,0 +1,99 @@ +#pragma once + +#include +#include "DPI.h" + +// Scrollbar orientation +enum class ScrollBarOrientation +{ + Vertical, + Horizontal +}; + +// Modern scrollbar overlay control with rounded corners +// Similar to Discord, Teams, GitHub Desktop style +class CModernScrollBar : public CWnd +{ + DECLARE_DYNAMIC(CModernScrollBar) + +public: + CModernScrollBar(); + virtual ~CModernScrollBar(); + + // Create the scrollbar overlay + BOOL Create(CWnd* pParentWnd, CListCtrl* pListCtrl, ScrollBarOrientation orientation = ScrollBarOrientation::Vertical); + + // Update scrollbar position and visibility based on list state + void UpdateScrollBar(); + + // Set scrollbar colors from theme + void SetColors(COLORREF trackColor, COLORREF thumbColor, COLORREF thumbHoverColor); + + // Set rounded corner radius + void SetCornerRadius(int radius) { m_cornerRadius = radius; } + + // Set scrollbar width/height (depending on orientation) + void SetWidth(int width) { m_scrollBarWidth = width; } + + // Show/hide with fade animation + void Show(bool animate = true); + void Hide(bool animate = true); + + // Check if mouse is over scrollbar + bool IsMouseOver() const { return m_isMouseOver; } + + // DPI awareness + void SetDPI(CDPI* pDPI) { m_pDPI = pDPI; } + + // Get orientation + ScrollBarOrientation GetOrientation() const { return m_orientation; } + +protected: + DECLARE_MESSAGE_MAP() + + afx_msg void OnPaint(); + afx_msg BOOL OnEraseBkgnd(CDC* pDC); + afx_msg void OnMouseMove(UINT nFlags, CPoint point); + afx_msg void OnMouseLeave(); + afx_msg void OnLButtonDown(UINT nFlags, CPoint point); + afx_msg void OnLButtonUp(UINT nFlags, CPoint point); + afx_msg void OnTimer(UINT_PTR nIDEvent); + afx_msg LRESULT OnMouseHover(WPARAM wParam, LPARAM lParam); + + // Calculate thumb rectangle based on list scroll position + CRect GetThumbRect(); + + // Draw rounded rectangle with GDI+ + void DrawRoundedRect(CDC* pDC, CRect rect, int radius, COLORREF color); + + // Scroll list to position based on thumb drag + void ScrollToPosition(int thumbPos); + +private: + CListCtrl* m_pListCtrl; + CWnd* m_pParentWnd; + CDPI* m_pDPI; + ScrollBarOrientation m_orientation; + + // Colors + COLORREF m_trackColor; + COLORREF m_thumbColor; + COLORREF m_thumbHoverColor; + + // Dimensions + int m_scrollBarWidth; + int m_scrollBarHoverWidth; + int m_cornerRadius; + int m_minThumbSize; // Min thumb width or height depending on orientation + + // State + bool m_isMouseOver; + bool m_isDragging; + int m_dragStartPos; // X or Y depending on orientation + int m_dragStartScrollPos; + bool m_isVisible; + bool m_trackingMouse; + + // Timer + enum { TIMER_AUTO_HIDE = 1 }; +}; diff --git a/src/Options.cpp b/src/Options.cpp index 41051aec..e25bcbda 100644 --- a/src/Options.cpp +++ b/src/Options.cpp @@ -72,6 +72,7 @@ CString CGetSetOptions::m_csIniFileName; __int64 CGetSetOptions::nLastDbWriteTime = 0; CTheme CGetSetOptions::m_Theme; BOOL CGetSetOptions::m_showScrollBar = false; +BOOL CGetSetOptions::m_useModernScrollBar = TRUE; BOOL CGetSetOptions::m_bShowAlwaysOnTopWarning = TRUE; CRegExFilterHelper CGetSetOptions::m_regexHelper; CString CGetSetOptions::m_ignoreAnnoyingCFDIB = ""; @@ -287,6 +288,7 @@ void CGetSetOptions::LoadSettings() m_outputDebugStringLogging = GetEnableOutputDebugStringLogging(); m_bEnsureConnectToClipboard = GetEnsureConnectToClipboard(); m_showScrollBar = GetShowScrollBar(); + m_useModernScrollBar = GetUseModernScrollBar(); m_bShowAlwaysOnTopWarning = GetShowAlwaysOnTopWarning(); m_ignoreAnnoyingCFDIB = GetIgnoreAnnoyingCFDIB(); m_doubleKeyStrokeTimeout = GetDoubleKeyStrokeTimeout(); @@ -2299,6 +2301,17 @@ BOOL CGetSetOptions::GetShowScrollBar() return GetProfileLong(_T("ShowScrollBar"), 0); } +void CGetSetOptions::SetUseModernScrollBar(BOOL val) +{ + m_useModernScrollBar = val; + SetProfileLong(_T("UseModernScrollBar"), val); +} + +BOOL CGetSetOptions::GetUseModernScrollBar() +{ + return GetProfileLong(_T("UseModernScrollBar"), TRUE); +} + void CGetSetOptions::SetPasteAsAdmin(BOOL val) { SetProfileLong(_T("PasteAsAdmin"), val); diff --git a/src/Options.h b/src/Options.h index 6347f846..0060556d 100644 --- a/src/Options.h +++ b/src/Options.h @@ -464,6 +464,10 @@ class CGetSetOptions static BOOL GetShowScrollBar(); static BOOL m_showScrollBar; + static void SetUseModernScrollBar(BOOL val); + static BOOL GetUseModernScrollBar(); + static BOOL m_useModernScrollBar; + static void SetPasteAsAdmin(BOOL val); static BOOL GetPasteAsAdmin(); diff --git a/src/QListCtrl.cpp b/src/QListCtrl.cpp index 1740c127..4486dd57 100644 --- a/src/QListCtrl.cpp +++ b/src/QListCtrl.cpp @@ -1410,7 +1410,16 @@ BOOL CQListCtrl::PreTranslateMessage(MSG* pMsg) return TRUE; break; // end case WM_KEYDOWN case WM_MOUSEWHEEL: - break; + // Will be handled by default, but ensure scrollbar updates after + { + BOOL result = CListCtrl::PreTranslateMessage(pMsg); + CWnd* pParent = GetParent(); + if (pParent && pParent->GetSafeHwnd()) + { + pParent->PostMessage(NM_UPDATE_SCROLLBAR, 0, 0); + } + return result; + } case WM_VSCROLL: ASSERT(FALSE); @@ -1925,6 +1934,13 @@ void CQListCtrl::OnSelectionChange(NMHDR* pNMHDR, LRESULT* pResult) if ((pnmv->uNewState == 3) || (pnmv->uNewState == 1)) { + // Notify parent to update modern scrollbar when selection changes (keyboard navigation) + CWnd* pParent = GetParent(); + if (pParent && pParent->GetSafeHwnd()) + { + pParent->PostMessage(NM_UPDATE_SCROLLBAR, 0, 0); + } + if (VALID_TOOLTIP && ::IsWindowVisible(m_pToolTip->m_hWnd)) { @@ -2045,6 +2061,13 @@ void CQListCtrl::SetLogFont(LOGFONT& font) void CQListCtrl::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) { CListCtrl::OnVScroll(nSBCode, nPos, pScrollBar); + + // Notify parent to update modern scrollbar + CWnd* pParent = GetParent(); + if (pParent && pParent->GetSafeHwnd()) + { + pParent->PostMessage(NM_UPDATE_SCROLLBAR, 0, 0); + } } BOOL CQListCtrl::OnChildNotify(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pLResult) @@ -2079,27 +2102,37 @@ void CQListCtrl::OnMouseMove(UINT nFlags, CPoint point) { if (CGetSetOptions::m_showScrollBar == FALSE) { - CPoint cursorPos; - GetCursorPos(&cursorPos); - CRect crWindow; this->GetWindowRect(&crWindow); ScreenToClient(&crWindow); - crWindow.right -= m_windowDpi->Scale(::GetSystemMetrics(SM_CXVSCROLL)); - crWindow.bottom -= m_windowDpi->Scale(::GetSystemMetrics(SM_CXHSCROLL)); + // Don't subtract scrollbar size - detect in the full window area + // This prevents flickering when scrollbar appears/disappears if (MouseInScrollBarArea(crWindow, point)) { - if ((GetTickCount() - m_mouseOverScrollAreaStart) > 500) + // Show scrollbar immediately when mouse enters scrollbar area + if (m_mouseOverScrollAreaStart == 0) { - SetTimer(TIMER_SHOW_SCROLL, 500, NULL); - m_mouseOverScrollAreaStart = GetTickCount(); + + // For modern scrollbar, notify parent + if (CGetSetOptions::m_useModernScrollBar) + { + GetParent()->PostMessage(NM_UPDATE_SCROLLBAR, 0, 0); + } + else + { + // For native scrollbar, show immediately and start hide timer + m_timerToHideScrollAreaSet = true; + GetParent()->SendMessage(NM_SHOW_HIDE_SCROLLBARS, 1, 0); + SetTimer(TIMER_HIDE_SCROL, 1000, NULL); + } } } else { + m_mouseOverScrollAreaStart = 0; if (m_timerToHideScrollAreaSet) { StopHideScrollBarTimer(); @@ -2113,11 +2146,16 @@ void CQListCtrl::OnMouseMove(UINT nFlags, CPoint point) bool CQListCtrl::MouseInScrollBarArea(CRect crWindow, CPoint point) { + int scrollBarWidth = m_windowDpi->Scale(::GetSystemMetrics(SM_CXVSCROLL)); + int scrollBarHeight = m_windowDpi->Scale(::GetSystemMetrics(SM_CYHSCROLL)); + int extraMargin = m_windowDpi->Scale(6); // Small extra margin for easier detection + CRect crRight(crWindow); CRect crBottom(crWindow); - crRight.left = crRight.right - m_windowDpi->Scale(::GetSystemMetrics(SM_CXVSCROLL)); - crBottom.top = crBottom.bottom - m_windowDpi->Scale(::GetSystemMetrics(SM_CYHSCROLL)); + // Detect from the right edge of the window (includes scrollbar area when visible) + crRight.left = crRight.right - scrollBarWidth - extraMargin; + crBottom.top = crBottom.bottom - scrollBarHeight - extraMargin; /*CString cs; cs.Format(_T("point.x: %d, Width: %d, Height: %d\n"), point.x, crWindow.Width(), crWindow.Height()); @@ -2285,5 +2323,12 @@ void CQListCtrl::OnMouseHWheel(UINT nFlags, short zDelta, CPoint pt) this->SendMessage(WM_HSCROLL, SB_LINELEFT, NULL); } + // Notify parent to update modern scrollbar + CWnd* pParent = GetParent(); + if (pParent && pParent->GetSafeHwnd()) + { + pParent->PostMessage(NM_UPDATE_SCROLLBAR, 0, 0); + } + //CListCtrl::OnMouseHWheel(nFlags, zDelta, pt); } \ No newline at end of file diff --git a/src/QListCtrl.h b/src/QListCtrl.h index 5242ddae..01ef076d 100644 --- a/src/QListCtrl.h +++ b/src/QListCtrl.h @@ -44,6 +44,7 @@ #define NM_MOVE_TO_GROUP WM_USER+0x128 #define NM_FOCUS_ON_SEARCH WM_USER+0x129 #define NM_COPY_CLIP WM_USER+0x130 +#define NM_UPDATE_SCROLLBAR WM_USER+0x131 diff --git a/src/QPasteWnd.cpp b/src/QPasteWnd.cpp index 1d848fbd..7998974f 100644 --- a/src/QPasteWnd.cpp +++ b/src/QPasteWnd.cpp @@ -223,6 +223,7 @@ BEGIN_MESSAGE_MAP(CQPasteWnd, CWndEx) ON_COMMAND_RANGE(3000, 4000, OnAddinSelect) ON_MESSAGE(NM_ALL_SELECTED, OnSelectAll) ON_MESSAGE(NM_SHOW_HIDE_SCROLLBARS, OnShowHideScrollBar) + ON_MESSAGE(NM_UPDATE_SCROLLBAR, OnUpdateScrollBar) ON_MESSAGE(NM_CANCEL_SEARCH, OnCancelFilter) ON_MESSAGE(NM_POST_OPTIONS_WINDOW, OnPostOptions) ON_COMMAND(ID_MENU_SEARCHDESCRIPTION, OnMenuSearchDescription) @@ -418,7 +419,7 @@ int CQPasteWnd::OnCreate(LPCREATESTRUCT lpCreateStruct) //m_search.SetButtonArea(rcCloseArea); // Create the header control - if (!m_lstHeader.Create(WS_TABSTOP | WS_CHILD | WS_VISIBLE | LVS_NOCOLUMNHEADER | LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | LVS_OWNERDRAWFIXED, CRect(0, 0, 0, 0), this, ID_LIST_HEADER)) + if (!m_lstHeader.Create(WS_TABSTOP | WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | LVS_NOCOLUMNHEADER | LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | LVS_OWNERDRAWFIXED, CRect(0, 0, 0, 0), this, ID_LIST_HEADER)) { ASSERT(FALSE); return -1; @@ -426,6 +427,24 @@ int CQPasteWnd::OnCreate(LPCREATESTRUCT lpCreateStruct) m_lstHeader.SetDpiInfo(&m_DittoWindow.m_dpi); m_lstHeader.ShowWindow(SW_SHOW); + // Create modern scrollbar overlay (vertical) + m_modernScrollBar.Create(this, &m_lstHeader, ScrollBarOrientation::Vertical); + m_modernScrollBar.SetDPI(&m_DittoWindow.m_dpi); + m_modernScrollBar.SetColors( + CGetSetOptions::m_Theme.ScrollBarTrack(), + CGetSetOptions::m_Theme.ScrollBarThumb(), + CGetSetOptions::m_Theme.ScrollBarThumbHover() + ); + + // Create modern scrollbar overlay (horizontal) + m_modernScrollBarHorz.Create(this, &m_lstHeader, ScrollBarOrientation::Horizontal); + m_modernScrollBarHorz.SetDPI(&m_DittoWindow.m_dpi); + m_modernScrollBarHorz.SetColors( + CGetSetOptions::m_Theme.ScrollBarTrack(), + CGetSetOptions::m_Theme.ScrollBarThumb(), + CGetSetOptions::m_Theme.ScrollBarThumbHover() + ); + ((CWnd*)&m_GroupTree)->CreateEx(NULL, _T("SysTreeView32"), NULL, TVS_HASLINES | TVS_LINESATROOT | TVS_HASBUTTONS, CRect(0, 0, 100, 100), this, 0); m_GroupTree.ModifyStyle(WS_CAPTION | WS_TABSTOP, 0); @@ -675,8 +694,11 @@ void CQPasteWnd::MoveControls() int extraSize = 0; - if (m_showScrollBars == false && - CGetSetOptions::m_showScrollBar == false) + // Hide native scrollbar if using modern scrollbar OR if scrollbar is set to not always show + bool hideNativeScrollbar = CGetSetOptions::m_useModernScrollBar || + (m_showScrollBars == false && CGetSetOptions::m_showScrollBar == false); + + if (hideNativeScrollbar) { extraSize = m_DittoWindow.m_dpi.Scale(::GetSystemMetrics(SM_CXVSCROLL)); @@ -684,10 +706,15 @@ void CQPasteWnd::MoveControls() CRect r; m_lstHeader.GetWindowRect(&r); - rgnRect.CreateRectRgn(0, 0, cx, (cy - listBoxBottomOffset - topOfListBox) + 1); + rgnRect.CreateRectRgn(0, 0, cx, (cy - listBoxBottomOffset - topOfListBox) ); m_lstHeader.SetWindowRgn(rgnRect, TRUE); } + else + { + // Clear region to show native scrollbar + m_lstHeader.SetWindowRgn(NULL, TRUE); + } if (m_noSearchResults && @@ -695,6 +722,8 @@ void CQPasteWnd::MoveControls() { m_lstHeader.ShowWindow(SW_HIDE); m_noSearchResultsStatic.ShowWindow(SW_SHOW); + m_modernScrollBar.ShowWindow(SW_HIDE); + m_modernScrollBarHorz.ShowWindow(SW_HIDE); auto border = m_DittoWindow.m_dpi.Scale(10); m_noSearchResultsStatic.MoveWindow(border, topOfListBox + border, cx - border, cy - listBoxBottomOffset - topOfListBox + 1 - border); @@ -705,6 +734,20 @@ void CQPasteWnd::MoveControls() m_noSearchResultsStatic.ShowWindow(SW_HIDE); m_lstHeader.MoveWindow(0, topOfListBox, cx + extraSize, cy - listBoxBottomOffset - topOfListBox + extraSize + 1); + + // Update modern scrollbar position and visibility (only if enabled) + if (CGetSetOptions::m_useModernScrollBar) + { + m_modernScrollBar.UpdateScrollBar(); + m_modernScrollBar.Show(false); + m_modernScrollBarHorz.UpdateScrollBar(); + m_modernScrollBarHorz.Show(false); + } + else + { + m_modernScrollBar.Hide(false); + m_modernScrollBarHorz.Hide(false); + } } m_search.MoveWindow(m_DittoWindow.m_dpi.Scale(34), cy - m_DittoWindow.m_dpi.Scale(searchRowStart - 5), cx - m_DittoWindow.m_dpi.Scale(70), m_DittoWindow.m_dpi.Scale(25)); @@ -6472,6 +6515,17 @@ LRESULT CQPasteWnd::OnShowHideScrollBar(WPARAM wParam, LPARAM lParam) return 1; } +LRESULT CQPasteWnd::OnUpdateScrollBar(WPARAM wParam, LPARAM lParam) +{ + // Update modern scrollbar position when list scrolls (only if enabled) + if (CGetSetOptions::m_useModernScrollBar) + { + m_modernScrollBar.Show(false); + m_modernScrollBarHorz.Show(false); + } + return 0; +} + //HBRUSH CQPasteWnd::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) //{ // // Call the base class implementation first! Otherwise, it may @@ -7741,12 +7795,29 @@ bool CQPasteWnd::DoActionGmail() return true; } +void CQPasteWnd::RefreshScrollBarColors() +{ + m_modernScrollBar.SetColors( + CGetSetOptions::m_Theme.ScrollBarTrack(), + CGetSetOptions::m_Theme.ScrollBarThumb(), + CGetSetOptions::m_Theme.ScrollBarThumbHover() + ); + m_modernScrollBarHorz.SetColors( + CGetSetOptions::m_Theme.ScrollBarTrack(), + CGetSetOptions::m_Theme.ScrollBarThumb(), + CGetSetOptions::m_Theme.ScrollBarThumbHover() + ); +} + void CQPasteWnd::RefreshThemeColors() { // Refresh caption bar colors SetCaptionColorActive(CGetSetOptions::m_bShowPersistent, theApp.GetConnectCV()); SetCaptionOn(CGetSetOptions::GetCaptionPos(), true, CGetSetOptions::m_Theme.GetCaptionSize(), CGetSetOptions::m_Theme.GetCaptionFontSize()); + // Refresh scrollbar colors + RefreshScrollBarColors(); + // Force repaint of the entire window including non-client area SetWindowPos(NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); RedrawWindow(NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ALLCHILDREN | RDW_FRAME); diff --git a/src/QPasteWnd.h b/src/QPasteWnd.h index 4ae3e478..667bd29c 100644 --- a/src/QPasteWnd.h +++ b/src/QPasteWnd.h @@ -20,6 +20,7 @@ #include "SymbolEdit.h" #include "Popup.h" #include "CustomFriendsHelper.h" +#include "ModernScrollBar.h" class CMainTable { @@ -165,6 +166,8 @@ class CQPasteWnd: public CWndEx CAccels m_toolTipActions; CAccels m_modifierKeyActions; bool m_showScrollBars; + CModernScrollBar m_modernScrollBar; // Vertical scrollbar + CModernScrollBar m_modernScrollBarHorz; // Horizontal scrollbar int m_leftSelectedCompareId; INT64 m_extraDataCounter; CPopup m_popupMsg; @@ -310,6 +313,11 @@ class CQPasteWnd: public CWndEx bool DoActionEmailTo(); bool DoActionGmail(); bool DoActionEmailToAttachExport(); + + // Refresh scrollbar colors from current theme + void RefreshScrollBarColors(); + // Refresh all theme colors (caption, scrollbars, etc.) + void RefreshThemeColors(); bool DoActionEmailToAttachContent(); bool DoActionSlugify(); bool DoCopySelection(); @@ -322,8 +330,6 @@ class CQPasteWnd: public CWndEx bool OnGlobalHotkyes(); void UpdateMenuShortCut(CCmdUI *pCmdUI, DWORD action); - // Refresh all theme colors (caption, scrollbars, etc.) - void RefreshThemeColors(); bool ShowProperties(int id, int row); bool DeleteClips(CClipIDs &IDs, ARRAY &Indexs); @@ -466,6 +472,7 @@ class CQPasteWnd: public CWndEx afx_msg void OnChaiScriptPaste(UINT idIn); afx_msg LRESULT OnSelectAll(WPARAM wParam, LPARAM lParam); afx_msg LRESULT OnShowHideScrollBar(WPARAM wParam, LPARAM lParam); + afx_msg LRESULT OnUpdateScrollBar(WPARAM wParam, LPARAM lParam); afx_msg void OnMenuSearchDescription(); afx_msg void OnMenuSearchFullText(); afx_msg void OnMenuSearchQuickPaste(); diff --git a/src/QuickPaste.cpp b/src/QuickPaste.cpp index 0b8294c3..778ae93a 100644 --- a/src/QuickPaste.cpp +++ b/src/QuickPaste.cpp @@ -292,6 +292,10 @@ void CQuickPaste::ShowQPasteWnd(CWnd *pParent, bool bAtPrevPos, bool bFromKeyboa { m_pwndPaste->ShowQPasteWindow(bReFillList); } + + // Refresh scrollbar colors to match current theme + m_pwndPaste->RefreshScrollBarColors(); + m_pwndPaste->SetForegroundWindow(); Log(StrF(_T("END of ShowQPasteWnd, AtPrevPos: %d, FromKeyboard: %d, RefillList: %d, Position, %d %d %d %d"), bAtPrevPos, bFromKeyboard, bReFillList, crRect.left, crRect.top, crRect.right, crRect.bottom)); diff --git a/src/Theme.cpp b/src/Theme.cpp index c875cd7d..d263835d 100644 --- a/src/Theme.cpp +++ b/src/Theme.cpp @@ -62,6 +62,11 @@ void CTheme::LoadDefaults() m_descriptionWindowText = RGB(0, 0, 0); + // Modern scrollbar defaults - rounded look + m_scrollBarThumb = RGB(180, 180, 180); + m_scrollBarThumbHover = RGB(140, 140, 140); + m_scrollBarTrack = RGB(240, 240, 240); + m_captionSize = 25; m_captionFontSize = 19; } @@ -181,6 +186,11 @@ bool CTheme::Load(CString csTheme, bool bHeaderOnly, bool bCheckLastWriteTime) LoadColor(ItemHeader, "DescriptionWindowBG", m_descriptionWindowBG); LoadColor(ItemHeader, "DescriptionWindowText", m_descriptionWindowText); + // Modern scrollbar colors + LoadColor(ItemHeader, "ScrollBarThumb", m_scrollBarThumb); + LoadColor(ItemHeader, "ScrollBarThumbHover", m_scrollBarThumbHover); + LoadColor(ItemHeader, "ScrollBarTrack", m_scrollBarTrack); + if (followWindows10Theme) { LoadWindowsAccentColor(); diff --git a/src/Theme.h b/src/Theme.h index 947fb02f..ba71e825 100644 --- a/src/Theme.h +++ b/src/Theme.h @@ -48,6 +48,11 @@ class CTheme COLORREF DescriptionWindowBG() const { return m_descriptionWindowBG; } COLORREF DescriptionWindowText() const { return m_descriptionWindowText; } + // Modern scrollbar colors + COLORREF ScrollBarThumb() const { return m_scrollBarThumb; } + COLORREF ScrollBarThumbHover() const { return m_scrollBarThumbHover; } + COLORREF ScrollBarTrack() const { return m_scrollBarTrack; } + CString Notes() const { return m_csNotes; } CString Author() const { return m_csAuthor; } long FileVersion() const { return m_lFileVersion; } @@ -95,6 +100,11 @@ class CTheme COLORREF m_descriptionWindowBG; COLORREF m_descriptionWindowText; + // Modern scrollbar colors + COLORREF m_scrollBarThumb; + COLORREF m_scrollBarThumbHover; + COLORREF m_scrollBarTrack; + int m_captionSize; int m_captionFontSize;