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;