Fixed issue #2507: Support keyboard shortcuts in yes/no prompts
[TortoiseGit.git] / src / Utils / MiscUI / HistoryCombo.cpp
blobf6f4dc83b070e051cdc9333f0d4c45cb7fbcd592
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2012 - TortoiseSVN
4 // Copyright (C) 2013-2014 - TortoiseGit
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 "HistoryCombo.h"
22 #include "../registry.h"
24 #ifdef HISTORYCOMBO_WITH_SYSIMAGELIST
25 #include "../SysImageList.h"
26 #endif
28 #define MAX_HISTORY_ITEMS 25
30 int CHistoryCombo::m_nGitIconIndex = 0;
32 CHistoryCombo::CHistoryCombo(BOOL bAllowSortStyle /*=FALSE*/ )
34 m_nMaxHistoryItems = MAX_HISTORY_ITEMS;
35 m_bAllowSortStyle = bAllowSortStyle;
36 m_bURLHistory = FALSE;
37 m_bPathHistory = FALSE;
38 m_hWndToolTip = NULL;
39 m_ttShown = FALSE;
40 m_bDyn = FALSE;
41 m_bWantReturn = FALSE;
42 m_bTrim = TRUE;
43 m_bCaseSensitive = FALSE;
44 SecureZeroMemory(&m_ToolInfo, sizeof(m_ToolInfo));
47 CHistoryCombo::~CHistoryCombo()
51 BOOL CHistoryCombo::PreCreateWindow(CREATESTRUCT& cs)
53 if (!m_bAllowSortStyle) //turn off CBS_SORT style
54 cs.style &= ~CBS_SORT;
55 cs.style |= CBS_AUTOHSCROLL;
56 m_bDyn = TRUE;
57 return CComboBoxEx::PreCreateWindow(cs);
60 BOOL CHistoryCombo::PreTranslateMessage(MSG* pMsg)
62 if (pMsg->message == WM_KEYDOWN)
64 bool bShift = !!(GetKeyState(VK_SHIFT) & 0x8000);
65 int nVirtKey = (int) pMsg->wParam;
67 if (nVirtKey == VK_RETURN)
68 return OnReturnKeyPressed();
69 else if (nVirtKey == VK_DELETE && bShift && GetDroppedState() )
71 RemoveSelectedItem();
72 return TRUE;
75 if (nVirtKey == 'A' && (GetKeyState(VK_CONTROL) & 0x8000 ) )
77 CEdit *edit = GetEditCtrl();
78 if (edit)
79 edit->SetSel(0, -1);
80 return TRUE;
83 else if (pMsg->message == WM_MOUSEMOVE && this->m_bDyn )
85 if ((pMsg->wParam & MK_LBUTTON) == 0)
87 CPoint pt;
88 pt.x = LOWORD(pMsg->lParam);
89 pt.y = HIWORD(pMsg->lParam);
90 OnMouseMove((UINT)pMsg->wParam, pt);
91 return TRUE;
94 else if ((pMsg->message == WM_MOUSEWHEEL || pMsg->message == WM_MOUSEHWHEEL) && !GetDroppedState())
96 return TRUE;
99 return CComboBoxEx::PreTranslateMessage(pMsg);
102 BEGIN_MESSAGE_MAP(CHistoryCombo, CComboBoxEx)
103 ON_WM_MOUSEMOVE()
104 ON_WM_TIMER()
105 ON_WM_CREATE()
106 END_MESSAGE_MAP()
108 int CHistoryCombo::AddString(const CString& str, INT_PTR pos /* = -1*/, BOOL isSel /* = TRUE */)
110 if (str.IsEmpty())
111 return -1;
113 if (pos < 0)
114 pos = GetCount();
116 CString combostring = str;
117 combostring.Replace('\r', ' ');
118 combostring.Replace('\n', ' ');
119 if (m_bTrim)
120 combostring.Trim();
121 if (combostring.IsEmpty())
122 return -1;
124 //search the Combo for another string like this
125 //and do not insert if found
126 int nIndex = m_bCaseSensitive ? FindStringExactCaseSensitive(-1, combostring) : FindStringExact(-1, combostring);
127 if (nIndex != -1)
129 if (nIndex > pos)
131 DeleteItem(nIndex);
132 m_arEntries.RemoveAt(nIndex);
134 else
136 if(isSel)
137 SetCurSel(nIndex);
138 return nIndex;
142 //truncate list to m_nMaxHistoryItems
143 int nNumItems = GetCount();
144 for (int n = m_nMaxHistoryItems; n < nNumItems; n++)
146 DeleteItem(m_nMaxHistoryItems);
147 m_arEntries.RemoveAt(m_nMaxHistoryItems);
150 int nRet = InsertEntry(combostring, pos);
152 if (isSel)
153 SetCurSel(nRet);
154 return nRet;
157 int CHistoryCombo::InsertEntry(const CString& combostring, INT_PTR pos)
159 COMBOBOXEXITEM cbei = { 0 };
160 cbei.mask = CBEIF_TEXT;
161 cbei.iItem = pos;
163 cbei.pszText = const_cast<LPTSTR>(combostring.GetString());
165 #ifdef HISTORYCOMBO_WITH_SYSIMAGELIST
166 if (m_bURLHistory)
168 cbei.iImage = SYS_IMAGE_LIST().GetFileIconIndex(combostring);
169 if (cbei.iImage == 0 || cbei.iImage == SYS_IMAGE_LIST().GetDefaultIconIndex())
171 if (combostring.Left(5) == _T("http:"))
172 cbei.iImage = SYS_IMAGE_LIST().GetFileIconIndex(_T(".html"));
173 else if (combostring.Left(6) == _T("https:"))
174 cbei.iImage = SYS_IMAGE_LIST().GetFileIconIndex(_T(".html"));
175 else if (combostring.Left(5) == _T("file:"))
176 cbei.iImage = SYS_IMAGE_LIST().GetDirIconIndex();
177 else if (combostring.Left(4) == _T("git:"))
178 cbei.iImage = m_nGitIconIndex;
179 else if (combostring.Left(4) == _T("ssh:"))
180 cbei.iImage = m_nGitIconIndex;
181 else
182 cbei.iImage = SYS_IMAGE_LIST().GetDirIconIndex();
184 cbei.iSelectedImage = cbei.iImage;
185 cbei.mask |= CBEIF_IMAGE | CBEIF_SELECTEDIMAGE;
187 if (m_bPathHistory)
189 cbei.iImage = SYS_IMAGE_LIST().GetFileIconIndex(combostring);
190 if (cbei.iImage == SYS_IMAGE_LIST().GetDefaultIconIndex())
192 cbei.iImage = SYS_IMAGE_LIST().GetDirIconIndex();
194 cbei.iSelectedImage = cbei.iImage;
195 cbei.mask |= CBEIF_IMAGE | CBEIF_SELECTEDIMAGE;
197 #endif
199 int nRet = InsertItem(&cbei);
200 if (nRet >= 0)
201 m_arEntries.InsertAt(nRet, combostring);
203 return nRet;
206 void CHistoryCombo::SetList(const STRING_VECTOR& list)
208 Reset();
209 for (int i = 0; i < list.size(); ++i)
211 CString combostring = list[i];
212 combostring.Replace('\r', ' ');
213 combostring.Replace('\n', ' ');
214 if (m_bTrim)
215 combostring.Trim();
216 if (combostring.IsEmpty())
217 continue;
219 InsertEntry(combostring, i);
223 CString CHistoryCombo::LoadHistory(LPCTSTR lpszSection, LPCTSTR lpszKeyPrefix)
225 if (lpszSection == NULL || lpszKeyPrefix == NULL || *lpszSection == '\0')
226 return _T("");
228 m_sSection = lpszSection;
229 m_sKeyPrefix = lpszKeyPrefix;
231 int n = 0;
232 CString sText;
235 //keys are of form <lpszKeyPrefix><entrynumber>
236 CString sKey;
237 sKey.Format(_T("%s\\%s%d"), (LPCTSTR)m_sSection, (LPCTSTR)m_sKeyPrefix, n++);
238 sText = CRegString(sKey);
239 if (!sText.IsEmpty())
240 AddString(sText);
241 } while (!sText.IsEmpty() && n < m_nMaxHistoryItems);
243 SetCurSel(-1);
245 ModifyStyleEx(WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE, 0);
247 // need to resize the control for correct display
248 CRect rect;
249 GetWindowRect(rect);
250 GetParent()->ScreenToClient(rect);
251 MoveWindow(rect.left, rect.top, rect.Width(),100);
253 return sText;
256 void CHistoryCombo::SaveHistory()
258 if (m_sSection.IsEmpty())
259 return;
261 //add the current item to the history
262 CString sCurItem;
263 GetWindowText(sCurItem);
264 if (m_bTrim)
265 sCurItem.Trim();
266 if (!sCurItem.IsEmpty())
267 AddString(sCurItem, 0);
268 //save history to registry/inifile
269 int nMax = min(GetCount(), m_nMaxHistoryItems + 1);
270 for (int n = 0; n < nMax; n++)
272 CString sKey;
273 sKey.Format(_T("%s\\%s%d"), (LPCTSTR)m_sSection, (LPCTSTR)m_sKeyPrefix, n);
274 CRegString regkey(sKey);
275 regkey = m_arEntries.GetAt(n);
277 //remove items exceeding the max number of history items
278 for (int n = nMax; ; n++)
280 CString sKey;
281 sKey.Format(_T("%s\\%s%d"), (LPCTSTR)m_sSection, (LPCTSTR)m_sKeyPrefix, n);
282 CRegString regkey(sKey);
283 CString sText = regkey;
284 if (sText.IsEmpty())
285 break;
286 regkey.removeValue(); // remove entry
290 void CHistoryCombo::ClearHistory(BOOL bDeleteRegistryEntries/*=TRUE*/)
292 ResetContent();
293 if (! m_sSection.IsEmpty() && bDeleteRegistryEntries)
295 //remove profile entries
296 CString sKey;
297 for (int n = 0; ; n++)
299 sKey.Format(_T("%s\\%s%d"), (LPCTSTR)m_sSection, (LPCTSTR)m_sKeyPrefix, n);
300 CRegString regkey(sKey);
301 CString sText = regkey;
302 if (sText.IsEmpty())
303 break;
304 regkey.removeValue(); // remove entry
309 void CHistoryCombo::RemoveEntryFromHistory(LPCTSTR lpszSection, LPCTSTR lpszKeyPrefix, const CString& entryToRemove)
311 if (entryToRemove.IsEmpty())
312 return;
313 CString sText;
314 bool found = false;
315 int n = -1;
318 CString sKey;
319 sKey.Format(_T("%s\\%s%d"), lpszSection, lpszKeyPrefix, ++n);
320 CRegString regkey(sKey);
321 sText = regkey;
322 if (sText == entryToRemove)
324 regkey.removeValue();
325 found = true;
326 ++n;
327 break;
329 } while (!sText.IsEmpty());
330 if (!found)
331 return;
332 for (;; ++n)
334 CString sKey;
335 sKey.Format(_T("%s\\%s%d"), lpszSection, lpszKeyPrefix, n);
336 CRegString regkey(sKey);
337 sText = regkey;
338 if (!sText.IsEmpty())
340 CString sKeyNew;
341 sKeyNew.Format(_T("%s\\%s%d"), lpszSection, lpszKeyPrefix, n - 1);
342 CRegString regkeyNew(sKeyNew);
343 regkeyNew = sText;
344 regkey.removeValue();
345 continue;
347 else
348 break;
352 void CHistoryCombo::SetURLHistory(BOOL bURLHistory)
354 m_bURLHistory = bURLHistory;
356 if (m_bURLHistory)
358 HWND hwndEdit;
359 // use for ComboEx
360 hwndEdit = (HWND)::SendMessage(this->m_hWnd, CBEM_GETEDITCONTROL, 0, 0);
361 if (NULL == hwndEdit)
363 // Try the unofficial way of getting the edit control CWnd*
364 CWnd* pWnd = this->GetDlgItem(1001);
365 if(pWnd)
367 hwndEdit = pWnd->GetSafeHwnd();
370 if (hwndEdit)
371 SHAutoComplete(hwndEdit, SHACF_URLALL);
374 #ifdef HISTORYCOMBO_WITH_SYSIMAGELIST
375 SetImageList(&SYS_IMAGE_LIST());
376 #endif
379 void CHistoryCombo::SetPathHistory(BOOL bPathHistory)
381 m_bPathHistory = bPathHistory;
383 if (m_bPathHistory)
385 HWND hwndEdit;
386 // use for ComboEx
387 hwndEdit = (HWND)::SendMessage(this->m_hWnd, CBEM_GETEDITCONTROL, 0, 0);
388 if (NULL == hwndEdit)
390 //if not, try the old standby
391 if(hwndEdit==NULL)
393 CWnd* pWnd = this->GetDlgItem(1001);
394 if(pWnd)
396 hwndEdit = pWnd->GetSafeHwnd();
400 if (hwndEdit)
401 SHAutoComplete(hwndEdit, SHACF_FILESYSTEM);
404 #ifdef HISTORYCOMBO_WITH_SYSIMAGELIST
405 SetImageList(&SYS_IMAGE_LIST());
406 #endif
409 void CHistoryCombo::SetMaxHistoryItems(int nMaxItems)
411 m_nMaxHistoryItems = nMaxItems;
413 //truncate list to nMaxItems
414 int nNumItems = GetCount();
415 for (int n = m_nMaxHistoryItems; n < nNumItems; n++)
416 DeleteString(m_nMaxHistoryItems);
419 CString CHistoryCombo::GetString() const
421 CString str;
422 int sel;
423 sel = GetCurSel();
424 DWORD style=GetStyle();
426 if (sel == CB_ERR ||(m_bURLHistory)||(m_bPathHistory) || (!(style&CBS_SIMPLE)))
428 GetWindowText(str);
429 return str;
432 return m_arEntries.GetAt(sel);
435 BOOL CHistoryCombo::RemoveSelectedItem()
437 int nIndex = GetCurSel();
438 if (nIndex == CB_ERR)
440 return FALSE;
443 DeleteItem(nIndex);
444 m_arEntries.RemoveAt(nIndex);
446 if ( nIndex < GetCount() )
448 // index stays the same to select the
449 // next item after the item which has
450 // just been deleted
452 else
454 // the end of the list has been reached
455 // so we select the previous item
456 nIndex--;
459 if ( nIndex == -1 )
461 // The one and only item has just been
462 // deleted -> reset window text since
463 // there is no item to select
464 SetWindowText(_T(""));
466 else
468 SetCurSel(nIndex);
471 // Since the dialog might be canceled we
472 // should now save the history. Before that
473 // set the selection to the first item so that
474 // the items will not be reordered and restore
475 // the selection after saving.
476 SetCurSel(0);
477 SaveHistory();
478 if ( nIndex != -1 )
480 SetCurSel(nIndex);
483 return TRUE;
486 void CHistoryCombo::PreSubclassWindow()
488 CComboBoxEx::PreSubclassWindow();
490 if (!m_bDyn)
491 CreateToolTip();
494 void CHistoryCombo::OnMouseMove(UINT nFlags, CPoint point)
496 CRect rectClient;
497 GetClientRect(&rectClient);
498 int nComboButtonWidth = ::GetSystemMetrics(SM_CXHTHUMB) + 2;
499 rectClient.right = rectClient.right - nComboButtonWidth;
501 if (rectClient.PtInRect(point))
503 ClientToScreen(&rectClient);
505 m_ToolText = GetString();
506 m_ToolInfo.lpszText = (LPTSTR)(LPCTSTR)m_ToolText;
508 HDC hDC = ::GetDC(m_hWnd);
510 CFont *pFont = GetFont();
511 HFONT hOldFont = (HFONT) ::SelectObject(hDC, (HFONT) *pFont);
513 SIZE size;
514 ::GetTextExtentPoint32(hDC, m_ToolText, m_ToolText.GetLength(), &size);
515 ::SelectObject(hDC, hOldFont);
516 ::ReleaseDC(m_hWnd, hDC);
518 if (size.cx > (rectClient.Width() - 6))
520 rectClient.left += 1;
521 rectClient.top += 3;
523 COLORREF rgbText = ::GetSysColor(COLOR_WINDOWTEXT);
524 COLORREF rgbBackground = ::GetSysColor(COLOR_WINDOW);
526 CWnd *pWnd = GetFocus();
527 if (pWnd)
529 if (pWnd->m_hWnd == m_hWnd)
531 rgbText = ::GetSysColor(COLOR_HIGHLIGHTTEXT);
532 rgbBackground = ::GetSysColor(COLOR_HIGHLIGHT);
536 if (!m_ttShown)
538 ::SendMessage(m_hWndToolTip, TTM_SETTIPBKCOLOR, rgbBackground, 0);
539 ::SendMessage(m_hWndToolTip, TTM_SETTIPTEXTCOLOR, rgbText, 0);
540 ::SendMessage(m_hWndToolTip, TTM_UPDATETIPTEXT, 0, (LPARAM) &m_ToolInfo);
541 ::SendMessage(m_hWndToolTip, TTM_TRACKPOSITION, 0, (LPARAM)MAKELONG(rectClient.left, rectClient.top));
542 ::SendMessage(m_hWndToolTip, TTM_TRACKACTIVATE, TRUE, (LPARAM)(LPTOOLINFO) &m_ToolInfo);
543 SetTimer(1, 80, NULL);
544 SetTimer(2, 2000, NULL);
545 m_ttShown = TRUE;
548 else
550 ::SendMessage(m_hWndToolTip, TTM_TRACKACTIVATE, FALSE, (LPARAM)(LPTOOLINFO) &m_ToolInfo);
551 m_ttShown = FALSE;
554 else
556 ::SendMessage(m_hWndToolTip, TTM_TRACKACTIVATE, FALSE, (LPARAM)(LPTOOLINFO) &m_ToolInfo);
557 m_ttShown = FALSE;
560 CComboBoxEx::OnMouseMove(nFlags, point);
563 void CHistoryCombo::OnTimer(UINT_PTR nIDEvent)
565 CPoint point;
566 DWORD ptW = GetMessagePos();
567 point.x = GET_X_LPARAM(ptW);
568 point.y = GET_Y_LPARAM(ptW);
569 ScreenToClient(&point);
571 CRect rectClient;
572 GetClientRect(&rectClient);
573 int nComboButtonWidth = ::GetSystemMetrics(SM_CXHTHUMB) + 2;
575 rectClient.right = rectClient.right - nComboButtonWidth;
577 if (!rectClient.PtInRect(point))
579 KillTimer(nIDEvent);
580 ::SendMessage(m_hWndToolTip, TTM_TRACKACTIVATE, FALSE, (LPARAM)(LPTOOLINFO) &m_ToolInfo);
581 m_ttShown = FALSE;
583 if (nIDEvent == 2)
585 // tooltip timeout, just deactivate it
586 ::SendMessage(m_hWndToolTip, TTM_TRACKACTIVATE, FALSE, (LPARAM)(LPTOOLINFO) &m_ToolInfo);
587 // don't set m_ttShown to FALSE, because we don't want the tooltip to show up again
588 // without the mouse pointer first leaving the control and entering it again
591 CComboBoxEx::OnTimer(nIDEvent);
594 void CHistoryCombo::CreateToolTip()
596 // create tooltip
597 m_hWndToolTip = ::CreateWindowEx(WS_EX_TOPMOST,
598 TOOLTIPS_CLASS,
599 NULL,
600 TTS_NOPREFIX | TTS_ALWAYSTIP,
601 CW_USEDEFAULT,
602 CW_USEDEFAULT,
603 CW_USEDEFAULT,
604 CW_USEDEFAULT,
605 m_hWnd,
606 NULL,
607 NULL,
608 NULL);
610 // initialize tool info struct
611 memset(&m_ToolInfo, 0, sizeof(m_ToolInfo));
612 m_ToolInfo.cbSize = sizeof(m_ToolInfo);
613 m_ToolInfo.uFlags = TTF_TRANSPARENT;
614 m_ToolInfo.hwnd = m_hWnd;
616 ::SendMessage(m_hWndToolTip, TTM_SETMAXTIPWIDTH, 0, SHRT_MAX);
617 ::SendMessage(m_hWndToolTip, TTM_ADDTOOL, 0, (LPARAM) (LPTOOLINFO) &m_ToolInfo);
618 ::SendMessage(m_hWndToolTip, TTM_SETTIPBKCOLOR, ::GetSysColor(COLOR_HIGHLIGHT), 0);
619 ::SendMessage(m_hWndToolTip, TTM_SETTIPTEXTCOLOR, ::GetSysColor(COLOR_HIGHLIGHTTEXT), 0);
621 CRect rectMargins(0,-1,0,-1);
622 ::SendMessage(m_hWndToolTip, TTM_SETMARGIN, 0, (LPARAM)&rectMargins);
624 CFont *pFont = GetFont();
625 ::SendMessage(m_hWndToolTip, WM_SETFONT, (WPARAM)(HFONT)*pFont, FALSE);
628 int CHistoryCombo::OnCreate(LPCREATESTRUCT lpCreateStruct)
630 if (CComboBoxEx::OnCreate(lpCreateStruct) == -1)
631 return -1;
633 if (m_bDyn)
634 CreateToolTip();
636 return 0;
639 int CHistoryCombo::FindStringExactCaseSensitive(int nIndexStart, LPCTSTR lpszFind)
641 nIndexStart = max(0, nIndexStart);
642 for (int i = nIndexStart; i < GetCount(); i++)
644 CString exactString;
645 GetLBText(i, exactString);
646 if (exactString == lpszFind)
648 return i;
651 return -1;