Integrate the DPIAware.h changes from TortoiseSVN
[TortoiseGit.git] / src / Utils / MiscUI / FilterEdit.cpp
blob0da6f74cf18b9848fdd253ba88b4723ab9aedcea
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2012, 2014, 2016-2018 - TortoiseGit
4 // Copyright (C) 2007, 2012-2014, 2018 - TortoiseSVN
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "stdafx.h"
21 #include "FilterEdit.h"
22 #include "DPIAware.h"
24 const UINT CFilterEdit::WM_FILTEREDIT_INFOCLICKED = ::RegisterWindowMessage(L"TGITWM_FILTEREDIT_INFOCLICKED");
25 const UINT CFilterEdit::WM_FILTEREDIT_CANCELCLICKED = ::RegisterWindowMessage(L"TGITWM_FILTEREDIT_CANCELCLICKED");
27 IMPLEMENT_DYNAMIC(CFilterEdit, CEdit)
29 CFilterEdit::CFilterEdit()
30 : m_hIconCancelNormal(nullptr)
31 , m_hIconCancelPressed(nullptr)
32 , m_hIconInfo(nullptr)
33 , m_bPressed(FALSE)
34 , m_bShowCancelButtonAlways(FALSE)
35 , m_iButtonClickedMessageId(WM_FILTEREDIT_INFOCLICKED)
36 , m_iCancelClickedMessageId(WM_FILTEREDIT_CANCELCLICKED)
37 , m_pValidator(nullptr)
38 , m_backColor(GetSysColor(COLOR_WINDOW))
40 m_rcEditArea.SetRect(0, 0, 0, 0);
41 m_rcButtonArea.SetRect(0, 0, 0, 0);
42 m_rcInfoArea.SetRect(0, 0, 0, 0);
43 m_sizeInfoIcon.SetSize(0, 0);
44 m_sizeCancelIcon.SetSize(0, 0);
47 CFilterEdit::~CFilterEdit()
49 if (m_hIconCancelNormal)
50 DestroyIcon(m_hIconCancelNormal);
51 if (m_hIconCancelPressed)
52 DestroyIcon(m_hIconCancelPressed);
53 if (m_hIconInfo)
54 DestroyIcon(m_hIconInfo);
57 BEGIN_MESSAGE_MAP(CFilterEdit, CEdit)
59 ON_MESSAGE(WM_SETFONT, OnSetFont)
60 ON_WM_SIZE()
61 ON_WM_ERASEBKGND()
62 ON_WM_KEYDOWN()
63 ON_WM_LBUTTONDOWN()
64 ON_WM_LBUTTONUP()
65 ON_WM_CREATE()
66 ON_WM_SETCURSOR()
67 ON_CONTROL_REFLECT_EX(EN_CHANGE, &CFilterEdit::OnEnChange)
68 ON_WM_CTLCOLOR_REFLECT()
69 ON_WM_PAINT()
70 ON_CONTROL_REFLECT(EN_KILLFOCUS, &CFilterEdit::OnEnKillfocus)
71 ON_CONTROL_REFLECT(EN_SETFOCUS, &CFilterEdit::OnEnSetfocus)
72 ON_MESSAGE(WM_PASTE, &CFilterEdit::OnPaste)
73 ON_WM_SYSCOLORCHANGE()
74 END_MESSAGE_MAP()
78 // CFilterEdit message handlers
80 void CFilterEdit::PreSubclassWindow( )
82 // We must have a multi line edit
83 // to be able to set the edit rect
84 ASSERT( GetStyle() & ES_MULTILINE );
86 ResizeWindow();
89 BOOL CFilterEdit::PreTranslateMessage( MSG* pMsg )
91 return CEdit::PreTranslateMessage(pMsg);
94 BOOL CFilterEdit::SetCancelBitmaps(UINT uCancelNormal, UINT uCancelPressed, int cx96dpi, int cy96dpi, BOOL bShowAlways)
96 m_bShowCancelButtonAlways = bShowAlways;
98 if (m_hIconCancelNormal)
99 DestroyIcon(m_hIconCancelNormal);
100 if (m_hIconCancelPressed)
101 DestroyIcon(m_hIconCancelPressed);
103 m_hIconCancelNormal = LoadDpiScaledIcon(uCancelNormal, cx96dpi, cy96dpi);
104 m_hIconCancelPressed = LoadDpiScaledIcon(uCancelPressed, cx96dpi, cy96dpi);
106 if (!m_hIconCancelNormal || !m_hIconCancelPressed)
107 return FALSE;
109 m_sizeCancelIcon = GetIconSize(m_hIconCancelNormal);
111 ResizeWindow();
112 return TRUE;
115 BOOL CFilterEdit::SetInfoIcon(UINT uInfo, int cx96dpi, int cy96dpi)
117 if (m_hIconInfo)
118 DestroyIcon(m_hIconInfo);
120 m_hIconInfo = LoadDpiScaledIcon(uInfo, cx96dpi, cy96dpi);
122 if (!m_hIconInfo)
123 return FALSE;
125 m_sizeInfoIcon = GetIconSize(m_hIconInfo);
127 ResizeWindow();
128 return TRUE;
131 BOOL CFilterEdit::SetCueBanner(LPCWSTR lpcwText)
133 if (lpcwText)
135 m_sCueBanner = lpcwText;
136 InvalidateRect(nullptr, TRUE);
137 return TRUE;
139 m_sCueBanner.Empty();
140 InvalidateRect(nullptr, TRUE);
141 return FALSE;
144 void CFilterEdit::ResizeWindow()
146 if (!::IsWindow(m_hWnd))
147 return;
149 CRect editrc, rc;
150 GetRect(&editrc);
151 GetClientRect(&rc);
152 editrc.left = rc.left + 4;
153 editrc.top = rc.top + 1;
154 editrc.right = rc.right - 4;
155 editrc.bottom = rc.bottom - 4;
157 CWindowDC dc(this);
158 HGDIOBJ oldFont = dc.SelectObject(GetFont()->GetSafeHandle());
159 TEXTMETRIC tm = { 0 };
160 dc.GetTextMetrics(&tm);
161 dc.SelectObject(oldFont);
163 m_rcEditArea.left = editrc.left + m_sizeInfoIcon.cx;
164 m_rcEditArea.right = editrc.right - m_sizeCancelIcon.cx - 5;
165 m_rcEditArea.top = (rc.Height() - tm.tmHeight) / 2;
166 m_rcEditArea.bottom = m_rcEditArea.top + tm.tmHeight;
168 m_rcButtonArea.left = m_rcEditArea.right + 5;
169 m_rcButtonArea.right = rc.right;
170 m_rcButtonArea.top = (((rc.bottom)-m_sizeCancelIcon.cy)/2);
171 m_rcButtonArea.bottom = m_rcButtonArea.top + m_sizeCancelIcon.cy;
173 m_rcInfoArea.left = 0;
174 m_rcInfoArea.right = m_rcEditArea.left;
175 m_rcInfoArea.top = (((rc.bottom)-m_sizeInfoIcon.cy)/2);
176 m_rcInfoArea.bottom = m_rcInfoArea.top + m_sizeInfoIcon.cy;
178 SetRect(&m_rcEditArea);
181 void CFilterEdit::SetButtonClickedMessageId(UINT iButtonClickedMessageId, UINT iCancelClickedMessageId)
183 m_iButtonClickedMessageId = iButtonClickedMessageId;
184 m_iCancelClickedMessageId = iCancelClickedMessageId;
187 CSize CFilterEdit::GetIconSize(HICON hIcon)
189 CSize size(0, 0);
190 ICONINFO iconinfo;
191 if (GetIconInfo(hIcon, &iconinfo))
193 BITMAP bmp;
194 if (GetObject(iconinfo.hbmColor, sizeof(BITMAP), &bmp))
196 size.cx = bmp.bmWidth;
197 size.cy = bmp.bmHeight;
200 return size;
203 BOOL CFilterEdit::OnEraseBkgnd(CDC* pDC)
205 RECT rc;
206 GetClientRect(&rc);
207 pDC->FillSolidRect(&rc, m_backColor);
209 if (GetWindowTextLength() || m_bShowCancelButtonAlways)
211 if (!m_bPressed)
213 DrawIconEx(pDC->GetSafeHdc(), m_rcButtonArea.left, m_rcButtonArea.top, m_hIconCancelNormal,
214 m_sizeCancelIcon.cx, m_sizeCancelIcon.cy, 0, nullptr, DI_NORMAL);
216 else
218 DrawIconEx(pDC->GetSafeHdc(), m_rcButtonArea.left, m_rcButtonArea.top, m_hIconCancelPressed,
219 m_sizeCancelIcon.cx, m_sizeCancelIcon.cy, 0, nullptr, DI_NORMAL);
222 if (m_hIconInfo)
224 DrawIconEx(pDC->GetSafeHdc(), m_rcInfoArea.left, m_rcInfoArea.top, m_hIconInfo,
225 m_sizeInfoIcon.cx, m_sizeInfoIcon.cy, 0, nullptr, DI_NORMAL);
228 return TRUE;
231 void CFilterEdit::OnLButtonUp(UINT nFlags, CPoint point)
233 m_bPressed = FALSE;
234 InvalidateRect(nullptr);
235 if (m_rcButtonArea.PtInRect(point))
237 SetWindowText(L"");
238 CWnd *pOwner = GetOwner();
239 if (pOwner)
241 pOwner->SendMessage(m_iCancelClickedMessageId, (WPARAM)GetSafeHwnd(), 0);
243 Validate();
245 if (m_rcInfoArea.PtInRect(point))
247 CWnd *pOwner = GetOwner();
248 if (pOwner)
250 RECT rc = m_rcInfoArea;
251 ClientToScreen(&rc);
252 pOwner->SendMessage(m_iButtonClickedMessageId, (WPARAM)GetSafeHwnd(), (LPARAM)(LPRECT)&rc);
256 CEdit::OnLButtonUp(nFlags, point);
259 void CFilterEdit::OnLButtonDown(UINT nFlags, CPoint point)
261 m_bPressed = m_rcButtonArea.PtInRect(point);
262 //InvalidateRect(nullptr);
263 CEdit::OnLButtonDown(nFlags, point);
266 int CFilterEdit::OnCreate(LPCREATESTRUCT lpCreateStruct)
268 if (CEdit::OnCreate(lpCreateStruct) == -1)
269 return -1;
271 ResizeWindow();
273 return 0;
276 LRESULT CFilterEdit::OnSetFont( WPARAM wParam, LPARAM lParam )
278 DefWindowProc( WM_SETFONT, wParam, lParam );
280 ResizeWindow();
282 return 0;
285 void CFilterEdit::OnSize( UINT nType, int cx, int cy )
287 CEdit::OnSize( nType, cx, cy );
288 ResizeWindow();
291 BOOL CFilterEdit::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
293 CPoint pntCursor;
294 GetCursorPos(&pntCursor);
295 ScreenToClient(&pntCursor);
296 // if the cursor is not in the edit area, show the normal arrow cursor
297 if (!m_rcEditArea.PtInRect(pntCursor))
299 SetCursor(AfxGetApp()->LoadStandardCursor(IDC_ARROW));
300 return TRUE;
303 return CEdit::OnSetCursor(pWnd, nHitTest, message);
306 BOOL CFilterEdit::OnEnChange()
308 // check whether the entered text is valid
309 Validate();
310 InvalidateRect(nullptr);
311 return FALSE;
314 HBRUSH CFilterEdit::CtlColor(CDC* pDC, UINT /*nCtlColor*/)
316 if (m_backColor != GetSysColor(COLOR_WINDOW))
318 pDC->SetBkColor(m_backColor);
319 return m_brBack;
321 return nullptr;
324 void CFilterEdit::ValidateAndRedraw()
326 OnEnChange();
329 void CFilterEdit::Validate()
331 if (m_pValidator)
333 CString text;
334 GetWindowText(text);
335 m_backColor = GetSysColor(COLOR_WINDOW);
336 if (!m_pValidator->Validate(text))
338 // Use a background color slightly shifted to red.
339 // We do this by increasing red component and decreasing green and blue.
340 const int SHIFT_PRECENTAGE = 10;
341 int r = GetRValue(m_backColor);
342 int g = GetGValue(m_backColor);
343 int b = GetBValue(m_backColor);
345 r = min(r * (100 + SHIFT_PRECENTAGE) / 100, 255);
346 // Ensure that there is at least some redness.
347 r = max(r, 255 * SHIFT_PRECENTAGE / 100);
348 g = g * (100 - SHIFT_PRECENTAGE) / 100;
349 b = b * (100 - SHIFT_PRECENTAGE) / 100;
350 m_backColor = RGB(r, g, b);
351 m_brBack.DeleteObject();
352 m_brBack.CreateSolidBrush(m_backColor);
357 void CFilterEdit::OnPaint()
359 LRESULT defres = Default();
361 DrawDimText();
362 if (defres)
364 // the Default() call did not process the WM_PAINT message!
365 // Validate the update region ourselves to avoid
366 // an endless loop repainting
367 CRect rc;
368 GetUpdateRect(&rc, FALSE);
369 if (!rc.IsRectEmpty())
370 ValidateRect(rc);
373 return;
376 void CFilterEdit::DrawDimText()
378 if (m_sCueBanner.IsEmpty())
379 return;
380 if (GetWindowTextLength())
381 return;
382 if (GetFocus() == this)
383 return;
385 CClientDC dcDraw(this);
386 int iState = dcDraw.SaveDC();
388 dcDraw.SelectObject((*GetFont()));
389 dcDraw.SetTextColor(GetSysColor(COLOR_GRAYTEXT));
390 dcDraw.SetBkColor(GetSysColor(COLOR_WINDOW));
391 dcDraw.DrawText(m_sCueBanner, m_sCueBanner.GetLength(), &m_rcEditArea, DT_CENTER | DT_VCENTER);
392 dcDraw.RestoreDC(iState);
393 return;
396 HICON CFilterEdit::LoadDpiScaledIcon(UINT resourceId, int cx96dpi, int cy96dpi)
398 int cx = CDPIAware::Instance().PointsToPixelsX(cx96dpi);
399 int cy = CDPIAware::Instance().PointsToPixelsY(cy96dpi);
401 return (HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(resourceId), IMAGE_ICON, cx, cy, LR_DEFAULTCOLOR);
404 void CFilterEdit::OnEnKillfocus()
406 InvalidateRect(nullptr);
409 void CFilterEdit::OnEnSetfocus()
411 InvalidateRect(nullptr);
414 LRESULT CFilterEdit::OnPaste(WPARAM, LPARAM)
416 if (OpenClipboard())
418 HANDLE hData = GetClipboardData (CF_TEXT);
419 CString toInsert((const char*)GlobalLock(hData));
420 GlobalUnlock(hData);
421 CloseClipboard();
423 // elimate control chars, especially newlines
424 toInsert.Replace(L'\t', L' ');
426 // only insert first line
427 toInsert.Replace(L'\r', L'\n');
428 int pos = 0;
429 toInsert = toInsert.Tokenize(L"\n", pos);
430 toInsert.Trim();
432 // get the current text
433 CString text;
434 GetWindowText(text);
436 // construct the new text
437 int from, to;
438 GetSel(from, to);
439 text.Delete(from, to - from);
440 text.Insert(from, toInsert);
441 from += toInsert.GetLength();
443 // update & notify controls
444 SetWindowText(text);
445 SetSel(from, from, FALSE);
446 SetModify(TRUE);
448 GetParent()->SendMessage(WM_COMMAND, MAKEWPARAM(GetDlgCtrlID(), EN_CHANGE), (LPARAM)GetSafeHwnd());
450 return 0;
453 void CFilterEdit::OnSysColorChange()
455 __super::OnSysColorChange();
456 m_backColor = GetSysColor(COLOR_WINDOW);
457 Invalidate();
460 ULONG CFilterEdit::GetGestureStatus(CPoint /*ptTouch*/)
462 return 0;