Check null pointer before using
[TortoiseGit.git] / src / Utils / MiscUI / HistoryCombo.cpp
blobbd7a2ceaf4eb41d2f1f4b8ddf96cebb3c16aed98
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 CEdit *edit = GetEditCtrl();
74 if (edit)
75 edit->SetSel(0, -1);
76 return TRUE;
79 else if (pMsg->message == WM_MOUSEMOVE && this->m_bDyn )
81 if ((pMsg->wParam & MK_LBUTTON) == 0)
83 CPoint pt;
84 pt.x = LOWORD(pMsg->lParam);
85 pt.y = HIWORD(pMsg->lParam);
86 OnMouseMove((UINT)pMsg->wParam, pt);
87 return TRUE;
90 else if ((pMsg->message == WM_MOUSEWHEEL || pMsg->message == WM_MOUSEHWHEEL) && !GetDroppedState())
92 return TRUE;
95 return CComboBoxEx::PreTranslateMessage(pMsg);
98 BEGIN_MESSAGE_MAP(CHistoryCombo, CComboBoxEx)
99 ON_WM_MOUSEMOVE()
100 ON_WM_TIMER()
101 ON_WM_CREATE()
102 END_MESSAGE_MAP()
104 int CHistoryCombo::AddString(CString str, INT_PTR pos,BOOL isSel)
106 if (str.IsEmpty())
107 return -1;
109 COMBOBOXEXITEM cbei;
110 SecureZeroMemory(&cbei, sizeof cbei);
111 cbei.mask = CBEIF_TEXT;
113 if (pos < 0)
114 cbei.iItem = GetCount();
115 else
116 cbei.iItem = pos;
118 if (m_bTrim)
119 str.Trim(_T(" "));
120 CString combostring = str;
121 combostring.Replace('\r', ' ');
122 combostring.Replace('\n', ' ');
123 if (m_bTrim)
124 combostring.Trim();
125 cbei.pszText = const_cast<LPTSTR>(combostring.GetString());
127 #ifdef HISTORYCOMBO_WITH_SYSIMAGELIST
128 if (m_bURLHistory)
130 cbei.iImage = SYS_IMAGE_LIST().GetFileIconIndex(str);
131 if (cbei.iImage == SYS_IMAGE_LIST().GetDefaultIconIndex())
133 if (str.Left(5) == _T("http:"))
134 cbei.iImage = SYS_IMAGE_LIST().GetFileIconIndex(_T(".html"));
135 else if (str.Left(6) == _T("https:"))
136 cbei.iImage = SYS_IMAGE_LIST().GetFileIconIndex(_T(".shtml"));
137 else if (str.Left(5) == _T("file:"))
138 cbei.iImage = SYS_IMAGE_LIST().GetDirIconIndex();
139 else if (str.Left(4) == _T("git:"))
140 cbei.iImage = SYS_IMAGE_LIST().GetDirIconIndex();
141 else if (str.Left(4) == _T("ssh:"))
142 cbei.iImage = SYS_IMAGE_LIST().GetDirIconIndex();
143 else
144 cbei.iImage = SYS_IMAGE_LIST().GetDirIconIndex();
146 cbei.iSelectedImage = cbei.iImage;
147 cbei.mask |= CBEIF_IMAGE | CBEIF_SELECTEDIMAGE;
149 if (m_bPathHistory)
151 cbei.iImage = SYS_IMAGE_LIST().GetFileIconIndex(str);
152 if (cbei.iImage == SYS_IMAGE_LIST().GetDefaultIconIndex())
154 cbei.iImage = SYS_IMAGE_LIST().GetDirIconIndex();
156 cbei.iSelectedImage = cbei.iImage;
157 cbei.mask |= CBEIF_IMAGE | CBEIF_SELECTEDIMAGE;
159 #endif
161 //search the Combo for another string like this
162 //and do not insert if found
163 int nIndex = FindStringExact(-1, combostring);
164 if (nIndex != -1)
166 if (nIndex > cbei.iItem)
168 DeleteItem(nIndex);
169 m_arEntries.RemoveAt(nIndex);
171 else
173 if(isSel)
174 SetCurSel(nIndex);
175 return nIndex;
179 int nRet = InsertItem(&cbei);
180 if (nRet >= 0)
181 m_arEntries.InsertAt(nRet, str);
183 //truncate list to m_nMaxHistoryItems
184 int nNumItems = GetCount();
185 for (int n = m_nMaxHistoryItems; n < nNumItems; n++)
187 DeleteItem(m_nMaxHistoryItems);
188 m_arEntries.RemoveAt(m_nMaxHistoryItems);
191 if(isSel)
192 SetCurSel(nRet);
193 return nRet;
196 CString CHistoryCombo::LoadHistory(LPCTSTR lpszSection, LPCTSTR lpszKeyPrefix)
198 if (lpszSection == NULL || lpszKeyPrefix == NULL || *lpszSection == '\0')
199 return _T("");
201 m_sSection = lpszSection;
202 m_sKeyPrefix = lpszKeyPrefix;
204 int n = 0;
205 CString sText;
208 //keys are of form <lpszKeyPrefix><entrynumber>
209 CString sKey;
210 sKey.Format(_T("%s\\%s%d"), (LPCTSTR)m_sSection, (LPCTSTR)m_sKeyPrefix, n++);
211 sText = CRegString(sKey);
212 if (!sText.IsEmpty())
213 AddString(sText);
214 } while (!sText.IsEmpty() && n < m_nMaxHistoryItems);
216 SetCurSel(-1);
218 ModifyStyleEx(WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE, 0);
220 // need to resize the control for correct display
221 CRect rect;
222 GetWindowRect(rect);
223 GetParent()->ScreenToClient(rect);
224 MoveWindow(rect.left, rect.top, rect.Width(),100);
226 return sText;
229 void CHistoryCombo::SaveHistory()
231 if (m_sSection.IsEmpty())
232 return;
234 //add the current item to the history
235 CString sCurItem;
236 GetWindowText(sCurItem);
237 if (m_bTrim)
238 sCurItem.Trim();
239 if (!sCurItem.IsEmpty())
240 AddString(sCurItem, 0);
241 //save history to registry/inifile
242 int nMax = min(GetCount(), m_nMaxHistoryItems + 1);
243 for (int n = 0; n < nMax; n++)
245 CString sKey;
246 sKey.Format(_T("%s\\%s%d"), (LPCTSTR)m_sSection, (LPCTSTR)m_sKeyPrefix, n);
247 CRegString regkey = CRegString(sKey);
248 regkey = m_arEntries.GetAt(n);
250 //remove items exceeding the max number of history items
251 for (int n = nMax; ; n++)
253 CString sKey;
254 sKey.Format(_T("%s\\%s%d"), (LPCTSTR)m_sSection, (LPCTSTR)m_sKeyPrefix, n);
255 CRegString regkey = CRegString(sKey);
256 CString sText = regkey;
257 if (sText.IsEmpty())
258 break;
259 regkey.removeValue(); // remove entry
263 void CHistoryCombo::ClearHistory(BOOL bDeleteRegistryEntries/*=TRUE*/)
265 ResetContent();
266 if (! m_sSection.IsEmpty() && bDeleteRegistryEntries)
268 //remove profile entries
269 CString sKey;
270 for (int n = 0; ; n++)
272 sKey.Format(_T("%s\\%s%d"), (LPCTSTR)m_sSection, (LPCTSTR)m_sKeyPrefix, n);
273 CRegString regkey = CRegString(sKey);
274 CString sText = regkey;
275 if (sText.IsEmpty())
276 break;
277 regkey.removeValue(); // remove entry
282 void CHistoryCombo::SetURLHistory(BOOL bURLHistory)
284 m_bURLHistory = bURLHistory;
286 if (m_bURLHistory)
288 HWND hwndEdit;
289 // use for ComboEx
290 hwndEdit = (HWND)::SendMessage(this->m_hWnd, CBEM_GETEDITCONTROL, 0, 0);
291 if (NULL == hwndEdit)
293 // Try the unofficial way of getting the edit control CWnd*
294 CWnd* pWnd = this->GetDlgItem(1001);
295 if(pWnd)
297 hwndEdit = pWnd->GetSafeHwnd();
300 if (hwndEdit)
301 SHAutoComplete(hwndEdit, SHACF_URLALL);
304 #ifdef HISTORYCOMBO_WITH_SYSIMAGELIST
305 SetImageList(&SYS_IMAGE_LIST());
306 #endif
309 void CHistoryCombo::SetPathHistory(BOOL bPathHistory)
311 m_bPathHistory = bPathHistory;
313 if (m_bPathHistory)
315 HWND hwndEdit;
316 // use for ComboEx
317 hwndEdit = (HWND)::SendMessage(this->m_hWnd, CBEM_GETEDITCONTROL, 0, 0);
318 if (NULL == hwndEdit)
320 //if not, try the old standby
321 if(hwndEdit==NULL)
323 CWnd* pWnd = this->GetDlgItem(1001);
324 if(pWnd)
326 hwndEdit = pWnd->GetSafeHwnd();
330 if (hwndEdit)
331 SHAutoComplete(hwndEdit, SHACF_FILESYSTEM);
334 #ifdef HISTORYCOMBO_WITH_SYSIMAGELIST
335 SetImageList(&SYS_IMAGE_LIST());
336 #endif
339 void CHistoryCombo::SetMaxHistoryItems(int nMaxItems)
341 m_nMaxHistoryItems = nMaxItems;
343 //truncate list to nMaxItems
344 int nNumItems = GetCount();
345 for (int n = m_nMaxHistoryItems; n < nNumItems; n++)
346 DeleteString(m_nMaxHistoryItems);
348 void CHistoryCombo::AddString(STRING_VECTOR &list,BOOL isSel)
350 for(unsigned int i=0;i<list.size();i++)
352 AddString(list[i], -1, isSel);
355 CString CHistoryCombo::GetString() const
357 CString str;
358 int sel;
359 sel = GetCurSel();
360 DWORD style=GetStyle();
362 if (sel == CB_ERR ||(m_bURLHistory)||(m_bPathHistory) || (!(style&CBS_SIMPLE)))
364 GetWindowText(str);
365 return str;
368 return m_arEntries.GetAt(sel);
371 BOOL CHistoryCombo::RemoveSelectedItem()
373 int nIndex = GetCurSel();
374 if (nIndex == CB_ERR)
376 return FALSE;
379 DeleteItem(nIndex);
380 m_arEntries.RemoveAt(nIndex);
382 if ( nIndex < GetCount() )
384 // index stays the same to select the
385 // next item after the item which has
386 // just been deleted
388 else
390 // the end of the list has been reached
391 // so we select the previous item
392 nIndex--;
395 if ( nIndex == -1 )
397 // The one and only item has just been
398 // deleted -> reset window text since
399 // there is no item to select
400 SetWindowText(_T(""));
402 else
404 SetCurSel(nIndex);
407 // Since the dialog might be canceled we
408 // should now save the history. Before that
409 // set the selection to the first item so that
410 // the items will not be reordered and restore
411 // the selection after saving.
412 SetCurSel(0);
413 SaveHistory();
414 if ( nIndex != -1 )
416 SetCurSel(nIndex);
419 return TRUE;
422 void CHistoryCombo::PreSubclassWindow()
424 CComboBoxEx::PreSubclassWindow();
426 if (!m_bDyn)
427 CreateToolTip();
430 void CHistoryCombo::OnMouseMove(UINT nFlags, CPoint point)
432 CRect rectClient;
433 GetClientRect(&rectClient);
434 int nComboButtonWidth = ::GetSystemMetrics(SM_CXHTHUMB) + 2;
435 rectClient.right = rectClient.right - nComboButtonWidth;
437 if (rectClient.PtInRect(point))
439 ClientToScreen(&rectClient);
441 CString strText = GetString();
442 m_ToolInfo.lpszText = (LPTSTR)(LPCTSTR)strText;
444 HDC hDC = ::GetDC(m_hWnd);
446 CFont *pFont = GetFont();
447 HFONT hOldFont = (HFONT) ::SelectObject(hDC, (HFONT) *pFont);
449 SIZE size;
450 ::GetTextExtentPoint32(hDC, strText, strText.GetLength(), &size);
451 ::SelectObject(hDC, hOldFont);
452 ::ReleaseDC(m_hWnd, hDC);
454 if (size.cx > (rectClient.Width() - 6))
456 rectClient.left += 1;
457 rectClient.top += 3;
459 COLORREF rgbText = ::GetSysColor(COLOR_WINDOWTEXT);
460 COLORREF rgbBackground = ::GetSysColor(COLOR_WINDOW);
462 CWnd *pWnd = GetFocus();
463 if (pWnd)
465 if (pWnd->m_hWnd == m_hWnd)
467 rgbText = ::GetSysColor(COLOR_HIGHLIGHTTEXT);
468 rgbBackground = ::GetSysColor(COLOR_HIGHLIGHT);
472 if (!m_ttShown)
474 ::SendMessage(m_hWndToolTip, TTM_SETTIPBKCOLOR, rgbBackground, 0);
475 ::SendMessage(m_hWndToolTip, TTM_SETTIPTEXTCOLOR, rgbText, 0);
476 ::SendMessage(m_hWndToolTip, TTM_UPDATETIPTEXT, 0, (LPARAM) &m_ToolInfo);
477 ::SendMessage(m_hWndToolTip, TTM_TRACKPOSITION, 0, (LPARAM)MAKELONG(rectClient.left, rectClient.top));
478 ::SendMessage(m_hWndToolTip, TTM_TRACKACTIVATE, TRUE, (LPARAM)(LPTOOLINFO) &m_ToolInfo);
479 SetTimer(1, 80, NULL);
480 SetTimer(2, 2000, NULL);
481 m_ttShown = TRUE;
484 else
486 ::SendMessage(m_hWndToolTip, TTM_TRACKACTIVATE, FALSE, (LPARAM)(LPTOOLINFO) &m_ToolInfo);
487 m_ttShown = FALSE;
490 else
492 ::SendMessage(m_hWndToolTip, TTM_TRACKACTIVATE, FALSE, (LPARAM)(LPTOOLINFO) &m_ToolInfo);
493 m_ttShown = FALSE;
496 CComboBoxEx::OnMouseMove(nFlags, point);
499 void CHistoryCombo::OnTimer(UINT_PTR nIDEvent)
501 CPoint point;
502 DWORD ptW = GetMessagePos();
503 point.x = GET_X_LPARAM(ptW);
504 point.y = GET_Y_LPARAM(ptW);
505 ScreenToClient(&point);
507 CRect rectClient;
508 GetClientRect(&rectClient);
509 int nComboButtonWidth = ::GetSystemMetrics(SM_CXHTHUMB) + 2;
511 rectClient.right = rectClient.right - nComboButtonWidth;
513 if (!rectClient.PtInRect(point))
515 KillTimer(nIDEvent);
516 ::SendMessage(m_hWndToolTip, TTM_TRACKACTIVATE, FALSE, (LPARAM)(LPTOOLINFO) &m_ToolInfo);
517 m_ttShown = FALSE;
519 if (nIDEvent == 2)
521 // tooltip timeout, just deactivate it
522 ::SendMessage(m_hWndToolTip, TTM_TRACKACTIVATE, FALSE, (LPARAM)(LPTOOLINFO) &m_ToolInfo);
523 // don't set m_ttShown to FALSE, because we don't want the tooltip to show up again
524 // without the mouse pointer first leaving the control and entering it again
527 CComboBoxEx::OnTimer(nIDEvent);
530 void CHistoryCombo::CreateToolTip()
532 // create tooltip
533 m_hWndToolTip = ::CreateWindowEx(WS_EX_TOPMOST,
534 TOOLTIPS_CLASS,
535 NULL,
536 TTS_NOPREFIX | TTS_ALWAYSTIP,
537 CW_USEDEFAULT,
538 CW_USEDEFAULT,
539 CW_USEDEFAULT,
540 CW_USEDEFAULT,
541 m_hWnd,
542 NULL,
543 NULL,
544 NULL);
546 // initialize tool info struct
547 memset(&m_ToolInfo, 0, sizeof(m_ToolInfo));
548 m_ToolInfo.cbSize = sizeof(m_ToolInfo);
549 m_ToolInfo.uFlags = TTF_TRANSPARENT;
550 m_ToolInfo.hwnd = m_hWnd;
552 ::SendMessage(m_hWndToolTip, TTM_SETMAXTIPWIDTH, 0, SHRT_MAX);
553 ::SendMessage(m_hWndToolTip, TTM_ADDTOOL, 0, (LPARAM) (LPTOOLINFO) &m_ToolInfo);
554 ::SendMessage(m_hWndToolTip, TTM_SETTIPBKCOLOR, ::GetSysColor(COLOR_HIGHLIGHT), 0);
555 ::SendMessage(m_hWndToolTip, TTM_SETTIPTEXTCOLOR, ::GetSysColor(COLOR_HIGHLIGHTTEXT), 0);
557 CRect rectMargins(0,-1,0,-1);
558 ::SendMessage(m_hWndToolTip, TTM_SETMARGIN, 0, (LPARAM)&rectMargins);
560 CFont *pFont = GetFont();
561 ::SendMessage(m_hWndToolTip, WM_SETFONT, (WPARAM)(HFONT)*pFont, FALSE);
564 int CHistoryCombo::OnCreate(LPCREATESTRUCT lpCreateStruct)
566 if (CComboBoxEx::OnCreate(lpCreateStruct) == -1)
567 return -1;
569 if (m_bDyn)
570 CreateToolTip();
572 return 0;