Fixed issue #438: Slow load Switch/Checkout dialog
[TortoiseGit.git] / src / Utils / MiscUI / HistoryCombo.cpp
blobc75310b0b4514bfdbcd7c89d22bb955fae444d0a
1 // TortoiseSVN - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2008 - TortoiseSVN
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 #include "stdafx.h"
20 #include "HistoryCombo.h"
21 #include "../registry.h"
23 #ifdef HISTORYCOMBO_WITH_SYSIMAGELIST
24 #include "../SysImageList.h"
25 #endif
27 #define MAX_HISTORY_ITEMS 25
29 CHistoryCombo::CHistoryCombo(BOOL bAllowSortStyle /*=FALSE*/ )
31 m_nMaxHistoryItems = MAX_HISTORY_ITEMS;
32 m_bAllowSortStyle = bAllowSortStyle;
33 m_bURLHistory = FALSE;
34 m_bPathHistory = FALSE;
35 m_hWndToolTip = NULL;
36 m_ttShown = FALSE;
37 m_bDyn = FALSE;
38 m_bWantReturn = FALSE;
41 CHistoryCombo::~CHistoryCombo()
45 BOOL CHistoryCombo::PreCreateWindow(CREATESTRUCT& cs)
47 if (!m_bAllowSortStyle) //turn off CBS_SORT style
48 cs.style &= ~CBS_SORT;
49 cs.style |= CBS_AUTOHSCROLL;
50 m_bDyn = TRUE;
51 return CComboBoxEx::PreCreateWindow(cs);
54 BOOL CHistoryCombo::PreTranslateMessage(MSG* pMsg)
57 if (pMsg->message == WM_KEYDOWN)
59 bool bShift = !!(GetKeyState(VK_SHIFT) & 0x8000);
60 int nVirtKey = (int) pMsg->wParam;
62 if (nVirtKey == VK_RETURN)
63 return OnReturnKeyPressed();
64 else if (nVirtKey == VK_DELETE && bShift && GetDroppedState() )
66 RemoveSelectedItem();
67 return TRUE;
70 if (pMsg->message == WM_MOUSEMOVE && this->m_bDyn )
72 if ((pMsg->wParam & MK_LBUTTON) == 0)
74 CPoint pt;
75 pt.x = LOWORD(pMsg->lParam);
76 pt.y = HIWORD(pMsg->lParam);
77 OnMouseMove(pMsg->wParam, pt);
78 return TRUE;
82 return CComboBoxEx::PreTranslateMessage(pMsg);
85 BEGIN_MESSAGE_MAP(CHistoryCombo, CComboBoxEx)
86 ON_WM_MOUSEMOVE()
87 ON_WM_TIMER()
88 ON_WM_CREATE()
89 END_MESSAGE_MAP()
91 int CHistoryCombo::AddString(CString str, INT_PTR pos,BOOL isSel)
93 if (str.IsEmpty())
94 return -1;
96 COMBOBOXEXITEM cbei;
97 SecureZeroMemory(&cbei, sizeof cbei);
98 cbei.mask = CBEIF_TEXT;
100 if (pos < 0)
101 cbei.iItem = GetCount();
102 else
103 cbei.iItem = pos;
105 str.Trim(_T(" "));
106 CString combostring = str;
107 combostring.Replace('\r', ' ');
108 combostring.Replace('\n', ' ');
109 cbei.pszText = const_cast<LPTSTR>(combostring.GetString());
111 #ifdef HISTORYCOMBO_WITH_SYSIMAGELIST
112 if (m_bURLHistory)
114 cbei.iImage = SYS_IMAGE_LIST().GetFileIconIndex(str);
115 if (cbei.iImage == SYS_IMAGE_LIST().GetDefaultIconIndex())
117 if (str.Left(5) == _T("http:"))
118 cbei.iImage = SYS_IMAGE_LIST().GetFileIconIndex(_T(".html"));
119 else if (str.Left(6) == _T("https:"))
120 cbei.iImage = SYS_IMAGE_LIST().GetFileIconIndex(_T(".shtml"));
121 else if (str.Left(5) == _T("file:"))
122 cbei.iImage = SYS_IMAGE_LIST().GetDirIconIndex();
123 else if (str.Left(4) == _T("git:"))
124 cbei.iImage = SYS_IMAGE_LIST().GetDirIconIndex();
125 else if (str.Left(4) == _T("ssh:"))
126 cbei.iImage = SYS_IMAGE_LIST().GetDirIconIndex();
127 else
128 cbei.iImage = SYS_IMAGE_LIST().GetDirIconIndex();
130 cbei.iSelectedImage = cbei.iImage;
131 cbei.mask |= CBEIF_IMAGE | CBEIF_SELECTEDIMAGE;
133 if (m_bPathHistory)
135 cbei.iImage = SYS_IMAGE_LIST().GetFileIconIndex(str);
136 if (cbei.iImage == SYS_IMAGE_LIST().GetDefaultIconIndex())
138 cbei.iImage = SYS_IMAGE_LIST().GetDirIconIndex();
140 cbei.iSelectedImage = cbei.iImage;
141 cbei.mask |= CBEIF_IMAGE | CBEIF_SELECTEDIMAGE;
143 #endif
144 int nRet = InsertItem(&cbei);
145 if (nRet >= 0)
146 m_arEntries.InsertAt(nRet, str);
148 //search the Combo for another string like this
149 //and delete it if one is found
150 str.Trim();
151 int nIndex = FindStringExact(0, combostring);
152 if (nIndex != -1 && nIndex != nRet)
154 DeleteItem(nIndex);
155 m_arEntries.RemoveAt(nIndex);
156 //nRet is now (potentially) invalid. Reset it.
157 nRet = FindStringExact(0, str);
160 //truncate list to m_nMaxHistoryItems
161 int nNumItems = GetCount();
162 for (int n = m_nMaxHistoryItems; n < nNumItems; n++)
164 DeleteItem(m_nMaxHistoryItems);
165 m_arEntries.RemoveAt(m_nMaxHistoryItems);
168 if(isSel)
169 SetCurSel(nRet);
170 return nRet;
173 CString CHistoryCombo::LoadHistory(LPCTSTR lpszSection, LPCTSTR lpszKeyPrefix)
175 if (lpszSection == NULL || lpszKeyPrefix == NULL || *lpszSection == '\0')
176 return _T("");
178 m_sSection = lpszSection;
179 m_sKeyPrefix = lpszKeyPrefix;
181 int n = 0;
182 CString sText;
185 //keys are of form <lpszKeyPrefix><entrynumber>
186 CString sKey;
187 sKey.Format(_T("%s\\%s%d"), (LPCTSTR)m_sSection, (LPCTSTR)m_sKeyPrefix, n++);
188 sText = CRegString(sKey);
189 if (!sText.IsEmpty())
190 AddString(sText);
191 } while (!sText.IsEmpty() && n < m_nMaxHistoryItems);
193 SetCurSel(-1);
195 ModifyStyleEx(WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE, 0);
197 // need to resize the control for correct display
198 CRect rect;
199 GetWindowRect(rect);
200 GetParent()->ScreenToClient(rect);
201 MoveWindow(rect.left, rect.top, rect.Width(),100);
203 return sText;
206 void CHistoryCombo::SaveHistory()
208 if (m_sSection.IsEmpty())
209 return;
211 //add the current item to the history
212 CString sCurItem;
213 GetWindowText(sCurItem);
214 sCurItem.Trim();
215 if (!sCurItem.IsEmpty())
216 AddString(sCurItem, 0);
217 //save history to registry/inifile
218 int nMax = min(GetCount(), m_nMaxHistoryItems + 1);
219 for (int n = 0; n < nMax; n++)
221 CString sKey;
222 sKey.Format(_T("%s\\%s%d"), (LPCTSTR)m_sSection, (LPCTSTR)m_sKeyPrefix, n);
223 CRegString regkey = CRegString(sKey);
224 regkey = m_arEntries.GetAt(n);
226 //remove items exceeding the max number of history items
227 for (int n = nMax; ; n++)
229 CString sKey;
230 sKey.Format(_T("%s\\%s%d"), (LPCTSTR)m_sSection, (LPCTSTR)m_sKeyPrefix, n);
231 CRegString regkey = CRegString(sKey);
232 CString sText = regkey;
233 if (sText.IsEmpty())
234 break;
235 regkey.removeValue(); // remove entry
239 void CHistoryCombo::ClearHistory(BOOL bDeleteRegistryEntries/*=TRUE*/)
241 ResetContent();
242 if (! m_sSection.IsEmpty() && bDeleteRegistryEntries)
244 //remove profile entries
245 CString sKey;
246 for (int n = 0; ; n++)
248 sKey.Format(_T("%s\\%s%d"), (LPCTSTR)m_sSection, (LPCTSTR)m_sKeyPrefix, n);
249 CRegString regkey = CRegString(sKey);
250 CString sText = regkey;
251 if (sText.IsEmpty())
252 break;
253 regkey.removeValue(); // remove entry
258 void CHistoryCombo::SetURLHistory(BOOL bURLHistory)
260 m_bURLHistory = bURLHistory;
262 if (m_bURLHistory)
264 HWND hwndEdit;
265 // use for ComboEx
266 hwndEdit = (HWND)::SendMessage(this->m_hWnd, CBEM_GETEDITCONTROL, 0, 0);
267 if (NULL == hwndEdit)
269 // Try the unofficial way of getting the edit control CWnd*
270 CWnd* pWnd = this->GetDlgItem(1001);
271 if(pWnd)
273 hwndEdit = pWnd->GetSafeHwnd();
276 if (hwndEdit)
277 SHAutoComplete(hwndEdit, SHACF_URLALL);
280 #ifdef HISTORYCOMBO_WITH_SYSIMAGELIST
281 SetImageList(&SYS_IMAGE_LIST());
282 #endif
285 void CHistoryCombo::SetPathHistory(BOOL bPathHistory)
287 m_bPathHistory = bPathHistory;
289 if (m_bPathHistory)
291 HWND hwndEdit;
292 // use for ComboEx
293 hwndEdit = (HWND)::SendMessage(this->m_hWnd, CBEM_GETEDITCONTROL, 0, 0);
294 if (NULL == hwndEdit)
296 //if not, try the old standby
297 if(hwndEdit==NULL)
299 CWnd* pWnd = this->GetDlgItem(1001);
300 if(pWnd)
302 hwndEdit = pWnd->GetSafeHwnd();
306 if (hwndEdit)
307 SHAutoComplete(hwndEdit, SHACF_FILESYSTEM);
310 #ifdef HISTORYCOMBO_WITH_SYSIMAGELIST
311 SetImageList(&SYS_IMAGE_LIST());
312 #endif
315 void CHistoryCombo::SetMaxHistoryItems(int nMaxItems)
317 m_nMaxHistoryItems = nMaxItems;
319 //truncate list to nMaxItems
320 int nNumItems = GetCount();
321 for (int n = m_nMaxHistoryItems; n < nNumItems; n++)
322 DeleteString(m_nMaxHistoryItems);
324 void CHistoryCombo::AddString(STRING_VECTOR &list,BOOL isSel)
326 for(unsigned int i=0;i<list.size();i++)
328 AddString(list[i], -1, isSel);
331 CString CHistoryCombo::GetString() const
333 CString str;
334 int sel;
335 sel = GetCurSel();
336 DWORD style=GetStyle();
338 if (sel == CB_ERR)
340 GetWindowText(str);
341 return str;
344 if ((m_bURLHistory)||(m_bPathHistory) || (!(style&CBS_SIMPLE)) )
346 //URL and path history combo boxes are editable, so get
347 //the string directly from the combobox
348 GetLBText(sel, str.GetBuffer(GetLBTextLen(sel)));
349 str.ReleaseBuffer();
350 return str;
352 return m_arEntries.GetAt(sel);
355 BOOL CHistoryCombo::RemoveSelectedItem()
357 int nIndex = GetCurSel();
358 if (nIndex == CB_ERR)
360 return FALSE;
363 DeleteItem(nIndex);
364 m_arEntries.RemoveAt(nIndex);
366 if ( nIndex < GetCount() )
368 // index stays the same to select the
369 // next item after the item which has
370 // just been deleted
372 else
374 // the end of the list has been reached
375 // so we select the previous item
376 nIndex--;
379 if ( nIndex == -1 )
381 // The one and only item has just been
382 // deleted -> reset window text since
383 // there is no item to select
384 SetWindowText(_T(""));
386 else
388 SetCurSel(nIndex);
391 // Since the dialog might be canceled we
392 // should now save the history. Before that
393 // set the selection to the first item so that
394 // the items will not be reordered and restore
395 // the selection after saving.
396 SetCurSel(0);
397 SaveHistory();
398 if ( nIndex != -1 )
400 SetCurSel(nIndex);
403 return TRUE;
406 void CHistoryCombo::PreSubclassWindow()
408 CComboBoxEx::PreSubclassWindow();
410 if (!m_bDyn)
411 CreateToolTip();
414 void CHistoryCombo::OnMouseMove(UINT nFlags, CPoint point)
416 CRect rectClient;
417 GetClientRect(&rectClient);
418 int nComboButtonWidth = ::GetSystemMetrics(SM_CXHTHUMB) + 2;
419 rectClient.right = rectClient.right - nComboButtonWidth;
421 if (rectClient.PtInRect(point))
423 ClientToScreen(&rectClient);
425 CString strText = GetString();
426 m_ToolInfo.lpszText = (LPTSTR)(LPCTSTR)strText;
428 HDC hDC = ::GetDC(m_hWnd);
430 CFont *pFont = GetFont();
431 HFONT hOldFont = (HFONT) ::SelectObject(hDC, (HFONT) *pFont);
433 SIZE size;
434 ::GetTextExtentPoint32(hDC, strText, strText.GetLength(), &size);
435 ::SelectObject(hDC, hOldFont);
436 ::ReleaseDC(m_hWnd, hDC);
438 if (size.cx > (rectClient.Width() - 6))
440 rectClient.left += 1;
441 rectClient.top += 3;
443 COLORREF rgbText = ::GetSysColor(COLOR_WINDOWTEXT);
444 COLORREF rgbBackground = ::GetSysColor(COLOR_WINDOW);
446 CWnd *pWnd = GetFocus();
447 if (pWnd)
449 if (pWnd->m_hWnd == m_hWnd)
451 rgbText = ::GetSysColor(COLOR_HIGHLIGHTTEXT);
452 rgbBackground = ::GetSysColor(COLOR_HIGHLIGHT);
456 if (!m_ttShown)
458 ::SendMessage(m_hWndToolTip, TTM_SETTIPBKCOLOR, rgbBackground, 0);
459 ::SendMessage(m_hWndToolTip, TTM_SETTIPTEXTCOLOR, rgbText, 0);
460 ::SendMessage(m_hWndToolTip, TTM_UPDATETIPTEXT, 0, (LPARAM) &m_ToolInfo);
461 ::SendMessage(m_hWndToolTip, TTM_TRACKPOSITION, 0, (LPARAM)MAKELONG(rectClient.left, rectClient.top));
462 ::SendMessage(m_hWndToolTip, TTM_TRACKACTIVATE, TRUE, (LPARAM)(LPTOOLINFO) &m_ToolInfo);
463 SetTimer(1, 80, NULL);
464 SetTimer(2, 2000, NULL);
465 m_ttShown = TRUE;
468 else
470 ::SendMessage(m_hWndToolTip, TTM_TRACKACTIVATE, FALSE, (LPARAM)(LPTOOLINFO) &m_ToolInfo);
471 m_ttShown = FALSE;
474 else
476 ::SendMessage(m_hWndToolTip, TTM_TRACKACTIVATE, FALSE, (LPARAM)(LPTOOLINFO) &m_ToolInfo);
477 m_ttShown = FALSE;
480 CComboBoxEx::OnMouseMove(nFlags, point);
483 void CHistoryCombo::OnTimer(UINT_PTR nIDEvent)
485 CPoint point;
486 DWORD ptW = GetMessagePos();
487 point.x = GET_X_LPARAM(ptW);
488 point.y = GET_Y_LPARAM(ptW);
489 ScreenToClient(&point);
491 CRect rectClient;
492 GetClientRect(&rectClient);
493 int nComboButtonWidth = ::GetSystemMetrics(SM_CXHTHUMB) + 2;
495 rectClient.right = rectClient.right - nComboButtonWidth;
497 if (!rectClient.PtInRect(point))
499 KillTimer(nIDEvent);
500 ::SendMessage(m_hWndToolTip, TTM_TRACKACTIVATE, FALSE, (LPARAM)(LPTOOLINFO) &m_ToolInfo);
501 m_ttShown = FALSE;
503 if (nIDEvent == 2)
505 // tooltip timeout, just deactivate it
506 ::SendMessage(m_hWndToolTip, TTM_TRACKACTIVATE, FALSE, (LPARAM)(LPTOOLINFO) &m_ToolInfo);
507 // don't set m_ttShown to FALSE, because we don't want the tooltip to show up again
508 // without the mouse pointer first leaving the control and entering it again
511 CComboBoxEx::OnTimer(nIDEvent);
514 void CHistoryCombo::CreateToolTip()
516 // create tooltip
517 m_hWndToolTip = ::CreateWindowEx(WS_EX_TOPMOST,
518 TOOLTIPS_CLASS,
519 NULL,
520 TTS_NOPREFIX | TTS_ALWAYSTIP,
521 CW_USEDEFAULT,
522 CW_USEDEFAULT,
523 CW_USEDEFAULT,
524 CW_USEDEFAULT,
525 m_hWnd,
526 NULL,
527 NULL,
528 NULL);
530 // initialize tool info struct
531 memset(&m_ToolInfo, 0, sizeof(m_ToolInfo));
532 m_ToolInfo.cbSize = sizeof(m_ToolInfo);
533 m_ToolInfo.uFlags = TTF_TRANSPARENT;
534 m_ToolInfo.hwnd = m_hWnd;
536 ::SendMessage(m_hWndToolTip, TTM_SETMAXTIPWIDTH, 0, SHRT_MAX);
537 ::SendMessage(m_hWndToolTip, TTM_ADDTOOL, 0, (LPARAM) (LPTOOLINFO) &m_ToolInfo);
538 ::SendMessage(m_hWndToolTip, TTM_SETTIPBKCOLOR, ::GetSysColor(COLOR_HIGHLIGHT), 0);
539 ::SendMessage(m_hWndToolTip, TTM_SETTIPTEXTCOLOR, ::GetSysColor(COLOR_HIGHLIGHTTEXT), 0);
541 CRect rectMargins(0,-1,0,-1);
542 ::SendMessage(m_hWndToolTip, TTM_SETMARGIN, 0, (LPARAM)&rectMargins);
544 CFont *pFont = GetFont();
545 ::SendMessage(m_hWndToolTip, WM_SETFONT, (WPARAM)(HFONT)*pFont, FALSE);
548 int CHistoryCombo::OnCreate(LPCREATESTRUCT lpCreateStruct)
550 if (CComboBoxEx::OnCreate(lpCreateStruct) == -1)
551 return -1;
553 if (m_bDyn)
554 CreateToolTip();
556 return 0;