45d62cad02f691df1f65480b376c404cc7f15a7b
[TortoiseGit.git] / src / Utils / MiscUI / HistoryCombo.cpp
blob45d62cad02f691df1f65480b376c404cc7f15a7b
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2012 - 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;
39 m_bTrim = TRUE;
40 SecureZeroMemory(&m_ToolInfo, sizeof(m_ToolInfo));
43 CHistoryCombo::~CHistoryCombo()
47 BOOL CHistoryCombo::PreCreateWindow(CREATESTRUCT& cs)
49 if (!m_bAllowSortStyle) //turn off CBS_SORT style
50 cs.style &= ~CBS_SORT;
51 cs.style |= CBS_AUTOHSCROLL;
52 m_bDyn = TRUE;
53 return CComboBoxEx::PreCreateWindow(cs);
56 BOOL CHistoryCombo::PreTranslateMessage(MSG* pMsg)
58 if (pMsg->message == WM_KEYDOWN)
60 bool bShift = !!(GetKeyState(VK_SHIFT) & 0x8000);
61 int nVirtKey = (int) pMsg->wParam;
63 if (nVirtKey == VK_RETURN)
64 return OnReturnKeyPressed();
65 else if (nVirtKey == VK_DELETE && bShift && GetDroppedState() )
67 RemoveSelectedItem();
68 return TRUE;
71 if (nVirtKey == 'A' && (GetKeyState(VK_CONTROL) & 0x8000 ) )
73 GetEditCtrl()->SetSel(0, -1);
74 return TRUE;
77 else if (pMsg->message == WM_MOUSEMOVE && this->m_bDyn )
79 if ((pMsg->wParam & MK_LBUTTON) == 0)
81 CPoint pt;
82 pt.x = LOWORD(pMsg->lParam);
83 pt.y = HIWORD(pMsg->lParam);
84 OnMouseMove((UINT)pMsg->wParam, pt);
85 return TRUE;
88 else if ((pMsg->message == WM_MOUSEWHEEL || pMsg->message == WM_MOUSEHWHEEL) && !GetDroppedState())
90 return TRUE;
93 return CComboBoxEx::PreTranslateMessage(pMsg);
96 BEGIN_MESSAGE_MAP(CHistoryCombo, CComboBoxEx)
97 ON_WM_MOUSEMOVE()
98 ON_WM_TIMER()
99 ON_WM_CREATE()
100 END_MESSAGE_MAP()
102 int CHistoryCombo::AddString(CString str, INT_PTR pos,BOOL isSel)
104 if (str.IsEmpty())
105 return -1;
107 COMBOBOXEXITEM cbei;
108 SecureZeroMemory(&cbei, sizeof cbei);
109 cbei.mask = CBEIF_TEXT;
111 if (pos < 0)
112 cbei.iItem = GetCount();
113 else
114 cbei.iItem = pos;
116 if (m_bTrim)
117 str.Trim(_T(" "));
118 CString combostring = str;
119 combostring.Replace('\r', ' ');
120 combostring.Replace('\n', ' ');
121 if (m_bTrim)
122 combostring.Trim();
123 cbei.pszText = const_cast<LPTSTR>(combostring.GetString());
125 #ifdef HISTORYCOMBO_WITH_SYSIMAGELIST
126 if (m_bURLHistory)
128 cbei.iImage = SYS_IMAGE_LIST().GetFileIconIndex(str);
129 if (cbei.iImage == SYS_IMAGE_LIST().GetDefaultIconIndex())
131 if (str.Left(5) == _T("http:"))
132 cbei.iImage = SYS_IMAGE_LIST().GetFileIconIndex(_T(".html"));
133 else if (str.Left(6) == _T("https:"))
134 cbei.iImage = SYS_IMAGE_LIST().GetFileIconIndex(_T(".shtml"));
135 else if (str.Left(5) == _T("file:"))
136 cbei.iImage = SYS_IMAGE_LIST().GetDirIconIndex();
137 else if (str.Left(4) == _T("git:"))
138 cbei.iImage = SYS_IMAGE_LIST().GetDirIconIndex();
139 else if (str.Left(4) == _T("ssh:"))
140 cbei.iImage = SYS_IMAGE_LIST().GetDirIconIndex();
141 else
142 cbei.iImage = SYS_IMAGE_LIST().GetDirIconIndex();
144 cbei.iSelectedImage = cbei.iImage;
145 cbei.mask |= CBEIF_IMAGE | CBEIF_SELECTEDIMAGE;
147 if (m_bPathHistory)
149 cbei.iImage = SYS_IMAGE_LIST().GetFileIconIndex(str);
150 if (cbei.iImage == SYS_IMAGE_LIST().GetDefaultIconIndex())
152 cbei.iImage = SYS_IMAGE_LIST().GetDirIconIndex();
154 cbei.iSelectedImage = cbei.iImage;
155 cbei.mask |= CBEIF_IMAGE | CBEIF_SELECTEDIMAGE;
157 #endif
159 //search the Combo for another string like this
160 //and do not insert if found
161 int nIndex = FindStringExact(-1, combostring);
162 if (nIndex != -1)
164 if (nIndex > cbei.iItem)
166 DeleteItem(nIndex);
167 m_arEntries.RemoveAt(nIndex);
169 else
171 if(isSel)
172 SetCurSel(nIndex);
173 return nIndex;
177 int nRet = InsertItem(&cbei);
178 if (nRet >= 0)
179 m_arEntries.InsertAt(nRet, str);
181 //truncate list to m_nMaxHistoryItems
182 int nNumItems = GetCount();
183 for (int n = m_nMaxHistoryItems; n < nNumItems; n++)
185 DeleteItem(m_nMaxHistoryItems);
186 m_arEntries.RemoveAt(m_nMaxHistoryItems);
189 if(isSel)
190 SetCurSel(nRet);
191 return nRet;
194 CString CHistoryCombo::LoadHistory(LPCTSTR lpszSection, LPCTSTR lpszKeyPrefix)
196 if (lpszSection == NULL || lpszKeyPrefix == NULL || *lpszSection == '\0')
197 return _T("");
199 m_sSection = lpszSection;
200 m_sKeyPrefix = lpszKeyPrefix;
202 int n = 0;
203 CString sText;
206 //keys are of form <lpszKeyPrefix><entrynumber>
207 CString sKey;
208 sKey.Format(_T("%s\\%s%d"), (LPCTSTR)m_sSection, (LPCTSTR)m_sKeyPrefix, n++);
209 sText = CRegString(sKey);
210 if (!sText.IsEmpty())
211 AddString(sText);
212 } while (!sText.IsEmpty() && n < m_nMaxHistoryItems);
214 SetCurSel(-1);
216 ModifyStyleEx(WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE, 0);
218 // need to resize the control for correct display
219 CRect rect;
220 GetWindowRect(rect);
221 GetParent()->ScreenToClient(rect);
222 MoveWindow(rect.left, rect.top, rect.Width(),100);
224 return sText;
227 void CHistoryCombo::SaveHistory()
229 if (m_sSection.IsEmpty())
230 return;
232 //add the current item to the history
233 CString sCurItem;
234 GetWindowText(sCurItem);
235 if (m_bTrim)
236 sCurItem.Trim();
237 if (!sCurItem.IsEmpty())
238 AddString(sCurItem, 0);
239 //save history to registry/inifile
240 int nMax = min(GetCount(), m_nMaxHistoryItems + 1);
241 for (int n = 0; n < nMax; n++)
243 CString sKey;
244 sKey.Format(_T("%s\\%s%d"), (LPCTSTR)m_sSection, (LPCTSTR)m_sKeyPrefix, n);
245 CRegString regkey = CRegString(sKey);
246 regkey = m_arEntries.GetAt(n);
248 //remove items exceeding the max number of history items
249 for (int n = nMax; ; n++)
251 CString sKey;
252 sKey.Format(_T("%s\\%s%d"), (LPCTSTR)m_sSection, (LPCTSTR)m_sKeyPrefix, n);
253 CRegString regkey = CRegString(sKey);
254 CString sText = regkey;
255 if (sText.IsEmpty())
256 break;
257 regkey.removeValue(); // remove entry
261 void CHistoryCombo::ClearHistory(BOOL bDeleteRegistryEntries/*=TRUE*/)
263 ResetContent();
264 if (! m_sSection.IsEmpty() && bDeleteRegistryEntries)
266 //remove profile entries
267 CString sKey;
268 for (int n = 0; ; n++)
270 sKey.Format(_T("%s\\%s%d"), (LPCTSTR)m_sSection, (LPCTSTR)m_sKeyPrefix, n);
271 CRegString regkey = CRegString(sKey);
272 CString sText = regkey;
273 if (sText.IsEmpty())
274 break;
275 regkey.removeValue(); // remove entry
280 void CHistoryCombo::SetURLHistory(BOOL bURLHistory)
282 m_bURLHistory = bURLHistory;
284 if (m_bURLHistory)
286 HWND hwndEdit;
287 // use for ComboEx
288 hwndEdit = (HWND)::SendMessage(this->m_hWnd, CBEM_GETEDITCONTROL, 0, 0);
289 if (NULL == hwndEdit)
291 // Try the unofficial way of getting the edit control CWnd*
292 CWnd* pWnd = this->GetDlgItem(1001);
293 if(pWnd)
295 hwndEdit = pWnd->GetSafeHwnd();
298 if (hwndEdit)
299 SHAutoComplete(hwndEdit, SHACF_URLALL);
302 #ifdef HISTORYCOMBO_WITH_SYSIMAGELIST
303 SetImageList(&SYS_IMAGE_LIST());
304 #endif
307 void CHistoryCombo::SetPathHistory(BOOL bPathHistory)
309 m_bPathHistory = bPathHistory;
311 if (m_bPathHistory)
313 HWND hwndEdit;
314 // use for ComboEx
315 hwndEdit = (HWND)::SendMessage(this->m_hWnd, CBEM_GETEDITCONTROL, 0, 0);
316 if (NULL == hwndEdit)
318 //if not, try the old standby
319 if(hwndEdit==NULL)
321 CWnd* pWnd = this->GetDlgItem(1001);
322 if(pWnd)
324 hwndEdit = pWnd->GetSafeHwnd();
328 if (hwndEdit)
329 SHAutoComplete(hwndEdit, SHACF_FILESYSTEM);
332 #ifdef HISTORYCOMBO_WITH_SYSIMAGELIST
333 SetImageList(&SYS_IMAGE_LIST());
334 #endif
337 void CHistoryCombo::SetMaxHistoryItems(int nMaxItems)
339 m_nMaxHistoryItems = nMaxItems;
341 //truncate list to nMaxItems
342 int nNumItems = GetCount();
343 for (int n = m_nMaxHistoryItems; n < nNumItems; n++)
344 DeleteString(m_nMaxHistoryItems);
346 void CHistoryCombo::AddString(STRING_VECTOR &list,BOOL isSel)
348 for(unsigned int i=0;i<list.size();i++)
350 AddString(list[i], -1, isSel);
353 CString CHistoryCombo::GetString() const
355 CString str;
356 int sel;
357 sel = GetCurSel();
358 DWORD style=GetStyle();
360 if (sel == CB_ERR ||(m_bURLHistory)||(m_bPathHistory) || (!(style&CBS_SIMPLE)))
362 GetWindowText(str);
363 return str;
366 return m_arEntries.GetAt(sel);
369 BOOL CHistoryCombo::RemoveSelectedItem()
371 int nIndex = GetCurSel();
372 if (nIndex == CB_ERR)
374 return FALSE;
377 DeleteItem(nIndex);
378 m_arEntries.RemoveAt(nIndex);
380 if ( nIndex < GetCount() )
382 // index stays the same to select the
383 // next item after the item which has
384 // just been deleted
386 else
388 // the end of the list has been reached
389 // so we select the previous item
390 nIndex--;
393 if ( nIndex == -1 )
395 // The one and only item has just been
396 // deleted -> reset window text since
397 // there is no item to select
398 SetWindowText(_T(""));
400 else
402 SetCurSel(nIndex);
405 // Since the dialog might be canceled we
406 // should now save the history. Before that
407 // set the selection to the first item so that
408 // the items will not be reordered and restore
409 // the selection after saving.
410 SetCurSel(0);
411 SaveHistory();
412 if ( nIndex != -1 )
414 SetCurSel(nIndex);
417 return TRUE;
420 void CHistoryCombo::PreSubclassWindow()
422 CComboBoxEx::PreSubclassWindow();
424 if (!m_bDyn)
425 CreateToolTip();
428 void CHistoryCombo::OnMouseMove(UINT nFlags, CPoint point)
430 CRect rectClient;
431 GetClientRect(&rectClient);
432 int nComboButtonWidth = ::GetSystemMetrics(SM_CXHTHUMB) + 2;
433 rectClient.right = rectClient.right - nComboButtonWidth;
435 if (rectClient.PtInRect(point))
437 ClientToScreen(&rectClient);
439 CString strText = GetString();
440 m_ToolInfo.lpszText = (LPTSTR)(LPCTSTR)strText;
442 HDC hDC = ::GetDC(m_hWnd);
444 CFont *pFont = GetFont();
445 HFONT hOldFont = (HFONT) ::SelectObject(hDC, (HFONT) *pFont);
447 SIZE size;
448 ::GetTextExtentPoint32(hDC, strText, strText.GetLength(), &size);
449 ::SelectObject(hDC, hOldFont);
450 ::ReleaseDC(m_hWnd, hDC);
452 if (size.cx > (rectClient.Width() - 6))
454 rectClient.left += 1;
455 rectClient.top += 3;
457 COLORREF rgbText = ::GetSysColor(COLOR_WINDOWTEXT);
458 COLORREF rgbBackground = ::GetSysColor(COLOR_WINDOW);
460 CWnd *pWnd = GetFocus();
461 if (pWnd)
463 if (pWnd->m_hWnd == m_hWnd)
465 rgbText = ::GetSysColor(COLOR_HIGHLIGHTTEXT);
466 rgbBackground = ::GetSysColor(COLOR_HIGHLIGHT);
470 if (!m_ttShown)
472 ::SendMessage(m_hWndToolTip, TTM_SETTIPBKCOLOR, rgbBackground, 0);
473 ::SendMessage(m_hWndToolTip, TTM_SETTIPTEXTCOLOR, rgbText, 0);
474 ::SendMessage(m_hWndToolTip, TTM_UPDATETIPTEXT, 0, (LPARAM) &m_ToolInfo);
475 ::SendMessage(m_hWndToolTip, TTM_TRACKPOSITION, 0, (LPARAM)MAKELONG(rectClient.left, rectClient.top));
476 ::SendMessage(m_hWndToolTip, TTM_TRACKACTIVATE, TRUE, (LPARAM)(LPTOOLINFO) &m_ToolInfo);
477 SetTimer(1, 80, NULL);
478 SetTimer(2, 2000, NULL);
479 m_ttShown = TRUE;
482 else
484 ::SendMessage(m_hWndToolTip, TTM_TRACKACTIVATE, FALSE, (LPARAM)(LPTOOLINFO) &m_ToolInfo);
485 m_ttShown = FALSE;
488 else
490 ::SendMessage(m_hWndToolTip, TTM_TRACKACTIVATE, FALSE, (LPARAM)(LPTOOLINFO) &m_ToolInfo);
491 m_ttShown = FALSE;
494 CComboBoxEx::OnMouseMove(nFlags, point);
497 void CHistoryCombo::OnTimer(UINT_PTR nIDEvent)
499 CPoint point;
500 DWORD ptW = GetMessagePos();
501 point.x = GET_X_LPARAM(ptW);
502 point.y = GET_Y_LPARAM(ptW);
503 ScreenToClient(&point);
505 CRect rectClient;
506 GetClientRect(&rectClient);
507 int nComboButtonWidth = ::GetSystemMetrics(SM_CXHTHUMB) + 2;
509 rectClient.right = rectClient.right - nComboButtonWidth;
511 if (!rectClient.PtInRect(point))
513 KillTimer(nIDEvent);
514 ::SendMessage(m_hWndToolTip, TTM_TRACKACTIVATE, FALSE, (LPARAM)(LPTOOLINFO) &m_ToolInfo);
515 m_ttShown = FALSE;
517 if (nIDEvent == 2)
519 // tooltip timeout, just deactivate it
520 ::SendMessage(m_hWndToolTip, TTM_TRACKACTIVATE, FALSE, (LPARAM)(LPTOOLINFO) &m_ToolInfo);
521 // don't set m_ttShown to FALSE, because we don't want the tooltip to show up again
522 // without the mouse pointer first leaving the control and entering it again
525 CComboBoxEx::OnTimer(nIDEvent);
528 void CHistoryCombo::CreateToolTip()
530 // create tooltip
531 m_hWndToolTip = ::CreateWindowEx(WS_EX_TOPMOST,
532 TOOLTIPS_CLASS,
533 NULL,
534 TTS_NOPREFIX | TTS_ALWAYSTIP,
535 CW_USEDEFAULT,
536 CW_USEDEFAULT,
537 CW_USEDEFAULT,
538 CW_USEDEFAULT,
539 m_hWnd,
540 NULL,
541 NULL,
542 NULL);
544 // initialize tool info struct
545 memset(&m_ToolInfo, 0, sizeof(m_ToolInfo));
546 m_ToolInfo.cbSize = sizeof(m_ToolInfo);
547 m_ToolInfo.uFlags = TTF_TRANSPARENT;
548 m_ToolInfo.hwnd = m_hWnd;
550 ::SendMessage(m_hWndToolTip, TTM_SETMAXTIPWIDTH, 0, SHRT_MAX);
551 ::SendMessage(m_hWndToolTip, TTM_ADDTOOL, 0, (LPARAM) (LPTOOLINFO) &m_ToolInfo);
552 ::SendMessage(m_hWndToolTip, TTM_SETTIPBKCOLOR, ::GetSysColor(COLOR_HIGHLIGHT), 0);
553 ::SendMessage(m_hWndToolTip, TTM_SETTIPTEXTCOLOR, ::GetSysColor(COLOR_HIGHLIGHTTEXT), 0);
555 CRect rectMargins(0,-1,0,-1);
556 ::SendMessage(m_hWndToolTip, TTM_SETMARGIN, 0, (LPARAM)&rectMargins);
558 CFont *pFont = GetFont();
559 ::SendMessage(m_hWndToolTip, WM_SETFONT, (WPARAM)(HFONT)*pFont, FALSE);
562 int CHistoryCombo::OnCreate(LPCREATESTRUCT lpCreateStruct)
564 if (CComboBoxEx::OnCreate(lpCreateStruct) == -1)
565 return -1;
567 if (m_bDyn)
568 CreateToolTip();
570 return 0;