// QListCtrl.cpp : implementation file // #include "stdafx.h" #include "CP_Main.h" #include "QListCtrl.h" #include "ProcessPaste.h" #include "BitmapHelper.h" #include "MainTableFunctions.h" #include "DittoCopyBuffer.h" #include #include "DrawHTML.h" #include "..\Shared\TextConvert.h" #include #include #include #include // For swscanf #include // For std::round #include #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif #define ROW_BOTTOM_BORDER 4 #define ROW_LEFT_BORDER 3 #define COLOR_SHADOW RGB(245, 245, 245) #define DUMMY_COL_WIDTH 2 #define TIMER_SHOW_PROPERTIES 1 #define TIMER_HIDE_SCROL 2 #define TIMER_SHOW_SCROLL 3 #define VALID_TOOLTIP (m_pToolTip && ::IsWindow(m_pToolTip->m_hWnd)) #ifndef M_PI #define M_PI 3.14159265358979323846 #endif // Static map to hold W3C color names and their RGB values static std::map g_colorNameMap; static CMutex g_colorNameMapMutex; // Initializes the color map on first use void InitializeColorNameMap() { CSingleLock lock(&g_colorNameMapMutex, TRUE); if (g_colorNameMap.empty()) { // Populate the map with W3C named colors // A comprehensive list of 148 colors g_colorNameMap[_T("black")] = RGB(0, 0, 0); g_colorNameMap[_T("silver")] = RGB(192, 192, 192); g_colorNameMap[_T("gray")] = RGB(128, 128, 128); g_colorNameMap[_T("white")] = RGB(255, 255, 255); g_colorNameMap[_T("maroon")] = RGB(128, 0, 0); g_colorNameMap[_T("red")] = RGB(255, 0, 0); g_colorNameMap[_T("purple")] = RGB(128, 0, 128); g_colorNameMap[_T("fuchsia")] = RGB(255, 0, 255); g_colorNameMap[_T("green")] = RGB(0, 128, 0); g_colorNameMap[_T("lime")] = RGB(0, 255, 0); g_colorNameMap[_T("olive")] = RGB(128, 128, 0); g_colorNameMap[_T("yellow")] = RGB(255, 255, 0); g_colorNameMap[_T("navy")] = RGB(0, 0, 128); g_colorNameMap[_T("blue")] = RGB(0, 0, 255); g_colorNameMap[_T("teal")] = RGB(0, 128, 128); g_colorNameMap[_T("aqua")] = RGB(0, 255, 255); g_colorNameMap[_T("aliceblue")] = RGB(240, 248, 255); g_colorNameMap[_T("antiquewhite")] = RGB(250, 235, 215); g_colorNameMap[_T("aquamarine")] = RGB(127, 255, 212); g_colorNameMap[_T("azure")] = RGB(240, 255, 255); g_colorNameMap[_T("beige")] = RGB(245, 245, 220); g_colorNameMap[_T("bisque")] = RGB(255, 228, 196); g_colorNameMap[_T("blanchedalmond")] = RGB(255, 235, 205); g_colorNameMap[_T("blueviolet")] = RGB(138, 43, 226); g_colorNameMap[_T("brown")] = RGB(165, 42, 42); g_colorNameMap[_T("burlywood")] = RGB(222, 184, 135); g_colorNameMap[_T("cadetblue")] = RGB(95, 158, 160); g_colorNameMap[_T("chartreuse")] = RGB(127, 255, 0); g_colorNameMap[_T("chocolate")] = RGB(210, 105, 30); g_colorNameMap[_T("coral")] = RGB(255, 127, 80); g_colorNameMap[_T("cornflowerblue")] = RGB(100, 149, 237); g_colorNameMap[_T("cornsilk")] = RGB(255, 248, 220); g_colorNameMap[_T("crimson")] = RGB(220, 20, 60); g_colorNameMap[_T("cyan")] = RGB(0, 255, 255); g_colorNameMap[_T("darkblue")] = RGB(0, 0, 139); g_colorNameMap[_T("darkcyan")] = RGB(0, 139, 139); g_colorNameMap[_T("darkgoldenrod")] = RGB(184, 134, 11); g_colorNameMap[_T("darkgray")] = RGB(169, 169, 169); g_colorNameMap[_T("darkgreen")] = RGB(0, 100, 0); g_colorNameMap[_T("darkkhaki")] = RGB(189, 183, 107); g_colorNameMap[_T("darkmagenta")] = RGB(139, 0, 139); g_colorNameMap[_T("darkolivegreen")] = RGB(85, 107, 47); g_colorNameMap[_T("darkorange")] = RGB(255, 140, 0); g_colorNameMap[_T("darkorchid")] = RGB(153, 50, 204); g_colorNameMap[_T("darkred")] = RGB(139, 0, 0); g_colorNameMap[_T("darksalmon")] = RGB(233, 150, 122); g_colorNameMap[_T("darkseagreen")] = RGB(143, 188, 143); g_colorNameMap[_T("darkslateblue")] = RGB(72, 61, 139); g_colorNameMap[_T("darkslategray")] = RGB(47, 79, 79); g_colorNameMap[_T("darkturquoise")] = RGB(0, 206, 209); g_colorNameMap[_T("darkviolet")] = RGB(148, 0, 211); g_colorNameMap[_T("deeppink")] = RGB(255, 20, 147); g_colorNameMap[_T("deepskyblue")] = RGB(0, 191, 255); g_colorNameMap[_T("dimgray")] = RGB(105, 105, 105); g_colorNameMap[_T("dodgerblue")] = RGB(30, 144, 255); g_colorNameMap[_T("firebrick")] = RGB(178, 34, 34); g_colorNameMap[_T("floralwhite")] = RGB(255, 250, 240); g_colorNameMap[_T("forestgreen")] = RGB(34, 139, 34); g_colorNameMap[_T("gainsboro")] = RGB(220, 220, 220); g_colorNameMap[_T("ghostwhite")] = RGB(248, 248, 255); g_colorNameMap[_T("gold")] = RGB(255, 215, 0); g_colorNameMap[_T("goldenrod")] = RGB(218, 165, 32); g_colorNameMap[_T("greenyellow")] = RGB(173, 255, 47); g_colorNameMap[_T("honeydew")] = RGB(240, 255, 240); g_colorNameMap[_T("hotpink")] = RGB(255, 105, 180); g_colorNameMap[_T("indianred")] = RGB(205, 92, 92); g_colorNameMap[_T("indigo")] = RGB(75, 0, 130); g_colorNameMap[_T("ivory")] = RGB(255, 255, 240); g_colorNameMap[_T("khaki")] = RGB(240, 230, 140); g_colorNameMap[_T("lavender")] = RGB(230, 230, 250); g_colorNameMap[_T("lavenderblush")] = RGB(255, 240, 245); g_colorNameMap[_T("lawngreen")] = RGB(124, 252, 0); g_colorNameMap[_T("lemonchiffon")] = RGB(255, 250, 205); g_colorNameMap[_T("lightblue")] = RGB(173, 216, 230); g_colorNameMap[_T("lightcoral")] = RGB(240, 128, 128); g_colorNameMap[_T("lightcyan")] = RGB(224, 255, 255); g_colorNameMap[_T("lightgoldenrodyellow")] = RGB(250, 250, 210); g_colorNameMap[_T("lightgray")] = RGB(211, 211, 211); g_colorNameMap[_T("lightgreen")] = RGB(144, 238, 144); g_colorNameMap[_T("lightpink")] = RGB(255, 182, 193); g_colorNameMap[_T("lightsalmon")] = RGB(255, 160, 122); g_colorNameMap[_T("lightseagreen")] = RGB(32, 178, 170); g_colorNameMap[_T("lightskyblue")] = RGB(135, 206, 250); g_colorNameMap[_T("lightslategray")] = RGB(119, 136, 153); g_colorNameMap[_T("lightsteelblue")] = RGB(176, 196, 222); g_colorNameMap[_T("lightyellow")] = RGB(255, 255, 224); g_colorNameMap[_T("limegreen")] = RGB(50, 205, 50); g_colorNameMap[_T("linen")] = RGB(250, 240, 230); g_colorNameMap[_T("magenta")] = RGB(255, 0, 255); g_colorNameMap[_T("mediumaquamarine")] = RGB(102, 205, 170); g_colorNameMap[_T("mediumblue")] = RGB(0, 0, 205); g_colorNameMap[_T("mediumorchid")] = RGB(186, 85, 211); g_colorNameMap[_T("mediumpurple")] = RGB(147, 112, 219); g_colorNameMap[_T("mediumseagreen")] = RGB(60, 179, 113); g_colorNameMap[_T("mediumslateblue")] = RGB(123, 104, 238); g_colorNameMap[_T("mediumspringgreen")] = RGB(0, 250, 154); g_colorNameMap[_T("mediumturquoise")] = RGB(72, 209, 204); g_colorNameMap[_T("mediumvioletred")] = RGB(199, 21, 133); g_colorNameMap[_T("midnightblue")] = RGB(25, 25, 112); g_colorNameMap[_T("mintcream")] = RGB(245, 255, 250); g_colorNameMap[_T("mistyrose")] = RGB(255, 228, 225); g_colorNameMap[_T("moccasin")] = RGB(255, 228, 181); g_colorNameMap[_T("navajowhite")] = RGB(255, 222, 173); g_colorNameMap[_T("oldlace")] = RGB(253, 245, 230); g_colorNameMap[_T("olivedrab")] = RGB(107, 142, 35); g_colorNameMap[_T("orange")] = RGB(255, 165, 0); g_colorNameMap[_T("orangered")] = RGB(255, 69, 0); g_colorNameMap[_T("orchid")] = RGB(218, 112, 214); g_colorNameMap[_T("palegoldenrod")] = RGB(238, 232, 170); g_colorNameMap[_T("palegreen")] = RGB(152, 251, 152); g_colorNameMap[_T("paleturquoise")] = RGB(175, 238, 238); g_colorNameMap[_T("palevioletred")] = RGB(219, 112, 147); g_colorNameMap[_T("papayawhip")] = RGB(255, 239, 213); g_colorNameMap[_T("peachpuff")] = RGB(255, 218, 185); g_colorNameMap[_T("peru")] = RGB(205, 133, 63); g_colorNameMap[_T("pink")] = RGB(255, 192, 203); g_colorNameMap[_T("plum")] = RGB(221, 160, 221); g_colorNameMap[_T("powderblue")] = RGB(176, 224, 230); g_colorNameMap[_T("rebeccapurple")] = RGB(102, 51, 153); g_colorNameMap[_T("rosybrown")] = RGB(188, 143, 143); g_colorNameMap[_T("royalblue")] = RGB(65, 105, 225); g_colorNameMap[_T("saddlebrown")] = RGB(139, 69, 19); g_colorNameMap[_T("salmon")] = RGB(250, 128, 114); g_colorNameMap[_T("sandybrown")] = RGB(244, 164, 96); g_colorNameMap[_T("seagreen")] = RGB(46, 139, 87); g_colorNameMap[_T("seashell")] = RGB(255, 245, 238); g_colorNameMap[_T("sienna")] = RGB(160, 82, 45); g_colorNameMap[_T("skyblue")] = RGB(135, 206, 235); g_colorNameMap[_T("slateblue")] = RGB(106, 90, 205); g_colorNameMap[_T("slategray")] = RGB(112, 128, 144); g_colorNameMap[_T("snow")] = RGB(255, 250, 250); g_colorNameMap[_T("springgreen")] = RGB(0, 255, 127); g_colorNameMap[_T("steelblue")] = RGB(70, 130, 180); g_colorNameMap[_T("tan")] = RGB(210, 180, 140); g_colorNameMap[_T("thistle")] = RGB(216, 191, 216); g_colorNameMap[_T("tomato")] = RGB(255, 99, 71); g_colorNameMap[_T("turquoise")] = RGB(64, 224, 208); g_colorNameMap[_T("violet")] = RGB(238, 130, 238); g_colorNameMap[_T("wheat")] = RGB(245, 222, 179); g_colorNameMap[_T("whitesmoke")] = RGB(245, 245, 245); g_colorNameMap[_T("yellowgreen")] = RGB(154, 205, 50); } } ///////////////////////////////////////////////////////////////////////////// // CQListCtrl CQListCtrl::CQListCtrl() { m_pchTip = NULL; m_pwchTip = NULL; m_linesPerRow = 1; m_windowDpi = NULL; m_SmallFont = NULL; m_pToolTip = NULL; m_pFormatter = NULL; m_allSelected = false; m_rowHeight = 50; m_mouseOverScrollAreaStart = 0; m_showIfClipWasPasted = TRUE; m_bShowTextForFirstTenHotKeys = true; m_pToolTipActions = NULL; } CQListCtrl::~CQListCtrl() { if (m_pchTip != NULL) delete m_pchTip; if (m_pwchTip != NULL) delete m_pwchTip; if (m_SmallFont) ::DeleteObject(m_SmallFont); m_Font.DeleteObject(); m_boldFont.DeleteObject(); if (m_pFormatter) { delete m_pFormatter; m_pFormatter = NULL; } } // returns the position 1-10 if the index is in the FirstTen block else -1 int CQListCtrl::GetFirstTenNum(int index) { // set firstTenNum to the first ten number (1-10) corresponding to the given index int firstTenNum = -1; // -1 means that nItem is not in the FirstTen block. int count = GetItemCount(); if (0 <= index && index <= 9) { firstTenNum = index + CGetSetOptions::m_firstTenHotKeysStart; firstTenNum = firstTenNum % 10; } return firstTenNum; } BEGIN_MESSAGE_MAP(CQListCtrl, CListCtrl) //{{AFX_MSG_MAP(CQListCtrl) ON_NOTIFY_REFLECT(LVN_KEYDOWN, OnKeydown) ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomdrawList) ON_WM_MOUSEMOVE() ON_WM_SYSKEYDOWN() ON_WM_ERASEBKGND() ON_WM_CREATE() ON_WM_HSCROLL() ON_WM_TIMER() ON_NOTIFY_REFLECT(LVN_ITEMCHANGED, OnSelectionChange) ON_WM_VSCROLL() ON_WM_WINDOWPOSCHANGED() //}}AFX_MSG_MAP ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipText) ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipText) ON_WM_KILLFOCUS() ON_WM_MEASUREITEM_REFLECT() ON_WM_MOUSEHWHEEL() END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CQListCtrl message handlers void CQListCtrl::OnKeydown(NMHDR* pNMHDR, LRESULT* pResult) { LV_KEYDOWN* pLVKeyDown = (LV_KEYDOWN*)pNMHDR; *pResult = 0; } DROPEFFECT CQListCtrl::OnDragOver(COleDataObject* pDataObject, DWORD dwKeyState, CPoint point) { return DROPEFFECT_COPY; } void CQListCtrl::GetSelectionIndexes(ARRAY& arr) { arr.RemoveAll(); POSITION pos = GetFirstSelectedItemPosition(); while (pos) { arr.Add(GetNextSelectedItem(pos)); } } bool CQListCtrl::PutSelectedItemOnDittoCopyBuffer(long lBuffer) { bool bRet = false; ARRAY arr; GetSelectionItemData(arr); INT_PTR nCount = arr.GetSize(); if (nCount > 0 && arr[0]) { CDittoCopyBuffer::PutClipOnDittoCopyBuffer(arr[0], lBuffer); bRet = true; } return bRet; } void CQListCtrl::GetSelectionItemData(ARRAY& arr) { DWORD dwData; int i; arr.RemoveAll(); POSITION pos = GetFirstSelectedItemPosition(); while (pos) { i = GetNextSelectedItem(pos); dwData = GetItemData(i); arr.Add(dwData); } } void CQListCtrl::RemoveAllSelection() { POSITION pos = GetFirstSelectedItemPosition(); while (pos) { SetSelection(GetNextSelectedItem(pos), FALSE); } } BOOL CQListCtrl::SetSelection(int nRow, BOOL bSelect) { if (bSelect) return SetItemState(nRow, LVIS_SELECTED, LVIS_SELECTED); else return SetItemState(nRow, ~LVIS_SELECTED, LVIS_SELECTED); } BOOL CQListCtrl::SetText(int nRow, int nCol, CString cs) { return SetItemText(nRow, nCol, cs); } BOOL CQListCtrl::SetCaret(int nRow, BOOL bFocus) { if (bFocus) return SetItemState(nRow, LVIS_FOCUSED, LVIS_FOCUSED); else return SetItemState(nRow, ~LVIS_FOCUSED, LVIS_FOCUSED); } long CQListCtrl::GetCaret() { return GetNextItem(-1, LVNI_FOCUSED); } // moves the caret to the given index, selects it, and ensures it is visible. BOOL CQListCtrl::SetListPos(int index) { if (index < 0 || index >= GetItemCount()) return FALSE; RemoveAllSelection(); SetCaret(index); SetSelection(index); ListView_SetSelectionMark(m_hWnd, index); EnsureVisible(index, FALSE); return TRUE; } BOOL CQListCtrl::SetFormattedText(int nRow, int nCol, LPCTSTR lpszFormat, ...) { CString csText; va_list vlist; ASSERT(AfxIsValidString(lpszFormat)); va_start(vlist, lpszFormat); csText.FormatV(lpszFormat, vlist); va_end(vlist); return SetText(nRow, nCol, csText); } void CQListCtrl::SetNumberOfLinesPerRow(int nLines, bool force) { if (m_linesPerRow != nLines || force) { m_linesPerRow = nLines; CRect rc; GetWindowRect(&rc); WINDOWPOS wp; wp.hwnd = m_hWnd; wp.cx = rc.Width(); wp.cy = rc.Height(); wp.flags = SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER; SendMessage(WM_WINDOWPOSCHANGED, 0, (LPARAM)&wp); } } void CQListCtrl::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct) { TEXTMETRIC tm; HDC hDC = ::GetDC(NULL); CFont* pFont = GetFont(); HFONT hFontOld = (HFONT)SelectObject(hDC, pFont->GetSafeHandle()); GetTextMetrics(hDC, &tm); if (m_windowDpi != NULL) { lpMeasureItemStruct->itemHeight = ((tm.tmHeight + tm.tmExternalLeading) * m_linesPerRow) + m_windowDpi->Scale(ROW_BOTTOM_BORDER); m_rowHeight = lpMeasureItemStruct->itemHeight; } SelectObject(hDC, hFontOld); ::ReleaseDC(NULL, hDC); } void CQListCtrl::OnCustomdrawList(NMHDR* pNMHDR, LRESULT* pResult) { NMLVCUSTOMDRAW* pLVCD = reinterpret_cast(pNMHDR); *pResult = 0; // Request item-specific notifications if this is the // beginning of the paint cycle. if (CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage) { *pResult = CDRF_NOTIFYITEMDRAW; } else if (CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage) { LVITEM rItem; int nItem = static_cast(pLVCD->nmcd.dwItemSpec); CDC* pDC = CDC::FromHandle(pLVCD->nmcd.hdc); COLORREF crBkgnd; BOOL bListHasFocus; CRect rcItem; bListHasFocus = (GetSafeHwnd() == ::GetFocus()); // Get the image index and selected/focused state of the // item being drawn. ZeroMemory(&rItem, sizeof(LVITEM)); rItem.mask = LVIF_STATE; rItem.iItem = nItem; rItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED; GetItem(&rItem); // Get the rect that bounds the text label. GetItemRect(nItem, rcItem, LVIR_SELECTBOUNDS); COLORREF OldColor = -1; int nOldBKMode = -1; CString csText; LPTSTR lpszText = csText.GetBufferSetLength(CGetSetOptions::m_bDescTextSize); GetItemText(nItem, 0, lpszText, CGetSetOptions::m_bDescTextSize); csText.ReleaseBuffer(); // extract symbols CString strSymbols; int nSymEnd = csText.Find('|'); if (nSymEnd >= 0) { strSymbols = csText.Left(nSymEnd); csText = csText.Mid(nSymEnd + 1); } // Draw the background of the list item. Colors are selected // according to the item's state. if (rItem.state & LVIS_SELECTED) { if (bListHasFocus) { crBkgnd = CGetSetOptions::m_Theme.ListBoxSelectedBG(); OldColor = pDC->SetTextColor(CGetSetOptions::m_Theme.ListBoxSelectedText()); } else { crBkgnd = CGetSetOptions::m_Theme.ListBoxSelectedNoFocusBG(); OldColor = pDC->SetTextColor(CGetSetOptions::m_Theme.ListBoxSelectedNoFocusText()); } } else { //Shade alternating Rows if ((nItem % 2) == 0) { crBkgnd = CGetSetOptions::m_Theme.ListBoxOddRowsBG(); OldColor = pDC->SetTextColor(CGetSetOptions::m_Theme.ListBoxOddRowsText()); } else { crBkgnd = CGetSetOptions::m_Theme.ListBoxEvenRowsBG(); OldColor = pDC->SetTextColor(CGetSetOptions::m_Theme.ListBoxEvenRowsText()); } } pDC->FillSolidRect(rcItem, crBkgnd); nOldBKMode = pDC->SetBkMode(TRANSPARENT); CRect rcText = rcItem; rcText.left += m_windowDpi->Scale(ROW_LEFT_BORDER); rcText.top += m_windowDpi->Scale(1); rcText.bottom -= m_windowDpi->Scale(1); if (m_showIfClipWasPasted && strSymbols.GetLength() > 0 && strSymbols.Find(_T("")) >= 0) //clip was pasted from ditto { CRect pastedRect(rcItem); pastedRect.left++; pastedRect.right = pastedRect.left + m_windowDpi->Scale(2); pDC->FillSolidRect(pastedRect, CGetSetOptions::m_Theme.ClipPastedColor()); } // set firstTenNum to the first ten number (1-10) corresponding to // the current nItem. // -1 means that nItem is not in the FirstTen block. int firstTenNum = GetFirstTenNum(nItem); if (m_bShowTextForFirstTenHotKeys && firstTenNum >= 0) { rcText.left += m_windowDpi->Scale(12); } else { rcText.left += m_windowDpi->Scale(3); } bool drawInGroupIcon = true; // if we are inside a group, don't display the "in group" flag if (theApp.m_GroupID > 0) { int nFlag = strSymbols.Find(_T("")); if (nFlag >= 0) drawInGroupIcon = false; } DrawCopiedColorCode(csText, rcText, pDC); DrawBitMap(nItem, rcText, pDC, csText); // draw the symbol box if (strSymbols.GetLength() > 0) { if (strSymbols.Find(_T("")) >= 0) //group { m_groupFolder.Draw(pDC, *m_windowDpi, this, rcText.left, rcText.top, false, false); rcText.left += m_groupFolder.ImageWidth() + m_windowDpi->Scale(2); } if (strSymbols.Find(_T("")) >= 0) //don't auto delete { m_dontDeleteImage.Draw(pDC, *m_windowDpi, this, rcText.left, rcText.top, false, false); rcText.left += m_dontDeleteImage.ImageWidth() + m_windowDpi->Scale(2); } if (strSymbols.Find(_T("")) >= 0) // has shortcut { m_shortCutImage.Draw(pDC, *m_windowDpi, this, rcText.left, rcText.top, false, false); rcText.left += m_shortCutImage.ImageWidth() + m_windowDpi->Scale(2); } if (drawInGroupIcon && strSymbols.Find(_T("")) >= 0) // in group { m_inFolderImage.Draw(pDC, *m_windowDpi, this, rcText.left, rcText.top, false, false); rcText.left += m_inFolderImage.ImageWidth() + m_windowDpi->Scale(2); } if (strSymbols.Find(_T("")) >= 0) // has quick paste text { } if (strSymbols.Find(_T("")) >= 0) //sticky clip { m_stickyImage.Draw(pDC, *m_windowDpi, this, rcText.left, rcText.top, false, false); rcText.left += m_stickyImage.ImageWidth() + m_windowDpi->Scale(2); } } if (DrawRtfText(nItem, rcText, pDC) == FALSE) { auto highlightColor = CGetSetOptions::m_Theme.SearchTextHighlight(); //use unprintable characters so it doesn't find copied html to convert if (m_searchText.GetLength() > 0 && FindNoCaseAndInsert(csText, m_searchText, StrF(_T("\x01\x04 color='#%02x%02x%02x'\x02"), GetRValue(highlightColor), GetGValue(highlightColor), GetBValue(highlightColor)), _T("\x01\x03\x04\x02"), m_linesPerRow) > 0) { DrawHTML(pDC->m_hDC, csText, csText.GetLength(), rcText, DT_VCENTER | DT_EXPANDTABS | DT_NOPREFIX); } else { pDC->DrawText(csText, rcText, DT_VCENTER | DT_EXPANDTABS | DT_NOPREFIX); } } // Draw a focus rect around the item if necessary. //if(bListHasFocus && (rItem.state & LVIS_FOCUSED)) // pDC->DrawFocusRect(rcItem); if (m_bShowTextForFirstTenHotKeys && firstTenNum >= 0) { CString cs; if (firstTenNum == 10) cs = "0"; else cs.Format(_T("%d"), firstTenNum); CRect crClient; GetWindowRect(crClient); ScreenToClient(crClient); CRect crHotKey = rcItem; int extraFromClipWasPaste = 0; if (m_showIfClipWasPasted) extraFromClipWasPaste = 3; crHotKey.right = crHotKey.left + m_windowDpi->Scale(11); crHotKey.left += m_windowDpi->Scale(1 + extraFromClipWasPaste); crHotKey.top += m_windowDpi->Scale(1 + extraFromClipWasPaste); HFONT hOldFont = (HFONT)pDC->SelectObject(m_SmallFont); COLORREF localOldTextColor = pDC->SetTextColor(CGetSetOptions::m_Theme.ListSmallQuickPasteIndexColor()); CPen pen(PS_SOLID, 0, CGetSetOptions::m_Theme.ListSmallQuickPasteIndexColor()); CPen* pOldPen = pDC->SelectObject(&pen); pDC->DrawText(cs, crHotKey, DT_BOTTOM); pDC->MoveTo(CPoint(rcItem.left + m_windowDpi->Scale(8 + extraFromClipWasPaste), rcItem.top)); pDC->LineTo(CPoint(rcItem.left + m_windowDpi->Scale(8 + extraFromClipWasPaste), rcItem.bottom)); pDC->SelectObject(hOldFont); pDC->SetTextColor(localOldTextColor); pDC->SelectObject(pOldPen); } // restore the previous values if (OldColor > -1) pDC->SetTextColor(OldColor); if (nOldBKMode > -1) pDC->SetBkMode(nOldBKMode); *pResult = CDRF_SKIPDEFAULT; // We've painted everything. } } // Helper function implementation to check for valid hex characters bool CQListCtrl::IsHexString(const CString& str) { if (str.IsEmpty()) { return false; } for (int i = 0; i < str.GetLength(); ++i) { if (!iswxdigit(str[i])) { return false; } } return true; } // Helper function implementation for HSL to RGB conversion COLORREF CQListCtrl::HslToRgb(double h, double s, double l) { // Input h(0-360), s(0-1), l(0-1) // Output COLORREF (RGB) double r, g, b; if (s == 0) { r = g = b = l; // Achromatic (gray) } else { auto hue2rgb = [&](double p, double q, double t) { if (t < 0) t += 1; if (t > 1) t -= 1; if (t < 1.0 / 6.0) return p + (q - p) * 6 * t; if (t < 1.0 / 2.0) return q; if (t < 2.0 / 3.0) return p + (q - p) * (2.0 / 3.0 - t) * 6; return p; }; // Normalize h to 0-1 range double h_norm = h / 360.0; double q = l < 0.5 ? l * (1 + s) : l + s - l * s; double p = 2 * l - q; r = hue2rgb(p, q, h_norm + 1.0 / 3.0); g = hue2rgb(p, q, h_norm); b = hue2rgb(p, q, h_norm - 1.0 / 3.0); } // Scale RGB values to 0-255 and round int R = static_cast(std::round(r * 255)); int G = static_cast(std::round(g * 255)); int B = static_cast(std::round(b * 255)); // Clamp values to 0-255 just in case of floating point inaccuracies R = max(0, min(255, R)); G = max(0, min(255, G)); B = max(0, min(255, B)); return RGB(R, G, B); } // Helper function to parse a CSS numeric value which can be a percentage or a number static bool ParseCssValue(const CString& token, double& value) { CString localToken = token; localToken.Trim(); if (localToken.IsEmpty()) return false; bool isPercent = (localToken.Right(1) == _T('%')); if (isPercent) { localToken.TrimRight(_T('%')); } if (_stscanf(localToken, _T("%lf"), &value) == 1) { return true; } return false; } // Converts a color from OKLCH color space to sRGB. // l: 0-1, c: 0-0.4 (theoretically unbounded, but practically small), h: 0-360 static COLORREF OklchToRgb(double l, double c, double h) { // 1. Convert OKLCH to OKLAB double h_rad = h * M_PI / 180.0; double a = c * cos(h_rad); double b = c * sin(h_rad); // 2. Convert OKLAB to XYZ // First, convert to an intermediate non-linear LMS-like space double l_ = l + 0.3963377774 * a + 0.2158037573 * b; double m_ = l - 0.1055613458 * a - 0.0638541728 * b; double s_ = l - 0.0894841775 * a - 1.2914855480 * b; // Then, convert to linear LMS by undoing the cube-root non-linearity double l_linear = l_ * l_ * l_; double m_linear = m_ * m_ * m_; double s_linear = s_ * s_ * s_; // Finally, convert linear LMS to XYZ color space double x = +1.2270138511 * l_linear - 0.5577999807 * m_linear + 0.2812561490 * s_linear; double y = -0.0405801784 * l_linear + 1.1122568696 * m_linear - 0.0716766787 * s_linear; double z = -0.0763812845 * l_linear - 0.4214819784 * m_linear + 1.5861632204 * s_linear; // 3. Convert XYZ to linear sRGB double r_linear = +3.2404542 * x - 1.5371385 * y - 0.4985314 * z; double g_linear = -0.9692660 * x + 1.8760108 * y + 0.0415560 * z; double b_linear = +0.0556434 * x - 0.2040259 * y + 1.0572252 * z; // 4. Convert linear sRGB to gamma-corrected sRGB auto ToSrgb = [](double val) { if (val <= 0.0031308) { return 12.92 * val; } return 1.055 * pow(val, 1.0 / 2.4) - 0.055; }; double r_srgb = ToSrgb(r_linear); double g_srgb = ToSrgb(g_linear); double b_srgb = ToSrgb(b_linear); // 5. Scale to 0-255 and clamp (for out-of-gamut colors) int R = static_cast(std::round(r_srgb * 255.0)); int G = static_cast(std::round(g_srgb * 255.0)); int B = static_cast(std::round(b_srgb * 255.0)); R = max(0, min(255, R)); G = max(0, min(255, G)); B = max(0, min(255, B)); return RGB(R, G, B); } void CQListCtrl::DrawCheckerboard(CDC* pDC, CRect rect) { COLORREF color1 = RGB(255, 255, 255); // White COLORREF color2 = RGB(204, 204, 204); // Light grey int squareSize = m_windowDpi->Scale(4); if (squareSize <= 0) { squareSize = 4; } for (int y = rect.top; y < rect.bottom; y += squareSize) { for (int x = rect.left; x < rect.right; x += squareSize) { COLORREF color = ((( (x - rect.left) / squareSize) + ((y - rect.top) / squareSize)) % 2 == 0) ? color1 : color2; CRect square(x, y, min(x + squareSize, rect.right), min(y + squareSize, rect.bottom)); pDC->FillSolidRect(square, color); } } } void CQListCtrl::DrawCopiedColorCode(CString& csText, CRect& rcText, CDC* pDC) { if (CGetSetOptions::m_bDrawCopiedColorCode == FALSE || csText.IsEmpty()) return; // 1. Initial Cleaning and Prep CString cleanedText = csText; cleanedText.Trim(_T("ยป")); cleanedText.Trim(); cleanedText.TrimRight(_T(";")); cleanedText.Trim(); if (cleanedText.IsEmpty()) return; CString originalCleanedText = cleanedText; CString parseText = cleanedText; parseText.MakeLower(); // 2. Helper lambda to draw the color box auto DrawColorBox = [&](COLORREF color, int alpha = 255) { CRect pastedRect(rcText); int boxSize = rcText.Height(); pastedRect.right = pastedRect.left + boxSize; pastedRect.bottom = pastedRect.top + boxSize; if (alpha < 255) { DrawCheckerboard(pDC, pastedRect); // Use GDI+ for alpha blending Gdiplus::Graphics graphics(pDC->GetSafeHdc()); Gdiplus::Color gdiplusColor(alpha, GetRValue(color), GetGValue(color), GetBValue(color)); Gdiplus::SolidBrush brush(gdiplusColor); graphics.FillRectangle(&brush, Gdiplus::Rect(pastedRect.left, pastedRect.top, pastedRect.Width(), pastedRect.Height())); } else { // Opaque color is faster with FillSolidRect and doesn't need a checkerboard. pDC->FillSolidRect(pastedRect, color); } rcText.left += boxSize + m_windowDpi->Scale(ROW_LEFT_BORDER); csText = originalCleanedText; }; // 3. Check for formats with unique signatures first // Check for W3C Named Colors InitializeColorNameMap(); auto it = g_colorNameMap.find(parseText); if (it != g_colorNameMap.end()) { DrawColorBox(it->second); return; } // Check for Hex Colors with # or 0x prefix if (parseText.Left(1) == _T('#') || parseText.Left(2) == _T("0x")) { CString hexString = parseText; if (hexString.Left(1) == _T('#')) hexString.Delete(0, 1); else if (hexString.Left(2) == _T("0x")) hexString.Delete(0, 2); int len = hexString.GetLength(); if (len == 3 || len == 4) // Expand shorthand { CString expanded; for (int i = 0; i < len; ++i) { expanded += hexString[i]; expanded += hexString[i]; } hexString = expanded; } if (IsHexString(hexString) && (hexString.GetLength() == 6 || hexString.GetLength() == 8)) { unsigned int r = 0, g = 0, b = 0, a = 255; if (hexString.GetLength() == 8) { if (swscanf(hexString, _T("%2x%2x%2x%2x"), &r, &g, &b, &a) == 4) { DrawColorBox(RGB(r, g, b), a); return; } } else // length is 6 { if (swscanf(hexString, _T("%2x%2x%2x"), &r, &g, &b) == 3) { DrawColorBox(RGB(r, g, b)); // default alpha return; } } } } // Check for W3C notations: rgb(...), hsl(...), and oklch(...) if (parseText.Right(1) == _T(")")) { if (parseText.Left(3) == _T("rgb")) { CString content; int prefixLen = (parseText.Left(4) == _T("rgba")) ? 5 : 4; content = parseText.Mid(prefixLen, parseText.GetLength() - prefixLen - 1); content.Replace(_T(','), _T(' ')); content.Replace(_T('/'), _T(' ')); std::vector tokens; int curPos = 0; CString token; while (!(token = content.Tokenize(_T(" "), curPos)).IsEmpty()) { tokens.push_back(token); } if (tokens.size() >= 3) { double r_val, g_val, b_val; if (ParseCssValue(tokens[0], r_val) && ParseCssValue(tokens[1], g_val) && ParseCssValue(tokens[2], b_val)) { if (tokens[0].Find('%') != -1) { r_val = std::round(r_val * 2.55); g_val = std::round(g_val * 2.55); b_val = std::round(b_val * 2.55); } int alpha = 255; double a_val = 1.0; if (tokens.size() >= 4 && ParseCssValue(tokens[3], a_val)) { if (tokens[3].Find('%') != -1) { a_val /= 100.0; } a_val = max(0.0, min(1.0, a_val)); alpha = static_cast(std::round(a_val * 255.0)); } if (r_val >= 0 && r_val <= 255 && g_val >= 0 && g_val <= 255 && b_val >= 0 && b_val <= 255) { DrawColorBox(RGB((int)r_val, (int)g_val, (int)b_val), alpha); return; } } } } else if (parseText.Left(3) == _T("hsl")) { CString content; int prefixLen = (parseText.Left(4) == _T("hsla")) ? 5 : 4; content = parseText.Mid(prefixLen, parseText.GetLength() - prefixLen - 1); content.Replace(_T(','), _T(' ')); content.Replace(_T('/'), _T(' ')); content.Replace(_T("deg"), _T("")); std::vector tokens; int curPos = 0; CString token; while (!(token = content.Tokenize(_T(" "), curPos)).IsEmpty()) { tokens.push_back(token); } if (tokens.size() >= 3) { double h_val, s_val, l_val; if (ParseCssValue(tokens[0], h_val) && ParseCssValue(tokens[1], s_val) && ParseCssValue(tokens[2], l_val)) { int alpha = 255; double a_val = 1.0; if (tokens.size() >= 4 && ParseCssValue(tokens[3], a_val)) { if (tokens[3].Find('%') != -1) { a_val /= 100.0; } a_val = max(0.0, min(1.0, a_val)); alpha = static_cast(std::round(a_val * 255.0)); } if (s_val >= 0 && s_val <= 100 && l_val >= 0 && l_val <= 100) { h_val = fmod(h_val, 360.0); if (h_val < 0) h_val += 360.0; DrawColorBox(HslToRgb(h_val, s_val / 100.0, l_val / 100.0), alpha); return; } } } } else if (parseText.Left(5) == _T("oklch")) { CString content; content = parseText.Mid(6, parseText.GetLength() - 7); content.Replace(_T(','), _T(' ')); content.Replace(_T('/'), _T(' ')); std::vector tokens; int curPos = 0; CString token; while (!(token = content.Tokenize(_T(" "), curPos)).IsEmpty()) { tokens.push_back(token); } if (tokens.size() >= 3) { double l_val, c_val, h_val; if (ParseCssValue(tokens[0], l_val) && ParseCssValue(tokens[1], c_val) && ParseCssValue(tokens[2], h_val)) { bool l_is_percent = tokens[0].Find('%') != -1; double l_normalized = l_val; if (l_is_percent) { l_normalized = l_val / 100.0; } int alpha = 255; double a_val = 1.0; if (tokens.size() >= 4 && ParseCssValue(tokens[3], a_val)) { if (tokens[3].Find('%') != -1) { a_val /= 100.0; } a_val = max(0.0, min(1.0, a_val)); alpha = static_cast(std::round(a_val * 255.0)); } if (l_normalized >= 0 && l_normalized <= 1.0 && c_val >= 0) { DrawColorBox(OklchToRgb(l_normalized, c_val, h_val), alpha); return; } } } } } // 4. --- Non-W3C Format Parsing --- int r, g, b, chars_consumed = 0; // Check for parenthesized RGB: "(255, 128, 0)" or "(255 128 0)" if (parseText.Left(1) == _T("(") && parseText.Right(1) == _T(")")) { CString content = parseText.Mid(1, parseText.GetLength() - 2); content.Trim(); content.Replace(_T(','), _T(' ')); if (swscanf(content, _T("%d %d %d %n"), &r, &g, &b, &chars_consumed) == 3) { CString remainingText = content.Mid(chars_consumed); remainingText.Trim(); if (remainingText.IsEmpty()) { if (r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255) { DrawColorBox(RGB(r, g, b)); return; } } } } // Check for comma-separated RGB: "255, 0, 0" if (swscanf(parseText, _T("%d , %d , %d %n"), &r, &g, &b, &chars_consumed) == 3 && chars_consumed == parseText.GetLength()) { if (r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255) { DrawColorBox(RGB(r, g, b)); return; } } // Check for space-separated RGB: "255 128 0" if (swscanf(parseText, _T("%d %d %d %n"), &r, &g, &b, &chars_consumed) == 3 && chars_consumed == parseText.GetLength()) { if (r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255) { DrawColorBox(RGB(r, g, b)); return; } } // Check for 6-digit hex: "FF00CC" if (parseText.GetLength() == 6 && IsHexString(parseText)) { // Use %n here as well for consistency, though length check is sufficient. if (swscanf(parseText, _T("%2x%2x%2x%n"), &r, &g, &b, &chars_consumed) == 3 && chars_consumed == 6) { DrawColorBox(RGB(r, g, b)); return; } } } BOOL CQListCtrl::DrawRtfText(int nItem, CRect& crRect, CDC* pDC) { if (CGetSetOptions::m_bDrawRTF == FALSE) return FALSE; BOOL bRet = FALSE; CClipFormat* pThumbnail = GetItem_CF_RTF_ClipFormat(nItem); if (pThumbnail == NULL) return FALSE; // if there's no data, then we're done. if (pThumbnail->m_hgData == NULL) return FALSE; if (m_pFormatter == NULL) { m_pFormatter = new CFormattedTextDraw; m_pFormatter->Create(); } if (m_rtfFormater.m_hWnd == NULL) { m_rtfFormater.Create(_T(""), _T(""), WS_CHILD | WS_VSCROLL | WS_HSCROLL | ES_MULTILINE | ES_AUTOVSCROLL | ES_NOHIDESEL | ES_AUTOHSCROLL, CRect(0, 0, 0, 0), this, -1); } if (m_pFormatter) { //somehow ms word places crazy rtf text onto the clipboard and our draw routine doesn't handle that //pass the rtf text into a richtext control and get it out and the contorl will clean the rtf so our routine can draw it m_rtfFormater.SetRTF(pThumbnail->GetAsCStringA()); CString betterRTF = m_rtfFormater.GetRTF(); CComBSTR bStr(betterRTF); m_pFormatter->put_RTFText(bStr); m_pFormatter->Draw(pDC->m_hDC, crRect); bRet = TRUE; } return bRet; } // DrawBitMap loads a DIB from the DB, draws a crRect thumbnail of the image // to pDC and caches that thumbnail as a DIB in m_ThumbNails[ ItemID ]. // ALL items are cached in m_ThumbNails (those without images are cached with NULL m_hgData) BOOL CQListCtrl::DrawBitMap(int nItem, CRect& crRect, CDC* pDC, const CString& csDescription) { if (CGetSetOptions::m_bDrawThumbnail == FALSE) return FALSE; CClipFormatQListCtrl* format = GetItem_CF_DIB_ClipFormat(nItem); if (format != NULL) { HGLOBAL smallImage = format->GetDibFittingToHeight(pDC, crRect.Height()); if (smallImage != NULL) { //Will return the width of the bitmap in nWidth int nWidth = 0; if (CBitmapHelper::DrawDIB(pDC, smallImage, crRect.left, crRect.top, nWidth)) { // adjust the rect so other information can be drawn next to the thumbnail crRect.left += nWidth + 3; } } } else if (csDescription.Find(_T("CF_DIB")) == 0) { crRect.left += crRect.Height(); } return TRUE; } void CQListCtrl::RefreshVisibleRows() { int nTopIndex = GetTopIndex(); int nLastIndex = nTopIndex + GetCountPerPage(); RedrawItems(nTopIndex, nLastIndex); } void CQListCtrl::RefreshRow(int row) { RedrawItems(row, row); } void CQListCtrl::OnSysKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { CListCtrl::OnSysKeyDown(nChar, nRepCnt, nFlags); } BOOL CQListCtrl::OnEraseBkgnd(CDC* pDC) { CRect rect; GetClientRect(&rect); CBrush myBrush(CGetSetOptions::m_Theme.MainWindowBG()); // dialog background color CBrush* pOld = pDC->SelectObject(&myBrush); BOOL bRes = pDC->PatBlt(0, 0, rect.Width(), rect.Height(), PATCOPY); pDC->SelectObject(pOld); // restore old brush return bRes; // CDialog::OnEraseBkgnd(pDC); // Simply returning TRUE seems OK since we do custom item // painting. However, there is a pixel buffer around the // border of this control (not within the item rects) // which becomes visually corrupt if it is not erased. // In most cases, I do not notice the erasure, so I have kept // the call to CListCtrl::OnEraseBkgnd(pDC); // However, for some reason, bulk erasure is very noticeable when // shift-scrolling the page to select a block of items, so // I made a special case for that: //if(GetSelectedCount() >= 2) // return TRUE; //return CListCtrl::OnEraseBkgnd(pDC); } BOOL CQListCtrl::OnToolTipText(UINT id, NMHDR* pNMHDR, LRESULT* pResult) { // need to handle both ANSI and UNICODE versions of the message TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR; TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR; CString strTipText; UINT_PTR nID = pNMHDR->idFrom; if (nID == 0) // Notification in NT from automatically return FALSE; // created tooltip ::SendMessage(pNMHDR->hwndFrom, TTM_SETMAXTIPWIDTH, 0, 500); if (CGetSetOptions::m_tooltipTimeout > 0) { ::SendMessage(pNMHDR->hwndFrom, TTM_SETDELAYTIME, TTDT_AUTOPOP, MAKELPARAM(CGetSetOptions::m_tooltipTimeout, 0)); } // Use Item's name as the tool tip. Change this for something different. // Like use its file size, etc. GetToolTipText((int)nID - 1, strTipText); //Replace the tabs with spaces, the tooltip didn't like the \t s strTipText.Replace(_T("\t"), _T(" ")); int nLength = strTipText.GetLength() + 2; #ifndef _UNICODE if (pNMHDR->code == TTN_NEEDTEXTA) { if (m_pchTip != NULL) delete m_pchTip; m_pchTip = new TCHAR[nLength]; lstrcpyn(m_pchTip, strTipText, nLength - 1); m_pchTip[nLength - 1] = 0; pTTTW->lpszText = (WCHAR*)m_pchTip; } else { if (m_pwchTip != NULL) delete m_pwchTip; m_pwchTip = new WCHAR[nLength]; _mbstowcsz(m_pwchTip, strTipText, nLength - 1); m_pwchTip[nLength - 1] = 0; // end of text pTTTW->lpszText = (WCHAR*)m_pwchTip; } #else if (pNMHDR->code == TTN_NEEDTEXTA) { if (m_pchTip != NULL) delete m_pchTip; m_pchTip = new TCHAR[nLength]; STRNCPY(m_pchTip, strTipText, nLength - 1); m_pchTip[nLength - 1] = 0; // end of text pTTTW->lpszText = (LPTSTR)m_pchTip; } else { if (m_pwchTip != NULL) delete m_pwchTip; m_pwchTip = new WCHAR[nLength]; lstrcpyn(m_pwchTip, strTipText, nLength - 1); m_pwchTip[nLength - 1] = 0; pTTTW->lpszText = (LPTSTR)m_pwchTip; } #endif * pResult = 0; return TRUE; // message was handled } INT_PTR CQListCtrl::OnToolHitTest(CPoint point, TOOLINFO* pTI) const { CRect rect; GetClientRect(&rect); if (rect.PtInRect(point)) { if (GetItemCount()) { int nTopIndex = GetTopIndex(); int nBottomIndex = nTopIndex + GetCountPerPage(); if (nBottomIndex > GetItemCount()) nBottomIndex = GetItemCount(); for (int nIndex = nTopIndex; nIndex <= nBottomIndex; nIndex++) { GetItemRect(nIndex, rect, LVIR_BOUNDS); if (rect.PtInRect(point)) { pTI->hwnd = m_hWnd; pTI->uId = (UINT)(nIndex + 1); pTI->lpszText = LPSTR_TEXTCALLBACK; pTI->rect = rect; pTI->uFlags = TTF_TRANSPARENT; return pTI->uId; } } } } return -1; } int CQListCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CListCtrl::OnCreate(lpCreateStruct) == -1) return -1; if (CGetSetOptions::m_tooltipTimeout > 0 || CGetSetOptions::m_tooltipTimeout == -1) { EnableToolTips(); } else { EnableToolTips(FALSE); } //m_pToolTip = new CToolTipEx; //m_pToolTip->Create(this); //m_pToolTip->SetNotifyWnd(GetParent()); return 0; } BOOL CQListCtrl::PreTranslateMessage(MSG* pMsg) { CAccel a; if (m_Accels.OnMsg(pMsg, a)) { switch (a.Cmd) { case COPY_BUFFER_HOT_KEY_1_ID: PutSelectedItemOnDittoCopyBuffer(0); break; case COPY_BUFFER_HOT_KEY_2_ID: PutSelectedItemOnDittoCopyBuffer(1); break; case COPY_BUFFER_HOT_KEY_3_ID: PutSelectedItemOnDittoCopyBuffer(2); break; default: if (a.RefId == CHotKey::PASTE_OPEN_CLIP) { GetParent()->SendMessage(NM_SELECT_DB_ID, a.Cmd, 0); } else if (a.RefId == CHotKey::MOVE_TO_GROUP) { GetParent()->SendMessage(NM_MOVE_TO_GROUP, a.Cmd, 0); } } return TRUE; } if (VALID_TOOLTIP) { if (m_pToolTip->OnMsg(pMsg)) return TRUE; } switch (pMsg->message) { case WM_KEYDOWN: if (HandleKeyDown(pMsg->wParam, pMsg->lParam)) return TRUE; break; // end case WM_KEYDOWN case WM_MOUSEWHEEL: // 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, TRUE, 0); } return result; } case WM_VSCROLL: ASSERT(FALSE); break; } // end switch(pMsg->message) return CListCtrl::PreTranslateMessage(pMsg); } BOOL CQListCtrl::HandleKeyDown(WPARAM wParam, LPARAM lParam) { if (VALID_TOOLTIP) { MSG Msg; Msg.lParam = lParam; Msg.wParam = wParam; Msg.message = WM_KEYDOWN; if (m_pToolTip->OnMsg(&Msg)) return TRUE; } WPARAM vk = wParam; switch (vk) { case 'A': // Ctrl-A = Select All if (CONTROL_PRESSED) { int nCount = GetItemCount(); for (int i = 0; i < nCount; i++) { SetSelection(i); } return TRUE; } break; case VK_HOME: if (GetKeyState(VK_SHIFT) & 0x8000) { int nAnchor = GetSelectionMark(); if (nAnchor < 0) { nAnchor = GetCaret(); } if (nAnchor >= 0) { RemoveAllSelection(); for (int i = 0; i <= nAnchor; i++) { SetSelection(i, TRUE); } ListView_SetSelectionMark(m_hWnd, nAnchor); SetCaret(0); EnsureVisible(0, FALSE); } } else { SetListPos(0); } return TRUE; } // end switch(vk) return FALSE; } bool CQListCtrl::PostEventLoadedCheckDescription(int updatedRow) { bool loadedClip = false; if (VALID_TOOLTIP) { int toolTipClipId = m_pToolTip->GetClipId(); int toolTipClipRow = m_pToolTip->GetClipRow(); if (toolTipClipRow >= 0) { log(StrF(_T("PostEventLoadedCheckDescription refreshRow: %d tt_row: %d tt_id: %d"), updatedRow, toolTipClipRow, toolTipClipId)); } //We tried to show the clip but we didn't have the id yet, it was loaded in a thread, now it's being updated //see if we need to show this rows description if (toolTipClipId <= 0 && toolTipClipRow == updatedRow && ::IsWindow(m_toolTipHwnd)) { ShowFullDescription(false, true); loadedClip = true; } } return loadedClip; } bool CQListCtrl::ShowFullDescription(bool bFromAuto, bool fromNextPrev) { if (this->GetSelectedCount() == 0) { return false; } int clipRow = this->GetCaret(); int clipId = this->GetItemData(clipRow); log(StrF(_T("Show full description row: %d id: %d"), clipRow, clipId)); if (VALID_TOOLTIP && clipId > 0 && m_pToolTip->GetClipId() == clipId && ::IsWindow(m_toolTipHwnd)) { return false; } int nItem = GetCaret(); CRect rc, crWindow; GetWindowRect(&crWindow); GetItemRect(nItem, rc, LVIR_BOUNDS); ClientToScreen(rc); CPoint pt; if (CGetSetOptions::GetRememberDescPos()) { CGetSetOptions::GetDescWndPoint(pt); } else if (bFromAuto == false) { pt = CPoint(rc.left, rc.bottom); } else { pt = CPoint((crWindow.left + (crWindow.right - crWindow.left) / 2), rc.bottom); } CString csDescription; GetToolTipText(nItem, csDescription); if (m_pToolTip == NULL || //fromNextPrev == false || ::IsWindow(m_toolTipHwnd) == FALSE) { if (m_pToolTip != NULL) { m_pToolTip->DestroyWindow(); } m_pToolTip = new CToolTipEx; m_pToolTip->Create(this); m_toolTipHwnd = m_pToolTip->GetSafeHwnd(); m_pToolTip->SetNotifyWnd(GetParent()); } else if (VALID_TOOLTIP) { if (fromNextPrev) { CRect r; m_pToolTip->GetWindowRectEx(r); pt = r.TopLeft(); } m_pToolTip->SetGdiplusBitmap(NULL); m_pToolTip->SetRTFText(""); m_pToolTip->SetToolTipText(_T("")); m_pToolTip->SetFolderPath(_T("")); } if (VALID_TOOLTIP) { m_pToolTip->SetTooltipActions(m_pToolTipActions); m_pToolTip->SetClipId(clipId); m_pToolTip->SetClipRow(clipRow); m_pToolTip->SetSearchText(m_searchText); LOGFONT lf; m_Font.GetLogFont(&lf); lf.lfHeight = m_windowDpi->UnScale(lf.lfHeight); m_pToolTip->SetLogFont(&lf, FALSE); m_pToolTip->SetClipData(_T("")); m_pToolTip->SetToolTipText(_T("")); m_pToolTip->SetRTFText(""); bool bSetPlainText = false; CClipFormat Clip; try { CppSQLite3Query q = theApp.m_db.execQueryEx(_T("SELECT lID, lDate, lastPasteDate, lDontAutoDelete, QuickPasteText, lShortCut, globalShortCut, stickyClipOrder, stickyClipGroupOrder, lParentID FROM Main WHERE lID = %d"), clipId); if (q.eof() == false) { CString clipData; COleDateTime time((time_t)q.getInt64Field(_T("lDate"))); clipData += "Added: " + time.Format(); COleDateTime modified((time_t)q.getInt64Field(_T("lastPasteDate"))); clipData += _T(" | Last Used: ") + modified.Format(); if (q.getIntField(_T("lDontAutoDelete")) > 0) { clipData += _T(" | Never Auto Delete"); } CString csQuickPaste = q.getStringField(_T("QuickPasteText")); if (csQuickPaste.IsEmpty() == FALSE) { clipData += _T(" | Quick Paste = "); clipData += csQuickPaste; } int shortCut = q.getIntField(_T("lShortCut")); if (shortCut > 0) { clipData += _T(" | "); clipData += CHotKey::GetHotKeyDisplayStatic(shortCut); BOOL globalShortCut = q.getIntField(_T("globalShortCut")); if (globalShortCut) { clipData += _T(" - Global Shortcut Key"); } } if (theApp.m_GroupID > 0) { int sticky = q.getIntField(_T("stickyClipGroupOrder")); if (sticky != INVALID_STICKY) { clipData += _T(" | "); clipData += _T(" - Sticky In Group"); } } else { int sticky = q.getIntField(_T("stickyClipOrder")); if (sticky != INVALID_STICKY) { clipData += _T(" | "); clipData += _T(" - Sticky"); } } int parentId = q.getIntField(_T("lParentID")); if (parentId > 0) { CString folder = FolderPath(parentId); m_pToolTip->SetFolderPath(folder); } m_pToolTip->SetClipData(clipData); } } CATCH_SQLITE_EXCEPTION Clip.m_cfType = CF_UNICODETEXT; if (GetClipData(nItem, Clip) && Clip.m_hgData) { m_pToolTip->SetToolTipText(Clip.GetAsCString()); bSetPlainText = true; Clip.Free(); Clip.Clear(); } if (bSetPlainText == false) { Clip.m_cfType = CF_TEXT; if (GetClipData(nItem, Clip) && Clip.m_hgData) { CString cs(Clip.GetAsCStringA()); m_pToolTip->SetToolTipText(cs); bSetPlainText = true; Clip.Free(); Clip.Clear(); } } if (bSetPlainText == false) { m_pToolTip->SetToolTipText(csDescription); } Clip.m_cfType = RegisterClipboardFormat(CF_RTF); if (GetClipData(nItem, Clip) && Clip.m_hgData) { m_pToolTip->SetRTFText(Clip.GetAsCStringA()); Clip.Free(); Clip.Clear(); } m_pToolTip->SetHtmlText(_T("")); Clip.m_cfType = GetFormatID(_T("HTML Format")); if (GetClipData(nItem, Clip) && Clip.m_hgData) { CString html = CTextConvert::Utf8ToUnicode(Clip.GetAsCStringA()); m_pToolTip->SetHtmlText(html); Clip.Free(); Clip.Clear(); } Clip.m_cfType = CF_DIB; if (GetClipData(nItem, Clip) && Clip.m_hgData) { m_pToolTip->SetGdiplusBitmap(Clip.CreateGdiplusBitmap()); } else { Clip.m_cfType = theApp.m_PNG_Format; if (GetClipData(nItem, Clip) && Clip.m_hgData) { m_pToolTip->SetGdiplusBitmap(Clip.CreateGdiplusBitmap()); } } m_pToolTip->Show(pt); } return true; } void CQListCtrl::GetToolTipText(int nItem, CString& csText) { CWnd* pParent = GetParent(); if (pParent && (pParent->GetSafeHwnd() != NULL)) { CQListToolTipText info; memset(&info, 0, sizeof(info)); info.hdr.code = NM_GETTOOLTIPTEXT; info.hdr.hwndFrom = GetSafeHwnd(); info.hdr.idFrom = GetDlgCtrlID(); info.lItem = nItem; //plus 100 for extra info - shortcut and such int maxCharacters = CGetSetOptions::GetMaxToolTipCharacters(); info.cchTextMax = min(maxCharacters, CGetSetOptions::m_bDescTextSize) + 200; info.pszText = csText.GetBufferSetLength(info.cchTextMax); pParent->SendMessage(WM_NOTIFY, (WPARAM)info.hdr.idFrom, (LPARAM)&info); csText.ReleaseBuffer(); } } BOOL CQListCtrl::GetClipData(int nItem, CClipFormat& Clip) { return theApp.GetClipData(GetItemData(nItem), Clip); } DWORD CQListCtrl::GetItemData(int nItem) { if ((GetStyle() & LVS_OWNERDATA)) { CWnd* pParent = GetParent(); if (pParent && (pParent->GetSafeHwnd() != NULL)) { LV_DISPINFO info; memset(&info, 0, sizeof(info)); info.hdr.code = LVN_GETDISPINFO; info.hdr.hwndFrom = GetSafeHwnd(); info.hdr.idFrom = GetDlgCtrlID(); info.item.iItem = nItem; info.item.lParam = -1; info.item.mask = LVIF_PARAM; pParent->SendMessage(WM_NOTIFY, (WPARAM)info.hdr.idFrom, (LPARAM)&info); return (DWORD)info.item.lParam; } } return (DWORD)CListCtrl::GetItemData(nItem); } CClipFormatQListCtrl* CQListCtrl::GetItem_CF_DIB_ClipFormat(int nItem) { CClipFormatQListCtrl* format = NULL; CWnd* pParent = GetParent(); if (pParent && (pParent->GetSafeHwnd() != NULL)) { LV_DISPINFO info; memset(&info, 0, sizeof(info)); info.hdr.code = LVN_GETDISPINFO; info.hdr.hwndFrom = GetSafeHwnd(); info.hdr.idFrom = GetDlgCtrlID(); info.item.iItem = nItem; info.item.lParam = NULL; info.item.mask = LVIF_CF_DIB; pParent->SendMessage(WM_NOTIFY, (WPARAM)info.hdr.idFrom, (LPARAM)&info); if (info.item.lParam != NULL) { format = (CClipFormatQListCtrl*)info.item.lParam; } } return format; } CClipFormatQListCtrl* CQListCtrl::GetItem_CF_RTF_ClipFormat(int nItem) { CClipFormatQListCtrl* format = NULL; CWnd* pParent = GetParent(); if (pParent && (pParent->GetSafeHwnd() != NULL)) { LV_DISPINFO info; memset(&info, 0, sizeof(info)); info.hdr.code = LVN_GETDISPINFO; info.hdr.hwndFrom = GetSafeHwnd(); info.hdr.idFrom = GetDlgCtrlID(); info.item.iItem = nItem; info.item.lParam = NULL; info.item.mask = LVIF_CF_RICHTEXT; pParent->SendMessage(WM_NOTIFY, (WPARAM)info.hdr.idFrom, (LPARAM)&info); if (info.item.lParam != NULL) { format = (CClipFormatQListCtrl*)info.item.lParam; } } return format; } void CQListCtrl::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) { CListCtrl::OnHScroll(nSBCode, nPos, pScrollBar); } void CQListCtrl::DestroyAndCreateAccelerator(BOOL bCreate, CppSQLite3DB& db) { m_Accels.RemoveAll(); if (bCreate) { CMainTableFunctions::LoadAcceleratorKeys(m_Accels, db); LoadDittoCopyBufferHotkeys(); } } void CQListCtrl::LoadDittoCopyBufferHotkeys() { CCopyBufferItem Item; CAccel a; CGetSetOptions::GetCopyBufferItem(0, Item); if (Item.m_lCopyHotKey > 0) { a.Cmd = COPY_BUFFER_HOT_KEY_1_ID; a.Key = Item.m_lCopyHotKey; m_Accels.AddAccel(a); } CGetSetOptions::GetCopyBufferItem(1, Item); if (Item.m_lCopyHotKey > 0) { a.Cmd = COPY_BUFFER_HOT_KEY_2_ID; a.Key = Item.m_lCopyHotKey; m_Accels.AddAccel(a); } CGetSetOptions::GetCopyBufferItem(2, Item); if (Item.m_lCopyHotKey > 0) { a.Cmd = COPY_BUFFER_HOT_KEY_3_ID; a.Key = Item.m_lCopyHotKey; m_Accels.AddAccel(a); } } void CQListCtrl::OnKillFocus(CWnd* pNewWnd) { CListCtrl::OnKillFocus(pNewWnd); //if(FocusOnToolTip() == FALSE) //m_pToolTip->Hide(); } HWND CQListCtrl::GetToolTipHWnd() { if (VALID_TOOLTIP) return m_pToolTip->GetSafeHwnd(); return NULL; } BOOL CQListCtrl::SetItemCountEx(int iCount, DWORD dwFlags /* = 0 */) { return CListCtrl::SetItemCountEx(iCount, dwFlags); } void CQListCtrl::OnSelectionChange(NMHDR* pNMHDR, LRESULT* pResult) { NMLISTVIEW* pnmv = (NMLISTVIEW*)pNMHDR; 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, FALSE, 0); } if (VALID_TOOLTIP && ::IsWindowVisible(m_pToolTip->m_hWnd)) { this->ShowFullDescription(false, true); } if (CGetSetOptions::m_bAllwaysShowDescription) { KillTimer(TIMER_SHOW_PROPERTIES); SetTimer(TIMER_SHOW_PROPERTIES, 300, NULL); } if (GetSelectedCount() > 0) theApp.SetStatus(NULL, FALSE); } if (GetSelectedCount() == this->GetItemCount()) { if (m_allSelected == false) { Log(StrF(_T("List box Select All"))); GetParent()->SendMessage(NM_ALL_SELECTED, 0, 0); m_allSelected = true; } } else if (m_allSelected == true) { Log(StrF(_T("List box REMOVED Select All"))); m_allSelected = false; } } void CQListCtrl::OnTimer(UINT_PTR nIDEvent) { //http://support.microsoft.com/kb/200054 //OnTimer() Is Not Called Repeatedly for a List Control bool callBase = true; switch (nIDEvent) { case TIMER_SHOW_PROPERTIES: { if (theApp.m_bShowingQuickPaste) ShowFullDescription(true); KillTimer(TIMER_SHOW_PROPERTIES); callBase = false; } break; case TIMER_HIDE_SCROL: { CPoint cursorPos; GetCursorPos(&cursorPos); CRect crWindow; this->GetWindowRect(&crWindow); //check and see if they moved out of the scroll area //If they did tell our parent so if (MouseInScrollBarArea(crWindow, cursorPos) == false) { StopHideScrollBarTimer(); } callBase = false; } break; case TIMER_SHOW_SCROLL: { CPoint cursorPos; GetCursorPos(&cursorPos); CRect crWindow; this->GetWindowRect(&crWindow); //Adjust for the v-scroll bar being off of the screen crWindow.right -= m_windowDpi->Scale(GetSystemMetrics(SM_CXVSCROLL)); crWindow.bottom -= m_windowDpi->Scale(::GetSystemMetrics(SM_CXHSCROLL)); //Check and see if we are still in the cursor area if (MouseInScrollBarArea(crWindow, cursorPos)) { m_timerToHideScrollAreaSet = true; GetParent()->SendMessage(NM_SHOW_HIDE_SCROLLBARS, 1, 0); //Start looking to hide the scroll bars SetTimer(TIMER_HIDE_SCROL, 1000, NULL); } KillTimer(TIMER_SHOW_SCROLL); callBase = false; } break; } if (callBase) { CListCtrl::OnTimer(nIDEvent); } } void CQListCtrl::SetLogFont(LOGFONT& font) { m_Font.DeleteObject(); m_boldFont.DeleteObject(); m_Font.CreateFontIndirect(&font); font.lfWeight = 600; m_boldFont.CreateFontIndirect(&font); SetFont(&m_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, TRUE, 0); } } BOOL CQListCtrl::OnChildNotify(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pLResult) { NMLVCACHEHINT* pcachehint = NULL; if (message == WM_NOTIFY) { NMHDR* phdr = (NMHDR*)lParam; switch (phdr->code) { case LVN_ODCACHEHINT: pcachehint = (NMLVCACHEHINT*)phdr; GetParent()->SendMessage(NM_FILL_REST_OF_LIST, pcachehint->iFrom, pcachehint->iTo); return FALSE; } } return CListCtrl::OnChildNotify(message, wParam, lParam, pLResult); } BOOL CQListCtrl::OnItemDeleted(long lID) { BOOL bRet2 = m_RTFData.RemoveKey(lID); return (bRet2); } void CQListCtrl::OnMouseMove(UINT nFlags, CPoint point) { if (CGetSetOptions::m_showScrollBar == FALSE) { CRect crWindow; this->GetWindowRect(&crWindow); ScreenToClient(&crWindow); // Don't subtract scrollbar size - detect in the full window area // This prevents flickering when scrollbar appears/disappears if (MouseInScrollBarArea(crWindow, point)) { // Show scrollbar immediately when mouse enters scrollbar area if (m_mouseOverScrollAreaStart == 0) { m_mouseOverScrollAreaStart = GetTickCount(); // For modern scrollbar, notify parent if (CGetSetOptions::m_useModernScrollBar) { GetParent()->PostMessage(NM_UPDATE_SCROLLBAR, TRUE, 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(); } KillTimer(TIMER_SHOW_SCROLL); } } CListCtrl::OnMouseMove(nFlags, 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); // 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()); OutputDebugString(cs);*/ if (crRight.PtInRect(point) || crBottom.PtInRect(point)) { return true; } return false; } void CQListCtrl::StopHideScrollBarTimer() { GetParent()->SendMessage(NM_SHOW_HIDE_SCROLLBARS, 0, 0); m_timerToHideScrollAreaSet = false; KillTimer(TIMER_HIDE_SCROL); } void CQListCtrl::SetSearchText(CString text) { m_searchText = text; } void CQListCtrl::HidePopup(bool checkShowPersistant) { if (VALID_TOOLTIP) { if (checkShowPersistant == false || m_pToolTip->GetShowPersistant() == false) { m_pToolTip->Hide(); } } } BOOL CQListCtrl::IsToolTipWindowVisible() { if (VALID_TOOLTIP) { return ::IsWindowVisible(m_toolTipHwnd); } return FALSE; } void CQListCtrl::ToggleToolTipShowPersistant() { if (VALID_TOOLTIP) { m_pToolTip->ToggleShowPersistant(); } } bool CQListCtrl::ToggleToolTipWordWrap() { bool didWordWrap = false; if (VALID_TOOLTIP) { didWordWrap = m_pToolTip->ToggleWordWrap(); } return didWordWrap; } BOOL CQListCtrl::IsToolTipWindowFocus() { if (VALID_TOOLTIP) { return ::GetFocus() == m_toolTipHwnd || ::GetParent(::GetFocus()) == m_toolTipHwnd; } return FALSE; } bool CQListCtrl::IsToolTipShowPersistant() { if (VALID_TOOLTIP) { return m_pToolTip->GetShowPersistant(); } return false; } void CQListCtrl::DoToolTipSearch() { if (VALID_TOOLTIP) { return m_pToolTip->DoSearch(); } } void CQListCtrl::HideToolTip() { if (VALID_TOOLTIP) { m_pToolTip->Hide(); } } void CQListCtrl::OnDpiChanged() { SetDpiInfo(m_windowDpi); } void CQListCtrl::SetDpiInfo(CDPI* dpi) { m_windowDpi = dpi; m_groupFolder.Reset(); m_groupFolder.LoadStdImageDPI(m_windowDpi->GetDPI(), IDB_OPEN_FOLDER_16_16, IDB_OPEN_FOLDER_20_20, IDB_OPEN_FOLDER_24_24, IDB_OPEN_FOLDER_24_24, IDB_OPEN_FOLDER_32_32, _T("PNG")); m_dontDeleteImage.Reset(); m_dontDeleteImage.LoadStdImageDPI(m_windowDpi->GetDPI(), IDB_YELLOW_STAR_16_16, IDB_YELLOW_STAR_20_20, IDB_YELLOW_STAR_24_24, IDB_YELLOW_STAR_24_24, IDB_YELLOW_STAR_32_32, _T("PNG")); m_inFolderImage.Reset(); m_inFolderImage.LoadStdImageDPI(m_windowDpi->GetDPI(), IDB_IN_FOLDER_16_16, IDB_IN_FOLDER_20_20, IDB_IN_FOLDER_24_24, IDB_IN_FOLDER_24_24, IDB_IN_FOLDER_32_32, _T("PNG")); m_shortCutImage.Reset(); m_shortCutImage.LoadStdImageDPI(m_windowDpi->GetDPI(), IDB_KEY_16_16, IDB_KEY_20_20, IDB_KEY_24_24, IDB_KEY_24_24, IDB_KEY_32_32, _T("PNG")); m_stickyImage.Reset(); m_stickyImage.LoadStdImageDPI(m_windowDpi->GetDPI(), IDB_STICKY_16_16, IDB_STICKY_20_20, IDB_STICKY_24_24, IDB_STICKY_24_24, IDB_STICKY_32_32, _T("PNG")); DeleteObject(m_SmallFont); CreateSmallFont(); } void CQListCtrl::CreateSmallFont() { LOGFONT lf; lf.lfHeight = -MulDiv(CGetSetOptions::GetFirstTenHotKeysFontSize(), m_windowDpi->GetDPI(), 72); lf.lfWidth = 0; lf.lfEscapement = 0; lf.lfOrientation = 0; lf.lfWeight = FW_LIGHT; lf.lfItalic = FALSE; lf.lfUnderline = FALSE; lf.lfStrikeOut = FALSE; lf.lfCharSet = ANSI_CHARSET; lf.lfOutPrecision = OUT_STRING_PRECIS; lf.lfClipPrecision = CLIP_STROKE_PRECIS; lf.lfQuality = DEFAULT_QUALITY; lf.lfPitchAndFamily = VARIABLE_PITCH | FF_DONTCARE; lstrcpy(lf.lfFaceName, _T("Small Font")); m_SmallFont = ::CreateFontIndirect(&lf); } void CQListCtrl::OnMouseHWheel(UINT nFlags, short zDelta, CPoint pt) { if (zDelta < 0) { this->SendMessage(WM_HSCROLL, SB_LINERIGHT, NULL); } else { 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, TRUE, 0); } //CListCtrl::OnMouseHWheel(nFlags, zDelta, pt); }