OSDN Git Service

cc5a0f64b3b04c8c4853811d4e52639f8904213d
[winmerge-jp/winmerge-jp.git] / Src / Common / SuperComboBox.cpp
1 // CSuperComboBox.cpp : implementation file
2 //
3
4 #include "StdAfx.h"
5 #include "SuperComboBox.h"
6 #include <vector>
7 #include "DropHandler.h"
8
9 // Wrap placement new to avoid the need to temporarily #undef new
10 template<typename T>
11 T &placement_cast(void *p)
12 {
13         return *new(p) T;
14 }
15
16 #ifdef _DEBUG
17 #define new DEBUG_NEW
18 #endif
19
20 #define DEF_MAXSIZE             20      // default maximum items to retain in each SuperComboBox
21
22
23 /////////////////////////////////////////////////////////////////////////////
24 // CSuperComboBox
25
26 HIMAGELIST CSuperComboBox::m_himlSystem = nullptr;
27
28 CSuperComboBox::CSuperComboBox()
29         : m_pDropHandler(nullptr)
30         , m_bInEditchange(false)
31         , m_bDoComplete(false)
32         , m_bAutoComplete(false)
33         , m_bHasImageList(false)
34         , m_bComboBoxEx(false)
35         , m_bExtendedFileNames(false)
36         , m_bCanBeEmpty(false)
37         , m_nMaxItems(DEF_MAXSIZE)
38 {
39
40
41         // Initialize OLE libraries if not yet initialized
42         m_bMustUninitOLE = false;
43         _AFX_THREAD_STATE* pState = AfxGetThreadState();
44         if (!pState->m_bNeedTerm)
45         {
46                 SCODE sc = ::OleInitialize(nullptr);
47                 if (FAILED(sc))
48                         AfxMessageBox(_T("OLE initialization failed. Make sure that the OLE libraries are the correct version"));
49                 else
50                         m_bMustUninitOLE = true;
51         }
52 }
53
54 CSuperComboBox::~CSuperComboBox()
55 {
56         // Uninitialize OLE support
57         if (m_bMustUninitOLE)
58                 ::OleUninitialize();
59 }
60
61 BEGIN_MESSAGE_MAP(CSuperComboBox, CComboBoxEx)
62         //{{AFX_MSG_MAP(CSuperComboBox)
63         ON_CONTROL_REFLECT_EX(CBN_EDITCHANGE, OnEditchange)
64         ON_CONTROL_REFLECT_EX(CBN_SETFOCUS, OnSetfocus)
65         ON_WM_CREATE()
66         ON_WM_DESTROY()
67         ON_WM_DRAWITEM()
68         ON_NOTIFY_REFLECT(CBEN_GETDISPINFO, OnGetDispInfo)
69         //}}AFX_MSG_MAP
70 END_MESSAGE_MAP()
71
72 /////////////////////////////////////////////////////////////////////////////
73 // CSuperComboBox message handlers
74
75 void CSuperComboBox::PreSubclassWindow()
76 {
77         CComboBoxEx::PreSubclassWindow();
78         m_pDropHandler = new DropHandler(std::bind(&CSuperComboBox::OnDropFiles, this, std::placeholders::_1));
79         RegisterDragDrop(m_hWnd, m_pDropHandler);
80         
81         TCHAR szClassName[256];
82         GetClassName(m_hWnd, szClassName, sizeof(szClassName)/sizeof(szClassName[0]));
83         if (lstrcmpi(_T("ComboBoxEx32"), szClassName) == 0)
84                 m_bComboBoxEx = true;
85 }
86
87 /**
88  * @brief Sets additional state for handling Extended Length file names.
89  */
90 void CSuperComboBox::SetFileControlStates(bool bCanBeEmpty /*= false*/, int nMaxItems /*= -1*/)
91 {
92         ASSERT(m_bComboBoxEx);
93
94         m_bExtendedFileNames = true;
95         m_bCanBeEmpty = bCanBeEmpty;
96         if (nMaxItems > 0)
97                 m_nMaxItems = nMaxItems;
98 }
99
100 /**
101  * @brief Adds a string to the list box of a combo box
102  * @param lpszItem Pointer to the null-terminated string that is to be added. 
103  */
104 int CSuperComboBox::AddString(LPCTSTR lpszItem)
105 {
106         return InsertString(GetCount(), lpszItem);
107 }
108
109 /**
110  * @brief Inserts a string into the list box of a combo box.
111  * @param nIndex The zero-based index to the position in the list box that receives the string.
112  * @param lpszItem Pointer to the null-terminated string that is to be added. 
113  */
114 int CSuperComboBox::InsertString(int nIndex, LPCTSTR lpszItem)
115 {
116         if (m_bComboBoxEx)
117         {
118                 CString sShortName;             // scoped to remain valid for calling CComboBoxEx::InsertItem()
119                 if (m_bExtendedFileNames)
120                 {
121                         if (nIndex >= static_cast<int>(m_sFullStateText.size()))
122                                 m_sFullStateText.resize(nIndex + 10);
123                         sShortName = m_sFullStateText[nIndex] = lpszItem;
124
125                         const int nPartLen = 72;
126                         if (sShortName.GetLength() > (nPartLen*2+8)) 
127                         {
128                                 if (sShortName.Left(4) == _T("\\\\?\\"))
129                                         sShortName.Delete(0, 4);
130                                 else
131                                 if (sShortName.Left(8) == _T("\\\\?\\UNC\\"))
132                                         sShortName.Delete(1, 6);
133                                 CString sL = sShortName.Left(nPartLen);
134                                 int nL = sL.ReverseFind(_T('\\'));
135                                 if (nL > 0) sL = sL.Left(nL+1);
136
137                                 CString sR = sShortName.Right(nPartLen);
138                                 int nR = sR.Find(_T('\\'));
139                                 if (nR > 0) sR = sR.Right(sR.GetLength() - nR);
140
141                                 sShortName = sL + _T(" ... ") + sR;
142                                 lpszItem = (LPCTSTR)sShortName;
143                         }
144                 }
145                 COMBOBOXEXITEM cbitem = {0};
146                 cbitem.mask = CBEIF_TEXT |
147                         (m_bHasImageList ? CBEIF_IMAGE|CBEIF_SELECTEDIMAGE : 0);
148                 cbitem.pszText = (LPTSTR)lpszItem;
149                 cbitem.cchTextMax = (int)_tcslen(lpszItem);
150                 cbitem.iItem = nIndex;
151                 cbitem.iImage = I_IMAGECALLBACK;
152                 cbitem.iSelectedImage = I_IMAGECALLBACK;
153                 return CComboBoxEx::InsertItem(&cbitem);
154         }
155         else
156         {
157                 return CComboBox::InsertString(nIndex, lpszItem);
158         }
159 }
160
161 int CSuperComboBox::DeleteString(int nIndex)
162 {
163         if (m_bComboBoxEx && m_bExtendedFileNames &&
164             nIndex >= 0 && nIndex < static_cast<int>(m_sFullStateText.size()))
165         {
166                 m_sFullStateText.erase(m_sFullStateText.begin() + nIndex);
167         }
168         return CComboBoxEx::DeleteString(nIndex);
169 }
170
171 int CSuperComboBox::FindString(int nStartAfter, LPCTSTR lpszString) const
172 {
173         
174         if (m_bComboBoxEx)
175         {
176                 ASSERT(m_bExtendedFileNames);
177                 CString sSearchString = lpszString;
178                 int nSearchStringLen = sSearchString.GetLength();
179                 if (nSearchStringLen <= 0)
180                         return CB_ERR;
181                 int nLimit = static_cast<int>(m_sFullStateText.size());
182                 for (int i = nStartAfter+1; i < nLimit; i++)
183                 {
184                         CString sListString = m_sFullStateText[i];
185                         int nListStringLen = sListString.GetLength();
186                         if (nSearchStringLen <= nListStringLen && sSearchString.CompareNoCase(sListString.Left(nSearchStringLen))==0)
187                                 return i;
188                 }
189                 return CB_ERR;
190         }
191         else
192         {
193                 return CComboBox::FindString(nStartAfter, lpszString);
194         }
195 }
196
197 /**
198  * @brief Gets the system image list and attaches the image list to a combo box control.
199  */
200 bool CSuperComboBox::AttachSystemImageList()
201 {
202         ASSERT(m_bComboBoxEx);
203         if (m_himlSystem==nullptr)
204         {
205                 SHFILEINFO sfi = {0};
206                 m_himlSystem = (HIMAGELIST)SHGetFileInfo(_T(""), 0, 
207                         &sfi, sizeof(sfi), SHGFI_SMALLICON | SHGFI_SYSICONINDEX);
208                 if (m_himlSystem==nullptr)
209                         return false;
210         }
211         SetImageList(CImageList::FromHandle(m_himlSystem));
212         m_bHasImageList = true;
213         return true;
214 }
215
216 void CSuperComboBox::LoadState(LPCTSTR szRegSubKey)
217 {
218         ResetContent();
219
220         int cnt = AfxGetApp()->GetProfileInt(szRegSubKey, _T("Count"), 0);
221         int idx = 0;
222         for (int i=0; i < cnt && idx < m_nMaxItems; i++)
223         {
224                 CString s,s2;
225                 s2.Format(_T("Item_%d"), i);
226                 s = AfxGetApp()->GetProfileString(szRegSubKey, s2);
227                 if (FindStringExact(-1, s) == CB_ERR && !s.IsEmpty())
228                 {
229                         AddString(s);
230                         idx++;
231                 }
232         }
233         if (idx > 0)
234         {
235                 bool bIsEmpty = (m_bCanBeEmpty ? (AfxGetApp()->GetProfileInt(szRegSubKey, _T("Empty"), FALSE) == TRUE) : false);
236                 if (bIsEmpty)
237                 {
238                         SetCurSel(-1);
239                 }
240                 else
241                 {
242                         SetCurSel(0);
243                         if (m_bExtendedFileNames)
244                                 GetEditCtrl()->SetWindowText(m_sFullStateText[0]);
245                 }
246         }
247 }
248
249 void CSuperComboBox::GetLBText(int nIndex, CString &rString) const
250 {
251         ASSERT(::IsWindow(m_hWnd));
252
253         if (m_bExtendedFileNames)
254         {
255                 rString = m_sFullStateText[nIndex];
256         }
257         else
258         {
259                 CComboBoxEx::GetLBText(nIndex, rString.GetBufferSetLength(GetLBTextLen(nIndex)));
260                 rString.ReleaseBuffer();
261         }
262 }
263
264 int CSuperComboBox::GetLBTextLen(int nIndex) const
265 {
266         if (m_bExtendedFileNames)
267         {
268                 return m_sFullStateText[nIndex].GetLength();
269         }
270         else
271         {
272                 return CComboBoxEx::GetLBTextLen(nIndex);
273         }
274 }
275
276 /** 
277  * @brief Saves strings in combobox.
278  * This function saves strings in combobox, in editbox and in dropdown.
279  * Whitespace characters are stripped from begin and end of the strings
280  * before saving. Empty strings are not saved. So strings which have only
281  * whitespace characters aren't save either.
282  * @param [in] szRegSubKey Registry subkey where to save strings.
283  * @param [in] bCanBeEmpty
284  * @param [in] nMaxItems Max number of strings to save.
285  */
286 void CSuperComboBox::SaveState(LPCTSTR szRegSubKey)
287 {
288         CString strItem;
289         if (m_bComboBoxEx)
290                 GetEditCtrl()->GetWindowText(strItem);
291         else
292                 GetWindowText(strItem);
293         strItem.TrimLeft();
294         strItem.TrimRight();
295
296         int idx = 0;
297         if (!strItem.IsEmpty())
298         {
299                 AfxGetApp()->WriteProfileString(szRegSubKey, _T("Item_0"), strItem);
300                 idx=1;
301         }
302
303         int cnt = GetCount();
304         for (int i=0; i < cnt && idx < m_nMaxItems; i++)
305         {               
306                 CString s;
307                 GetLBText(i, s);
308                 s.TrimLeft();
309                 s.TrimRight();
310                 if (s != strItem && !s.IsEmpty())
311                 {
312                         CString s2;
313                         s2.Format(_T("Item_%d"), idx);
314                         AfxGetApp()->WriteProfileString(szRegSubKey, s2, s);
315                         idx++;
316                 }
317         }
318         AfxGetApp()->WriteProfileInt(szRegSubKey, _T("Count"), idx);
319         
320         if (m_bCanBeEmpty)
321                 AfxGetApp()->WriteProfileInt(szRegSubKey, _T("Empty"), strItem.IsEmpty());
322 }
323
324 BOOL CSuperComboBox::OnEditchange()
325 {
326         if (m_bHasImageList)
327         {
328                 // Trigger a WM_WINDOWPOSCHANGING to help the client area receive an update trough WM_DRAWITEM
329                 SetWindowPos(NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOREDRAW | SWP_NOACTIVATE);
330         }
331
332         // bail if not auto completing 
333         if (!m_bDoComplete) 
334                 return FALSE;
335         
336         int length = GetWindowTextLength();
337
338         // bail if no text
339         if (length <= 0) 
340                 return FALSE;
341         
342         if (m_bInEditchange)
343                 return FALSE;
344         m_bInEditchange = true;
345
346         // Get the text in the edit box
347         CString s;
348         GetWindowText(s);
349         
350         // get the current selection
351         DWORD sel = GetEditSel();
352         int start = (short)LOWORD(sel), end = (short)HIWORD(sel);
353         
354         // look for the string that is prefixed by the typed text
355         int idx = FindString(-1, s);
356         if (idx != CB_ERR)
357         {
358                 // set the new string
359                 CString strNew;
360                 GetLBText(idx, strNew);
361                 SetWindowText(strNew);
362         }
363         
364         // select the text after our typing
365         if (sel == CB_ERR || end >= length)
366         {
367                 start = length;
368                 end = -1;
369         }
370
371         // get the caret back in the right spot
372         if (m_bComboBoxEx)
373                 GetEditCtrl()->SetSel(start, end);
374         else
375                 CComboBox::SetEditSel(start, end);  
376
377         m_bInEditchange = false;
378
379         return FALSE;
380 }
381
382 BOOL CSuperComboBox::OnSetfocus()
383 {
384         if (m_bHasImageList)
385                 GetEditCtrl()->SetModify(FALSE);
386
387         return FALSE;
388 }
389
390 BOOL CSuperComboBox::PreTranslateMessage(MSG* pMsg)
391 {
392     if (pMsg->message == WM_KEYDOWN)
393     {
394                 int nVirtKey = (int) pMsg->wParam;
395                 // If Shift+Del pressed when dropdown is open, delete selected item
396                 // from dropdown list
397                 if (GetAsyncKeyState(VK_SHIFT))
398                 {
399                         if (GetDroppedState() && nVirtKey == VK_DELETE)
400                         {
401                                 int cursel = GetCurSel();
402                                 if (cursel != CB_ERR)
403                                 {
404                                         DeleteString(cursel);
405                                         if (cursel >= GetCount())
406                                                 cursel = GetCount() - 1;
407                                         if (cursel >= 0)
408                                                 SetCurSel(cursel);
409                                 }
410                                 return FALSE; // No need to further handle this message
411                         }
412                 }
413                 if (m_bAutoComplete)
414                 {
415                         m_bDoComplete = true;
416
417                         if (nVirtKey == VK_DELETE || nVirtKey == VK_BACK)
418                                         m_bDoComplete = false;
419                 }
420     }
421
422     return CComboBoxEx::PreTranslateMessage(pMsg);
423 }
424
425 void CSuperComboBox::SetAutoComplete(INT nSource)
426 {
427         switch (nSource)
428         {
429                 case AUTO_COMPLETE_DISABLED:
430                         m_bAutoComplete = false;
431                         break;
432
433                 case AUTO_COMPLETE_FILE_SYSTEM:
434                 {
435                         // Disable the build-in auto-completion and use the Windows
436                         // shell functionality.
437                         m_bAutoComplete = false;
438
439                         // ComboBox's edit control is always 1001.
440                         CWnd *pWnd = m_bComboBoxEx ? this->GetEditCtrl() : GetDlgItem(1001);
441                         ASSERT(pWnd != nullptr);
442                         SHAutoComplete(pWnd->m_hWnd, SHACF_FILESYSTEM);
443                         break;
444                 }
445
446                 case AUTO_COMPLETE_RECENTLY_USED:
447                         m_bAutoComplete = true;
448                         break;
449
450                 default:
451                         ASSERT(!"Unknown AutoComplete source.");
452                         m_bAutoComplete = false;
453         }
454 }
455
456 void CSuperComboBox::ResetContent()
457 {
458         if (m_bExtendedFileNames)
459         {
460                 m_sFullStateText.resize(m_nMaxItems);
461                 for (int i = 0; i < m_nMaxItems; i++)
462                 {
463                         m_sFullStateText[i].Empty();
464                 }
465         }
466         CComboBoxEx::ResetContent();
467 }
468
469 int CSuperComboBox::OnCreate(LPCREATESTRUCT lpCreateStruct) 
470 {
471         if (CComboBoxEx::OnCreate(lpCreateStruct) == -1)
472                 return -1;
473         
474         m_pDropHandler = new DropHandler(std::bind(&CSuperComboBox::OnDropFiles, this, std::placeholders::_1));
475         RegisterDragDrop(m_hWnd, m_pDropHandler);
476         return 0;
477 }
478
479 void CSuperComboBox::OnDestroy(void)
480 {
481         if (m_pDropHandler != nullptr)
482                 RevokeDragDrop(m_hWnd);
483 }
484
485 /////////////////////////////////////////////////////////////////////////////
486 //
487 //      OnDropFiles code from CDropEdit
488 //      Copyright 1997 Chris Losinger
489 //
490 //      shortcut expansion code modified from :
491 //      CShortcut, 1996 Rob Warner
492 //
493 void CSuperComboBox::OnDropFiles(const std::vector<String>& files)
494 {
495         GetParent()->SendMessage(WM_COMMAND, GetDlgCtrlID() +
496                 (CBN_EDITUPDATE << 16), (LPARAM)m_hWnd);
497         SetWindowText(files[0].c_str());
498         GetParent()->SendMessage(WM_COMMAND, GetDlgCtrlID() +
499                 (CBN_EDITCHANGE << 16), (LPARAM)m_hWnd);
500 }
501
502 static DWORD WINAPI SHGetFileInfoThread(LPVOID pParam)
503 {
504         CString &sPath = reinterpret_cast<CString &>(pParam);
505         SHFILEINFO sfi = {0};
506         // If SHGetFileInfo() fails, intentionally leave sfi.iIcon as 0 (indicating
507         // a file of inspecific type) so as to not obstruct CBEIF_DI_SETITEM logic.
508         if (!sPath.IsEmpty())
509         {
510                 if (SUCCEEDED(CoInitialize(nullptr)))
511                 {
512                         SHGetFileInfo(sPath, 0, &sfi, sizeof(sfi), SHGFI_SYSICONINDEX);
513                         CoUninitialize();
514                 }
515         }
516         sPath.~CString();
517         return sfi.iIcon;
518 }
519
520 static int GetFileTypeIconIndex(LPVOID pParam)
521 {
522         CString &sText = reinterpret_cast<CString &>(pParam);
523         DWORD dwIconIndex = 0;
524         bool isNetworkDrive = false;
525         if (sText.GetLength() >= 2 && (sText[1] == L'\\'))
526         {
527                 if (sText.GetLength() > 4 && sText.Left(4) == L"\\\\?\\")
528                         if (sText.GetLength() > 8 && sText.Left(8) == L"\\\\?\\UNC\\")
529                                 isNetworkDrive = true;
530                         else
531                                 isNetworkDrive = false; // Just a Long File Name indicator
532                 else
533                         isNetworkDrive = true;
534         }
535         else
536         if (sText.GetLength() >= 3 && GetDriveType(sText.Left(3)) == DRIVE_REMOTE)
537                 isNetworkDrive = true;  // Drive letter, but mapped to Remote UNC device.
538
539         // Unless execution drops into the final else block,
540         // SHGetFileInfoThread() takes ownership of, and will eventually trash sText
541         if (!isNetworkDrive)
542         {
543                 dwIconIndex = SHGetFileInfoThread(pParam);
544         }
545         else
546         if (HANDLE hThread = CreateThread(nullptr, 0, SHGetFileInfoThread, pParam, 0, nullptr))
547         {
548                 // The path is a network path. 
549                 // Try to get the index of a system image list icon, with 1-sec timeout.
550
551                 DWORD dwResult = WaitForSingleObject(hThread, 1000);
552                 if (dwResult == WAIT_OBJECT_0)
553                 {
554                         GetExitCodeThread(hThread, &dwIconIndex);
555                 }
556                 CloseHandle(hThread);
557         }
558         else
559         {
560                 // Ownership of sText was retained, so trash it here
561                 sText.~CString();
562         }
563         return static_cast<int>(dwIconIndex);
564 }
565
566 void CSuperComboBox::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
567 {
568         if ((lpDrawItemStruct->itemState & ODS_COMBOBOXEDIT) != 0 && m_bHasImageList)
569         {
570                 LPVOID pvText;
571                 CString &sText = placement_cast<CString>(&pvText);
572                 CEdit *const pEdit = GetEditCtrl();
573                 if (!pEdit->GetModify() || GetFocus() != pEdit)
574                         GetWindowText(sText);
575                 int iIcon = GetFileTypeIconIndex(pvText);
576                 ImageList_DrawEx(m_himlSystem, iIcon, lpDrawItemStruct->hDC,
577                         lpDrawItemStruct->rcItem.left, lpDrawItemStruct->rcItem.top,
578                         0, 0, GetSysColor(COLOR_WINDOW), CLR_NONE, ILD_NORMAL);
579                 return;
580         }
581         CComboBoxEx::OnDrawItem(nIDCtl, lpDrawItemStruct);
582 }
583
584 /**
585  * @brief A message handler for CBEN_GETDISPINFO message
586  */
587 void CSuperComboBox::OnGetDispInfo(NMHDR *pNotifyStruct, LRESULT *pResult)
588 {
589         NMCOMBOBOXEX *pDispInfo = (NMCOMBOBOXEX *)pNotifyStruct;
590         if (pDispInfo && pDispInfo->ceItem.pszText && m_bHasImageList)
591         {
592                 pDispInfo->ceItem.mask |= CBEIF_DI_SETITEM;
593                 LPVOID pvText;
594                 GetLBText(static_cast<int>(pDispInfo->ceItem.iItem), placement_cast<CString>(&pvText));
595                 int iIcon = GetFileTypeIconIndex(pvText);
596                 pDispInfo->ceItem.iImage = iIcon;
597                 pDispInfo->ceItem.iSelectedImage = iIcon;
598         }
599         *pResult = 0;
600 }