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.
21 #include "HistoryCombo.h"
22 #include "../registry.h"
24 #ifdef HISTORYCOMBO_WITH_SYSIMAGELIST
25 #include "../SysImageList.h"
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
;
41 m_bWantReturn
= FALSE
;
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
;
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() )
75 if (nVirtKey
== 'A' && (GetKeyState(VK_CONTROL
) & 0x8000 ) )
77 CEdit
*edit
= GetEditCtrl();
83 else if (pMsg
->message
== WM_MOUSEMOVE
&& this->m_bDyn
)
85 if ((pMsg
->wParam
& MK_LBUTTON
) == 0)
88 pt
.x
= LOWORD(pMsg
->lParam
);
89 pt
.y
= HIWORD(pMsg
->lParam
);
90 OnMouseMove((UINT
)pMsg
->wParam
, pt
);
94 else if ((pMsg
->message
== WM_MOUSEWHEEL
|| pMsg
->message
== WM_MOUSEHWHEEL
) && !GetDroppedState())
99 return CComboBoxEx::PreTranslateMessage(pMsg
);
102 BEGIN_MESSAGE_MAP(CHistoryCombo
, CComboBoxEx
)
108 int CHistoryCombo::AddString(const CString
& str
, INT_PTR pos
/* = -1*/, BOOL isSel
/* = TRUE */)
116 CString combostring
= str
;
117 combostring
.Replace('\r', ' ');
118 combostring
.Replace('\n', ' ');
121 if (combostring
.IsEmpty())
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
);
132 m_arEntries
.RemoveAt(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
);
157 int CHistoryCombo::InsertEntry(const CString
& combostring
, INT_PTR pos
)
159 COMBOBOXEXITEM cbei
= { 0 };
160 cbei
.mask
= CBEIF_TEXT
;
163 cbei
.pszText
= const_cast<LPTSTR
>(combostring
.GetString());
165 #ifdef HISTORYCOMBO_WITH_SYSIMAGELIST
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
;
182 cbei
.iImage
= SYS_IMAGE_LIST().GetDirIconIndex();
184 cbei
.iSelectedImage
= cbei
.iImage
;
185 cbei
.mask
|= CBEIF_IMAGE
| CBEIF_SELECTEDIMAGE
;
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
;
199 int nRet
= InsertItem(&cbei
);
201 m_arEntries
.InsertAt(nRet
, combostring
);
206 void CHistoryCombo::SetList(const STRING_VECTOR
& list
)
209 for (int i
= 0; i
< list
.size(); ++i
)
211 CString combostring
= list
[i
];
212 combostring
.Replace('\r', ' ');
213 combostring
.Replace('\n', ' ');
216 if (combostring
.IsEmpty())
219 InsertEntry(combostring
, i
);
223 CString
CHistoryCombo::LoadHistory(LPCTSTR lpszSection
, LPCTSTR lpszKeyPrefix
)
225 if (lpszSection
== NULL
|| lpszKeyPrefix
== NULL
|| *lpszSection
== '\0')
228 m_sSection
= lpszSection
;
229 m_sKeyPrefix
= lpszKeyPrefix
;
235 //keys are of form <lpszKeyPrefix><entrynumber>
237 sKey
.Format(_T("%s\\%s%d"), (LPCTSTR
)m_sSection
, (LPCTSTR
)m_sKeyPrefix
, n
++);
238 sText
= CRegString(sKey
);
239 if (!sText
.IsEmpty())
241 } while (!sText
.IsEmpty() && n
< m_nMaxHistoryItems
);
245 ModifyStyleEx(WS_EX_DLGMODALFRAME
| WS_EX_WINDOWEDGE
, 0);
247 // need to resize the control for correct display
250 GetParent()->ScreenToClient(rect
);
251 MoveWindow(rect
.left
, rect
.top
, rect
.Width(),100);
256 void CHistoryCombo::SaveHistory()
258 if (m_sSection
.IsEmpty())
261 //add the current item to the history
263 GetWindowText(sCurItem
);
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
++)
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
++)
281 sKey
.Format(_T("%s\\%s%d"), (LPCTSTR
)m_sSection
, (LPCTSTR
)m_sKeyPrefix
, n
);
282 CRegString
regkey(sKey
);
283 CString sText
= regkey
;
286 regkey
.removeValue(); // remove entry
290 void CHistoryCombo::ClearHistory(BOOL bDeleteRegistryEntries
/*=TRUE*/)
293 if (! m_sSection
.IsEmpty() && bDeleteRegistryEntries
)
295 //remove profile entries
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
;
304 regkey
.removeValue(); // remove entry
309 void CHistoryCombo::RemoveEntryFromHistory(LPCTSTR lpszSection
, LPCTSTR lpszKeyPrefix
, const CString
& entryToRemove
)
311 if (entryToRemove
.IsEmpty())
319 sKey
.Format(_T("%s\\%s%d"), lpszSection
, lpszKeyPrefix
, ++n
);
320 CRegString
regkey(sKey
);
322 if (sText
== entryToRemove
)
324 regkey
.removeValue();
329 } while (!sText
.IsEmpty());
335 sKey
.Format(_T("%s\\%s%d"), lpszSection
, lpszKeyPrefix
, n
);
336 CRegString
regkey(sKey
);
338 if (!sText
.IsEmpty())
341 sKeyNew
.Format(_T("%s\\%s%d"), lpszSection
, lpszKeyPrefix
, n
- 1);
342 CRegString
regkeyNew(sKeyNew
);
344 regkey
.removeValue();
352 void CHistoryCombo::SetURLHistory(BOOL bURLHistory
)
354 m_bURLHistory
= bURLHistory
;
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);
367 hwndEdit
= pWnd
->GetSafeHwnd();
371 SHAutoComplete(hwndEdit
, SHACF_URLALL
);
374 #ifdef HISTORYCOMBO_WITH_SYSIMAGELIST
375 SetImageList(&SYS_IMAGE_LIST());
379 void CHistoryCombo::SetPathHistory(BOOL bPathHistory
)
381 m_bPathHistory
= bPathHistory
;
387 hwndEdit
= (HWND
)::SendMessage(this->m_hWnd
, CBEM_GETEDITCONTROL
, 0, 0);
388 if (NULL
== hwndEdit
)
390 //if not, try the old standby
393 CWnd
* pWnd
= this->GetDlgItem(1001);
396 hwndEdit
= pWnd
->GetSafeHwnd();
401 SHAutoComplete(hwndEdit
, SHACF_FILESYSTEM
);
404 #ifdef HISTORYCOMBO_WITH_SYSIMAGELIST
405 SetImageList(&SYS_IMAGE_LIST());
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
424 DWORD style
=GetStyle();
426 if (sel
== CB_ERR
||(m_bURLHistory
)||(m_bPathHistory
) || (!(style
&CBS_SIMPLE
)))
432 return m_arEntries
.GetAt(sel
);
435 BOOL
CHistoryCombo::RemoveSelectedItem()
437 int nIndex
= GetCurSel();
438 if (nIndex
== CB_ERR
)
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
454 // the end of the list has been reached
455 // so we select the previous item
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(""));
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.
486 void CHistoryCombo::PreSubclassWindow()
488 CComboBoxEx::PreSubclassWindow();
494 void CHistoryCombo::OnMouseMove(UINT nFlags
, CPoint point
)
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
);
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;
523 COLORREF rgbText
= ::GetSysColor(COLOR_WINDOWTEXT
);
524 COLORREF rgbBackground
= ::GetSysColor(COLOR_WINDOW
);
526 CWnd
*pWnd
= GetFocus();
529 if (pWnd
->m_hWnd
== m_hWnd
)
531 rgbText
= ::GetSysColor(COLOR_HIGHLIGHTTEXT
);
532 rgbBackground
= ::GetSysColor(COLOR_HIGHLIGHT
);
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
);
550 ::SendMessage(m_hWndToolTip
, TTM_TRACKACTIVATE
, FALSE
, (LPARAM
)(LPTOOLINFO
) &m_ToolInfo
);
556 ::SendMessage(m_hWndToolTip
, TTM_TRACKACTIVATE
, FALSE
, (LPARAM
)(LPTOOLINFO
) &m_ToolInfo
);
560 CComboBoxEx::OnMouseMove(nFlags
, point
);
563 void CHistoryCombo::OnTimer(UINT_PTR nIDEvent
)
566 DWORD ptW
= GetMessagePos();
567 point
.x
= GET_X_LPARAM(ptW
);
568 point
.y
= GET_Y_LPARAM(ptW
);
569 ScreenToClient(&point
);
572 GetClientRect(&rectClient
);
573 int nComboButtonWidth
= ::GetSystemMetrics(SM_CXHTHUMB
) + 2;
575 rectClient
.right
= rectClient
.right
- nComboButtonWidth
;
577 if (!rectClient
.PtInRect(point
))
580 ::SendMessage(m_hWndToolTip
, TTM_TRACKACTIVATE
, FALSE
, (LPARAM
)(LPTOOLINFO
) &m_ToolInfo
);
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()
597 m_hWndToolTip
= ::CreateWindowEx(WS_EX_TOPMOST
,
600 TTS_NOPREFIX
| TTS_ALWAYSTIP
,
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)
639 int CHistoryCombo::FindStringExactCaseSensitive(int nIndexStart
, LPCTSTR lpszFind
)
641 nIndexStart
= max(0, nIndexStart
);
642 for (int i
= nIndexStart
; i
< GetCount(); i
++)
645 GetLBText(i
, exactString
);
646 if (exactString
== lpszFind
)