Fix history combobox show twice item
[TortoiseGit.git] / src / Utils / MiscUI / HistoryCombo.cpp
bloba519402232ce5f6a0eef1c8d80fa1f6ad8f7162f
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 (nVirtKey == 'A' && (GetKeyState(VK_CONTROL) & 0x8000 ) )
72 GetEditCtrl()->SetSel(0, -1);
73 return TRUE;
76 if (pMsg->message == WM_MOUSEMOVE && this->m_bDyn )
78 if ((pMsg->wParam & MK_LBUTTON) == 0)
80 CPoint pt;
81 pt.x = LOWORD(pMsg->lParam);
82 pt.y = HIWORD(pMsg->lParam);
83 OnMouseMove(pMsg->wParam, pt);
84 return TRUE;
88 return CComboBoxEx::PreTranslateMessage(pMsg);
91 BEGIN_MESSAGE_MAP(CHistoryCombo, CComboBoxEx)
92 ON_WM_MOUSEMOVE()
93 ON_WM_TIMER()
94 ON_WM_CREATE()
95 END_MESSAGE_MAP()
97 int CHistoryCombo::AddString(CString str, INT_PTR pos,BOOL isSel)
99 if (str.IsEmpty())
100 return -1;
102 COMBOBOXEXITEM cbei;
103 SecureZeroMemory(&cbei, sizeof cbei);
104 cbei.mask = CBEIF_TEXT;
106 if (pos < 0)
107 cbei.iItem = GetCount();
108 else
109 cbei.iItem = pos;
111 str.Trim(_T(" "));
112 CString combostring = str;
113 combostring.Replace('\r', ' ');
114 combostring.Replace('\n', ' ');
115 str=combostring=combostring.Trim();
116 cbei.pszText = const_cast<LPTSTR>(combostring.GetString());
118 #ifdef HISTORYCOMBO_WITH_SYSIMAGELIST
119 if (m_bURLHistory)
121 cbei.iImage = SYS_IMAGE_LIST().GetFileIconIndex(str);
122 if (cbei.iImage == SYS_IMAGE_LIST().GetDefaultIconIndex())
124 if (str.Left(5) == _T("http:"))
125 cbei.iImage = SYS_IMAGE_LIST().GetFileIconIndex(_T(".html"));
126 else if (str.Left(6) == _T("https:"))
127 cbei.iImage = SYS_IMAGE_LIST().GetFileIconIndex(_T(".shtml"));
128 else if (str.Left(5) == _T("file:"))
129 cbei.iImage = SYS_IMAGE_LIST().GetDirIconIndex();
130 else if (str.Left(4) == _T("git:"))
131 cbei.iImage = SYS_IMAGE_LIST().GetDirIconIndex();
132 else if (str.Left(4) == _T("ssh:"))
133 cbei.iImage = SYS_IMAGE_LIST().GetDirIconIndex();
134 else
135 cbei.iImage = SYS_IMAGE_LIST().GetDirIconIndex();
137 cbei.iSelectedImage = cbei.iImage;
138 cbei.mask |= CBEIF_IMAGE | CBEIF_SELECTEDIMAGE;
140 if (m_bPathHistory)
142 cbei.iImage = SYS_IMAGE_LIST().GetFileIconIndex(str);
143 if (cbei.iImage == SYS_IMAGE_LIST().GetDefaultIconIndex())
145 cbei.iImage = SYS_IMAGE_LIST().GetDirIconIndex();
147 cbei.iSelectedImage = cbei.iImage;
148 cbei.mask |= CBEIF_IMAGE | CBEIF_SELECTEDIMAGE;
150 #endif
151 int nRet = InsertItem(&cbei);
152 if (nRet >= 0)
153 m_arEntries.InsertAt(nRet, str);
155 //search the Combo for another string like this
156 //and delete it if one is found
157 str.Trim();
158 int nIndex = FindStringExact(-1, combostring);
159 if (nIndex != -1 && nIndex != nRet)
161 DeleteItem(nIndex);
162 m_arEntries.RemoveAt(nIndex);
163 //nRet is now (potentially) invalid. Reset it.
164 nRet = FindStringExact(-1, str);
167 //truncate list to m_nMaxHistoryItems
168 int nNumItems = GetCount();
169 for (int n = m_nMaxHistoryItems; n < nNumItems; n++)
171 DeleteItem(m_nMaxHistoryItems);
172 m_arEntries.RemoveAt(m_nMaxHistoryItems);
175 if(isSel)
176 SetCurSel(nRet);
177 return nRet;
180 CString CHistoryCombo::LoadHistory(LPCTSTR lpszSection, LPCTSTR lpszKeyPrefix)
182 if (lpszSection == NULL || lpszKeyPrefix == NULL || *lpszSection == '\0')
183 return _T("");
185 m_sSection = lpszSection;
186 m_sKeyPrefix = lpszKeyPrefix;
188 int n = 0;
189 CString sText;
192 //keys are of form <lpszKeyPrefix><entrynumber>
193 CString sKey;
194 sKey.Format(_T("%s\\%s%d"), (LPCTSTR)m_sSection, (LPCTSTR)m_sKeyPrefix, n++);
195 sText = CRegString(sKey);
196 if (!sText.IsEmpty())
197 AddString(sText);
198 } while (!sText.IsEmpty() && n < m_nMaxHistoryItems);
200 SetCurSel(-1);
202 ModifyStyleEx(WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE, 0);
204 // need to resize the control for correct display
205 CRect rect;
206 GetWindowRect(rect);
207 GetParent()->ScreenToClient(rect);
208 MoveWindow(rect.left, rect.top, rect.Width(),100);
210 return sText;
213 void CHistoryCombo::SaveHistory()
215 if (m_sSection.IsEmpty())
216 return;
218 //add the current item to the history
219 CString sCurItem;
220 GetWindowText(sCurItem);
221 sCurItem.Trim();
222 if (!sCurItem.IsEmpty())
223 AddString(sCurItem, 0);
224 //save history to registry/inifile
225 int nMax = min(GetCount(), m_nMaxHistoryItems + 1);
226 for (int n = 0; n < nMax; n++)
228 CString sKey;
229 sKey.Format(_T("%s\\%s%d"), (LPCTSTR)m_sSection, (LPCTSTR)m_sKeyPrefix, n);
230 CRegString regkey = CRegString(sKey);
231 regkey = m_arEntries.GetAt(n);
233 //remove items exceeding the max number of history items
234 for (int n = nMax; ; n++)
236 CString sKey;
237 sKey.Format(_T("%s\\%s%d"), (LPCTSTR)m_sSection, (LPCTSTR)m_sKeyPrefix, n);
238 CRegString regkey = CRegString(sKey);
239 CString sText = regkey;
240 if (sText.IsEmpty())
241 break;
242 regkey.removeValue(); // remove entry
246 void CHistoryCombo::ClearHistory(BOOL bDeleteRegistryEntries/*=TRUE*/)
248 ResetContent();
249 if (! m_sSection.IsEmpty() && bDeleteRegistryEntries)
251 //remove profile entries
252 CString sKey;
253 for (int n = 0; ; n++)
255 sKey.Format(_T("%s\\%s%d"), (LPCTSTR)m_sSection, (LPCTSTR)m_sKeyPrefix, n);
256 CRegString regkey = CRegString(sKey);
257 CString sText = regkey;
258 if (sText.IsEmpty())
259 break;
260 regkey.removeValue(); // remove entry
265 void CHistoryCombo::SetURLHistory(BOOL bURLHistory)
267 m_bURLHistory = bURLHistory;
269 if (m_bURLHistory)
271 HWND hwndEdit;
272 // use for ComboEx
273 hwndEdit = (HWND)::SendMessage(this->m_hWnd, CBEM_GETEDITCONTROL, 0, 0);
274 if (NULL == hwndEdit)
276 // Try the unofficial way of getting the edit control CWnd*
277 CWnd* pWnd = this->GetDlgItem(1001);
278 if(pWnd)
280 hwndEdit = pWnd->GetSafeHwnd();
283 if (hwndEdit)
284 SHAutoComplete(hwndEdit, SHACF_URLALL);
287 #ifdef HISTORYCOMBO_WITH_SYSIMAGELIST
288 SetImageList(&SYS_IMAGE_LIST());
289 #endif
292 void CHistoryCombo::SetPathHistory(BOOL bPathHistory)
294 m_bPathHistory = bPathHistory;
296 if (m_bPathHistory)
298 HWND hwndEdit;
299 // use for ComboEx
300 hwndEdit = (HWND)::SendMessage(this->m_hWnd, CBEM_GETEDITCONTROL, 0, 0);
301 if (NULL == hwndEdit)
303 //if not, try the old standby
304 if(hwndEdit==NULL)
306 CWnd* pWnd = this->GetDlgItem(1001);
307 if(pWnd)
309 hwndEdit = pWnd->GetSafeHwnd();
313 if (hwndEdit)
314 SHAutoComplete(hwndEdit, SHACF_FILESYSTEM);
317 #ifdef HISTORYCOMBO_WITH_SYSIMAGELIST
318 SetImageList(&SYS_IMAGE_LIST());
319 #endif
322 void CHistoryCombo::SetMaxHistoryItems(int nMaxItems)
324 m_nMaxHistoryItems = nMaxItems;
326 //truncate list to nMaxItems
327 int nNumItems = GetCount();
328 for (int n = m_nMaxHistoryItems; n < nNumItems; n++)
329 DeleteString(m_nMaxHistoryItems);
331 void CHistoryCombo::AddString(STRING_VECTOR &list,BOOL isSel)
333 for(unsigned int i=0;i<list.size();i++)
335 AddString(list[i], -1, isSel);
338 CString CHistoryCombo::GetString() const
340 CString str;
341 int sel;
342 sel = GetCurSel();
343 DWORD style=GetStyle();
345 if (sel == CB_ERR)
347 GetWindowText(str);
348 return str;
351 if ((m_bURLHistory)||(m_bPathHistory) || (!(style&CBS_SIMPLE)) )
353 //URL and path history combo boxes are editable, so get
354 //the string directly from the combobox
355 GetLBText(sel, str.GetBuffer(GetLBTextLen(sel)));
356 str.ReleaseBuffer();
357 return str;
359 return m_arEntries.GetAt(sel);
362 BOOL CHistoryCombo::RemoveSelectedItem()
364 int nIndex = GetCurSel();
365 if (nIndex == CB_ERR)
367 return FALSE;
370 DeleteItem(nIndex);
371 m_arEntries.RemoveAt(nIndex);
373 if ( nIndex < GetCount() )
375 // index stays the same to select the
376 // next item after the item which has
377 // just been deleted
379 else
381 // the end of the list has been reached
382 // so we select the previous item
383 nIndex--;
386 if ( nIndex == -1 )
388 // The one and only item has just been
389 // deleted -> reset window text since
390 // there is no item to select
391 SetWindowText(_T(""));
393 else
395 SetCurSel(nIndex);
398 // Since the dialog might be canceled we
399 // should now save the history. Before that
400 // set the selection to the first item so that
401 // the items will not be reordered and restore
402 // the selection after saving.
403 SetCurSel(0);
404 SaveHistory();
405 if ( nIndex != -1 )
407 SetCurSel(nIndex);
410 return TRUE;
413 void CHistoryCombo::PreSubclassWindow()
415 CComboBoxEx::PreSubclassWindow();
417 if (!m_bDyn)
418 CreateToolTip();
421 void CHistoryCombo::OnMouseMove(UINT nFlags, CPoint point)
423 CRect rectClient;
424 GetClientRect(&rectClient);
425 int nComboButtonWidth = ::GetSystemMetrics(SM_CXHTHUMB) + 2;
426 rectClient.right = rectClient.right - nComboButtonWidth;
428 if (rectClient.PtInRect(point))
430 ClientToScreen(&rectClient);
432 CString strText = GetString();
433 m_ToolInfo.lpszText = (LPTSTR)(LPCTSTR)strText;
435 HDC hDC = ::GetDC(m_hWnd);
437 CFont *pFont = GetFont();
438 HFONT hOldFont = (HFONT) ::SelectObject(hDC, (HFONT) *pFont);
440 SIZE size;
441 ::GetTextExtentPoint32(hDC, strText, strText.GetLength(), &size);
442 ::SelectObject(hDC, hOldFont);
443 ::ReleaseDC(m_hWnd, hDC);
445 if (size.cx > (rectClient.Width() - 6))
447 rectClient.left += 1;
448 rectClient.top += 3;
450 COLORREF rgbText = ::GetSysColor(COLOR_WINDOWTEXT);
451 COLORREF rgbBackground = ::GetSysColor(COLOR_WINDOW);
453 CWnd *pWnd = GetFocus();
454 if (pWnd)
456 if (pWnd->m_hWnd == m_hWnd)
458 rgbText = ::GetSysColor(COLOR_HIGHLIGHTTEXT);
459 rgbBackground = ::GetSysColor(COLOR_HIGHLIGHT);
463 if (!m_ttShown)
465 ::SendMessage(m_hWndToolTip, TTM_SETTIPBKCOLOR, rgbBackground, 0);
466 ::SendMessage(m_hWndToolTip, TTM_SETTIPTEXTCOLOR, rgbText, 0);
467 ::SendMessage(m_hWndToolTip, TTM_UPDATETIPTEXT, 0, (LPARAM) &m_ToolInfo);
468 ::SendMessage(m_hWndToolTip, TTM_TRACKPOSITION, 0, (LPARAM)MAKELONG(rectClient.left, rectClient.top));
469 ::SendMessage(m_hWndToolTip, TTM_TRACKACTIVATE, TRUE, (LPARAM)(LPTOOLINFO) &m_ToolInfo);
470 SetTimer(1, 80, NULL);
471 SetTimer(2, 2000, NULL);
472 m_ttShown = TRUE;
475 else
477 ::SendMessage(m_hWndToolTip, TTM_TRACKACTIVATE, FALSE, (LPARAM)(LPTOOLINFO) &m_ToolInfo);
478 m_ttShown = FALSE;
481 else
483 ::SendMessage(m_hWndToolTip, TTM_TRACKACTIVATE, FALSE, (LPARAM)(LPTOOLINFO) &m_ToolInfo);
484 m_ttShown = FALSE;
487 CComboBoxEx::OnMouseMove(nFlags, point);
490 void CHistoryCombo::OnTimer(UINT_PTR nIDEvent)
492 CPoint point;
493 DWORD ptW = GetMessagePos();
494 point.x = GET_X_LPARAM(ptW);
495 point.y = GET_Y_LPARAM(ptW);
496 ScreenToClient(&point);
498 CRect rectClient;
499 GetClientRect(&rectClient);
500 int nComboButtonWidth = ::GetSystemMetrics(SM_CXHTHUMB) + 2;
502 rectClient.right = rectClient.right - nComboButtonWidth;
504 if (!rectClient.PtInRect(point))
506 KillTimer(nIDEvent);
507 ::SendMessage(m_hWndToolTip, TTM_TRACKACTIVATE, FALSE, (LPARAM)(LPTOOLINFO) &m_ToolInfo);
508 m_ttShown = FALSE;
510 if (nIDEvent == 2)
512 // tooltip timeout, just deactivate it
513 ::SendMessage(m_hWndToolTip, TTM_TRACKACTIVATE, FALSE, (LPARAM)(LPTOOLINFO) &m_ToolInfo);
514 // don't set m_ttShown to FALSE, because we don't want the tooltip to show up again
515 // without the mouse pointer first leaving the control and entering it again
518 CComboBoxEx::OnTimer(nIDEvent);
521 void CHistoryCombo::CreateToolTip()
523 // create tooltip
524 m_hWndToolTip = ::CreateWindowEx(WS_EX_TOPMOST,
525 TOOLTIPS_CLASS,
526 NULL,
527 TTS_NOPREFIX | TTS_ALWAYSTIP,
528 CW_USEDEFAULT,
529 CW_USEDEFAULT,
530 CW_USEDEFAULT,
531 CW_USEDEFAULT,
532 m_hWnd,
533 NULL,
534 NULL,
535 NULL);
537 // initialize tool info struct
538 memset(&m_ToolInfo, 0, sizeof(m_ToolInfo));
539 m_ToolInfo.cbSize = sizeof(m_ToolInfo);
540 m_ToolInfo.uFlags = TTF_TRANSPARENT;
541 m_ToolInfo.hwnd = m_hWnd;
543 ::SendMessage(m_hWndToolTip, TTM_SETMAXTIPWIDTH, 0, SHRT_MAX);
544 ::SendMessage(m_hWndToolTip, TTM_ADDTOOL, 0, (LPARAM) (LPTOOLINFO) &m_ToolInfo);
545 ::SendMessage(m_hWndToolTip, TTM_SETTIPBKCOLOR, ::GetSysColor(COLOR_HIGHLIGHT), 0);
546 ::SendMessage(m_hWndToolTip, TTM_SETTIPTEXTCOLOR, ::GetSysColor(COLOR_HIGHLIGHTTEXT), 0);
548 CRect rectMargins(0,-1,0,-1);
549 ::SendMessage(m_hWndToolTip, TTM_SETMARGIN, 0, (LPARAM)&rectMargins);
551 CFont *pFont = GetFont();
552 ::SendMessage(m_hWndToolTip, WM_SETFONT, (WPARAM)(HFONT)*pFont, FALSE);
555 int CHistoryCombo::OnCreate(LPCREATESTRUCT lpCreateStruct)
557 if (CComboBoxEx::OnCreate(lpCreateStruct) == -1)
558 return -1;
560 if (m_bDyn)
561 CreateToolTip();
563 return 0;