diff --git a/CP_Main.rc b/CP_Main.rc
index 74a871e6..8f6d1288 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
@@ -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/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/resource.h b/resource.h
index 2fb2e2ce..277e20db 100644
--- a/resource.h
+++ b/resource.h
@@ -648,6 +648,8 @@
#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_BUTTON_PREVIEW_THEME 2175
#define IDC_EDIT_ADV_FILTER 5001
#define IDC_NEXT_MATCH_BUTTON 5002
#define ID_FIRST_OPTION 32771
@@ -877,7 +879,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..8e8232e0 100644
--- a/src/AdvGeneral.cpp
+++ b/src/AdvGeneral.cpp
@@ -159,6 +159,9 @@ 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
+#define SETTING_HIDE_TASKBAR_ICON_ON_CLOSE 109
+#define SETTING_USE_MODERN_SCROLLBAR 110
BOOL CAdvGeneral::OnInitDialog()
{
@@ -198,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));
@@ -223,6 +227,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);
@@ -299,6 +305,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);
@@ -571,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)
{
@@ -961,6 +975,20 @@ 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;
+ 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/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 7df83185..e25bcbda 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;
@@ -71,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 = "";
@@ -90,6 +92,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 +258,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();
@@ -284,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();
@@ -294,6 +299,7 @@ void CGetSetOptions::LoadSettings()
m_maintainSearchView = GetMaintainSearchView();
m_clipEditSaveDelayAfterLoadSeconds = GetClipEditSaveDelayAfterLoadSeconds();
m_clipEditSaveDelayAfterSaveSeconds = GetClipEditSaveDelayAfterSaveSeconds();
+ m_bDoNotHideOnDeactivate = GetDoNotHideOnDeactivate();
GetExtraNetworkPassword(true);
@@ -1261,6 +1267,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);
@@ -2285,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);
@@ -2361,6 +2388,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);
@@ -2486,12 +2523,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 +3261,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..0060556d 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();
@@ -460,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();
@@ -481,6 +489,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();
@@ -719,6 +730,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/OptionsGeneral.cpp b/src/OptionsGeneral.cpp
index c9239e57..f34b4b23 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,58 @@ void COptionsGeneral::OnClickedExpireEntries()
m_eExpireAfter.EnableWindow(FALSE);
}
}
+
+void COptionsGeneral::OnBnClickedButtonPreviewTheme()
+{
+ if (theApp.m_pMainFrame != NULL)
+ {
+ CQPasteWnd* pPasteWnd = theApp.m_pMainFrame->m_quickPaste.m_pwndPaste;
+
+ // 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();
+ }
+
+ // Reuse the dropdown-change logic to load/apply the selected theme
+ OnCbnSelchangeComboTheme();
+ }
+}
+
+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->RefreshThemeColors();
+ }
+ }
+}
+
+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;
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 8c400587..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));
@@ -756,7 +799,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);
}
@@ -851,7 +894,7 @@ BOOL CQPasteWnd::HideQPasteWindow(bool releaseFocus, BOOL clearSearchData)
//Save the size
SaveWindowSize();
- if (CGetSetOptions::GetShowInTaskBar())
+ if (CGetSetOptions::GetShowInTaskBar() && !CGetSetOptions::GetHideTaskbarIconOnClose())
{
ShowWindow(SW_MINIMIZE);
}
@@ -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,6 +7795,34 @@ 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);
+}
+
bool CQPasteWnd::DoActionEmailToAttachExport()
{
CWaitCursor wait;
diff --git a/src/QPasteWnd.h b/src/QPasteWnd.h
index 5c67d6ee..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();
@@ -464,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;