OSDN Git Service

Improve label text on the Open Dialog View tab
[winmerge-jp/winmerge-jp.git] / Src / OpenView.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 //    WinMerge:  an interactive diff/merge utility
3 //    Copyright (C) 1997-2000  Thingamahoochie Software
4 //    Author: Dean Grimm
5 //
6 //    This program is free software; you can redistribute it and/or modify
7 //    it under the terms of the GNU General Public License as published by
8 //    the Free Software Foundation; either version 2 of the License, or
9 //    (at your option) any later version.
10 //
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.
15 //
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
18 //    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 //
20 /////////////////////////////////////////////////////////////////////////////
21 /** 
22  * @file  OpenView.cpp
23  *
24  * @brief Implementation of the COpenView class
25  */
26
27 #include "stdafx.h"
28 #include "OpenView.h"
29 #include <vector>
30 #include <sys/stat.h>
31 #include "UnicodeString.h"
32 #include "Merge.h"
33 #include "OpenDoc.h"
34 #include "ProjectFile.h"
35 #include "paths.h"
36 #include "SelectUnpackerDlg.h"
37 #include "OptionsDef.h"
38 #include "MainFrm.h"
39 #include "OptionsMgr.h"
40 #include "FileOrFolderSelect.h"
41 #include "7zCommon.h"
42 #include "Constants.h"
43 #include "Picture.h"
44 #include "DropHandler.h"
45 #include "FileFilterHelper.h"
46 #include "Plugins.h"
47 #include "BCMenu.h"
48
49 #ifdef _DEBUG
50 #define new DEBUG_NEW
51 #endif
52
53 #ifndef BCN_DROPDOWN
54 #define BCN_DROPDOWN            (BCN_FIRST + 0x0002)
55 #endif
56
57 // Timer ID and timeout for delaying path validity check
58 const UINT IDT_CHECKFILES = 1;
59 const UINT CHECKFILES_TIMEOUT = 1000; // milliseconds
60 static const TCHAR EMPTY_EXTENSION[] = _T(".*");
61
62 /** @brief Location for Open-dialog specific help to open. */
63 static TCHAR OpenDlgHelpLocation[] = _T("::/htmlhelp/Open_paths.html");
64
65 // COpenView
66
67 IMPLEMENT_DYNCREATE(COpenView, CFormView)
68
69 BEGIN_MESSAGE_MAP(COpenView, CFormView)
70         //{{AFX_MSG_MAP(COpenView)
71         ON_BN_CLICKED(IDC_PATH0_BUTTON, OnPathButton<0>)
72         ON_BN_CLICKED(IDC_PATH1_BUTTON, OnPathButton<1>)
73         ON_BN_CLICKED(IDC_PATH2_BUTTON, OnPathButton<2>)
74         ON_BN_CLICKED(IDC_SWAP01_BUTTON, (OnSwapButton<IDC_PATH0_COMBO, IDC_PATH1_COMBO>))
75         ON_BN_CLICKED(IDC_SWAP12_BUTTON, (OnSwapButton<IDC_PATH1_COMBO, IDC_PATH2_COMBO>))
76         ON_BN_CLICKED(IDC_SWAP02_BUTTON, (OnSwapButton<IDC_PATH0_COMBO, IDC_PATH2_COMBO>))
77         ON_CBN_SELCHANGE(IDC_PATH0_COMBO, OnSelchangePathCombo<0>)
78         ON_CBN_SELCHANGE(IDC_PATH1_COMBO, OnSelchangePathCombo<1>)
79         ON_CBN_SELCHANGE(IDC_PATH2_COMBO, OnSelchangePathCombo<2>)
80         ON_CBN_EDITCHANGE(IDC_PATH0_COMBO, OnEditEvent)
81         ON_CBN_EDITCHANGE(IDC_PATH1_COMBO, OnEditEvent)
82         ON_CBN_EDITCHANGE(IDC_PATH2_COMBO, OnEditEvent)
83         ON_BN_CLICKED(IDC_SELECT_UNPACKER, OnSelectUnpacker)
84         ON_CBN_SELENDCANCEL(IDC_PATH0_COMBO, UpdateButtonStates)
85         ON_CBN_SELENDCANCEL(IDC_PATH1_COMBO, UpdateButtonStates)
86         ON_CBN_SELENDCANCEL(IDC_PATH2_COMBO, UpdateButtonStates)
87         ON_NOTIFY_RANGE(CBEN_BEGINEDIT, IDC_PATH0_COMBO, IDC_PATH2_COMBO, OnSetfocusPathCombo)
88         ON_WM_TIMER()
89         ON_BN_CLICKED(IDC_SELECT_FILTER, OnSelectFilter)
90         ON_BN_CLICKED(IDC_OPTIONS, OnOptions)
91         ON_NOTIFY(BCN_DROPDOWN, IDC_OPTIONS, OnDropDownOptions)
92         ON_WM_ACTIVATE()
93         ON_COMMAND(ID_LOAD_PROJECT, OnLoadProject)
94         ON_COMMAND(ID_SAVE_PROJECT, OnSaveProject)
95         ON_NOTIFY(BCN_DROPDOWN, ID_SAVE_PROJECT, OnDropDownSaveProject)
96         ON_COMMAND(IDOK, OnOK)
97         ON_COMMAND(IDCANCEL, OnCancel)
98         ON_COMMAND(ID_HELP, OnHelp)
99         ON_COMMAND(ID_EDIT_COPY, OnEditAction<WM_COPY>)
100         ON_COMMAND(ID_EDIT_PASTE, OnEditAction<WM_PASTE>)
101         ON_COMMAND(ID_EDIT_CUT, OnEditAction<WM_CUT>)
102         ON_COMMAND(ID_EDIT_UNDO, OnEditAction<WM_UNDO>)
103         ON_COMMAND(ID_EDIT_SELECT_ALL, (OnEditAction<EM_SETSEL, 0, -1>))
104         ON_MESSAGE(WM_USER + 1, OnUpdateStatus)
105         ON_WM_PAINT()
106         ON_WM_LBUTTONDOWN()
107         ON_WM_WINDOWPOSCHANGED()
108         ON_WM_SETCURSOR()
109         ON_WM_DESTROY()
110         //}}AFX_MSG_MAP
111 END_MESSAGE_MAP()
112
113 // COpenView construction/destruction
114
115 COpenView::COpenView()
116         : CFormView(COpenView::IDD)
117         , m_pUpdateButtonStatusThread(NULL)
118         , m_bRecurse(FALSE)
119         , m_pDropHandler(NULL)
120         , m_dwFlags()
121         , m_bAutoCompleteReady()
122 {
123 }
124
125 COpenView::~COpenView()
126 {
127         TerminateThreadIfRunning();
128 }
129
130 void COpenView::DoDataExchange(CDataExchange* pDX)
131 {
132         CFormView::DoDataExchange(pDX);
133         //{{AFX_DATA_MAP(COpenView)
134         DDX_Control(pDX, IDC_EXT_COMBO, m_ctlExt);
135         DDX_Control(pDX, IDC_PATH0_COMBO, m_ctlPath[0]);
136         DDX_Control(pDX, IDC_PATH1_COMBO, m_ctlPath[1]);
137         DDX_Control(pDX, IDC_PATH2_COMBO, m_ctlPath[2]);
138         DDX_CBStringExact(pDX, IDC_PATH0_COMBO, m_strPath[0]);
139         DDX_CBStringExact(pDX, IDC_PATH1_COMBO, m_strPath[1]);
140         DDX_CBStringExact(pDX, IDC_PATH2_COMBO, m_strPath[2]);
141         DDX_Check(pDX, IDC_PATH0_READONLY, m_bReadOnly[0]);
142         DDX_Check(pDX, IDC_PATH1_READONLY, m_bReadOnly[1]);
143         DDX_Check(pDX, IDC_PATH2_READONLY, m_bReadOnly[2]);
144         DDX_Check(pDX, IDC_RECURS_CHECK, m_bRecurse);
145         DDX_CBStringExact(pDX, IDC_EXT_COMBO, m_strExt);
146         DDX_Text(pDX, IDC_UNPACKER_EDIT, m_strUnpacker);
147         //}}AFX_DATA_MAP
148 }
149
150 BOOL COpenView::PreCreateWindow(CREATESTRUCT& cs)
151 {
152         // TODO: Modify the Window class or styles here by modifying
153         //  the CREATESTRUCT cs
154         cs.style &= ~WS_BORDER;
155         cs.dwExStyle &= ~WS_EX_CLIENTEDGE;
156         return CFormView::PreCreateWindow(cs);
157 }
158
159 void COpenView::OnInitialUpdate()
160 {
161         m_sizeOrig = GetTotalSize();
162
163         theApp.TranslateDialog(m_hWnd);
164
165         if (!m_picture.Load(IDR_LOGO))
166                 return;
167
168         CFormView::OnInitialUpdate();
169         ResizeParentToFit();
170
171         // set caption to "swap paths" button
172         LOGFONT lf;
173         GetDlgItem(IDC_SWAP01_BUTTON)->GetFont()->GetObject(sizeof(lf), &lf);
174         lf.lfCharSet = SYMBOL_CHARSET;
175         lstrcpy(lf.lfFaceName, _T("Wingdings"));
176         m_fontSwapButton.CreateFontIndirect(&lf);
177         const int ids[] = {IDC_SWAP01_BUTTON, IDC_SWAP12_BUTTON, IDC_SWAP02_BUTTON};
178         for (int i = 0; i < sizeof(ids)/sizeof(ids[0]); ++i)
179         {
180                 GetDlgItem(ids[i])->SetFont(&m_fontSwapButton);
181                 SetDlgItemText(ids[i], _T("\xf4"));
182         }
183
184         m_constraint.InitializeCurrentSize(this);
185         m_constraint.InitializeSpecificSize(this, m_sizeOrig.cx, m_sizeOrig.cy);
186         m_constraint.SetMaxSizePixels(-1, m_sizeOrig.cy);
187         m_constraint.SetScrollScale(this, 1.0, 1.0);
188         m_constraint.SetSizeGrip(prdlg::CMoveConstraint::SG_NONE);
189         // configure how individual controls adjust when dialog resizes
190         m_constraint.ConstrainItem(IDC_PATH0_COMBO, 0, 1, 0, 0); // grows right
191         m_constraint.ConstrainItem(IDC_PATH1_COMBO, 0, 1, 0, 0); // grows right
192         m_constraint.ConstrainItem(IDC_PATH2_COMBO, 0, 1, 0, 0); // grows right
193         m_constraint.ConstrainItem(IDC_EXT_COMBO, 0, 1, 0, 0); // grows right
194         m_constraint.ConstrainItem(IDC_UNPACKER_EDIT, 0, 1, 0, 0); // grows right
195         m_constraint.ConstrainItem(IDC_FILES_DIRS_GROUP, 0, 1, 0, 0); // grows right
196         m_constraint.ConstrainItem(IDC_PATH0_BUTTON, 1, 0, 0, 0); // slides right
197         m_constraint.ConstrainItem(IDC_PATH1_BUTTON, 1, 0, 0, 0); // slides right
198         m_constraint.ConstrainItem(IDC_PATH2_BUTTON, 1, 0, 0, 0); // slides right
199         m_constraint.ConstrainItem(IDC_PATH0_READONLY, 1, 0, 0, 0); // slides right
200         m_constraint.ConstrainItem(IDC_PATH1_READONLY, 1, 0, 0, 0); // slides right
201         m_constraint.ConstrainItem(IDC_PATH2_READONLY, 1, 0, 0, 0); // slides right
202         m_constraint.ConstrainItem(IDC_SWAP01_BUTTON, 1, 0, 0, 0); // slides right
203         m_constraint.ConstrainItem(IDC_SWAP12_BUTTON, 1, 0, 0, 0); // slides right
204         m_constraint.ConstrainItem(IDC_SWAP02_BUTTON, 1, 0, 0, 0); // slides right
205         m_constraint.ConstrainItem(IDC_SELECT_UNPACKER, 1, 0, 0, 0); // slides right
206         m_constraint.ConstrainItem(IDC_OPEN_STATUS, 0, 1, 0, 0); // grows right
207         m_constraint.ConstrainItem(IDC_SELECT_FILTER, 1, 0, 0, 0); // slides right
208         m_constraint.ConstrainItem(IDC_OPTIONS, 1, 0, 0, 0); // slides right
209         m_constraint.ConstrainItem(ID_SAVE_PROJECT, 1, 0, 0, 0); // slides right
210         m_constraint.ConstrainItem(IDOK, 1, 0, 0, 0); // slides right
211         m_constraint.ConstrainItem(IDCANCEL, 1, 0, 0, 0); // slides right
212         m_constraint.ConstrainItem(ID_HELP, 1, 0, 0, 0); // slides right
213         m_constraint.DisallowHeightGrowth();
214         //m_constraint.SubclassWnd(); // install subclassing
215
216         m_constraint.LoadPosition(_T("ResizeableDialogs"), _T("OpenView"), false); // persist size via registry
217         m_constraint.UpdateSizes();
218
219         COpenDoc *pDoc = GetDocument();
220
221         CString strTitle;
222         GetWindowText(strTitle);
223         pDoc->SetTitle(strTitle);
224
225         m_files = pDoc->m_files;
226         m_bRecurse = pDoc->m_bRecurse;
227         m_strExt = pDoc->m_strExt;
228         m_strUnpacker = pDoc->m_strUnpacker;
229         m_infoHandler = pDoc->m_infoHandler;
230         m_dwFlags[0] = pDoc->m_dwFlags[0];
231         m_dwFlags[1] = pDoc->m_dwFlags[1];
232         m_dwFlags[2] = pDoc->m_dwFlags[2];
233
234         m_ctlPath[0].SetFileControlStates();
235         m_ctlPath[1].SetFileControlStates();
236         m_ctlPath[2].SetFileControlStates(true);
237
238         for (int file = 0; file < m_files.GetSize(); file++)
239         {
240                 m_strPath[file] = m_files[file];
241                 m_ctlPath[file].SetWindowText(m_files[file].c_str());
242                 m_bReadOnly[file] = (m_dwFlags[file] & FFILEOPEN_READONLY) != 0;
243         }
244
245         m_ctlPath[0].AttachSystemImageList();
246         m_ctlPath[1].AttachSystemImageList();
247         m_ctlPath[2].AttachSystemImageList();
248         LoadComboboxStates();
249
250         BOOL bDoUpdateData = TRUE;
251         for (int index = 0; index < countof(m_strPath); index++)
252         {
253                 if (!m_strPath[index].empty())
254                         bDoUpdateData = FALSE;
255         }
256         UpdateData(bDoUpdateData);
257
258         String filterNameOrMask = theApp.m_pGlobalFileFilter->GetFilterNameOrMask();
259         BOOL bMask = theApp.m_pGlobalFileFilter->IsUsingMask();
260
261         if (!bMask)
262         {
263                 String filterPrefix = _("[F] ");
264                 filterNameOrMask = filterPrefix + filterNameOrMask;
265         }
266
267         int ind = m_ctlExt.FindStringExact(0, filterNameOrMask.c_str());
268         if (ind != CB_ERR)
269                 m_ctlExt.SetCurSel(ind);
270         else
271         {
272                 ind = m_ctlExt.InsertString(0, filterNameOrMask.c_str());
273                 if (ind != CB_ERR)
274                         m_ctlExt.SetCurSel(ind);
275                 else
276                         LogErrorString(_T("Failed to add string to filters combo list!"));
277         }
278
279         if (!GetOptionsMgr()->GetBool(OPT_VERIFY_OPEN_PATHS))
280         {
281                 EnableDlgItem(IDOK, true);
282                 EnableDlgItem(IDC_UNPACKER_EDIT, true);
283                 EnableDlgItem(IDC_SELECT_UNPACKER, true);
284         }
285
286         UpdateButtonStates();
287
288         BOOL bOverwriteRecursive = FALSE;
289         if (m_dwFlags[0] & FFILEOPEN_PROJECT || m_dwFlags[1] & FFILEOPEN_PROJECT)
290                 bOverwriteRecursive = TRUE;
291         if (m_dwFlags[0] & FFILEOPEN_CMDLINE || m_dwFlags[1] & FFILEOPEN_CMDLINE)
292                 bOverwriteRecursive = TRUE;
293         if (!bOverwriteRecursive)
294                 m_bRecurse = GetOptionsMgr()->GetBool(OPT_CMP_INCLUDE_SUBDIRS);
295
296         m_strUnpacker = m_infoHandler.pluginName;
297         UpdateData(FALSE);
298         SetStatus(IDS_OPEN_FILESDIRS);
299         SetUnpackerStatus(IDS_OPEN_UNPACKERDISABLED);
300
301         m_pDropHandler = new DropHandler(std::bind(&COpenView::OnDropFiles, this, std::placeholders::_1));
302         RegisterDragDrop(m_hWnd, m_pDropHandler);
303 }
304
305 void COpenView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
306 {
307         m_bRecurse = GetDocument()->m_bRecurse;
308         UpdateData(FALSE);
309 }
310
311 // COpenView diagnostics
312
313 #ifdef _DEBUG
314 COpenDoc* COpenView::GetDocument() const // non-debug version is inline
315 {
316         ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(COpenDoc)));
317         return (COpenDoc*)m_pDocument;
318 }
319 #endif //_DEBUG
320
321
322 /////////////////////////////////////////////////////////////////////////////
323 // COpenView message handlers
324
325 void COpenView::OnPaint()
326 {
327         CPaintDC dc(this);
328         CSize size = m_picture.GetImageSize(&dc);
329         CRect rcImage(0, 0, size.cx * GetSystemMetrics(SM_CXSMICON) / 16, size.cy * GetSystemMetrics(SM_CYSMICON) / 16);
330         CRect rc;
331         m_picture.Render(&dc, rcImage);
332         GetClientRect(&rc);
333     dc.PatBlt(rcImage.Width(), 0, rc.Width() - rcImage.Width(), rcImage.Height(), PATCOPY);
334
335         rc.left = rc.right - GetSystemMetrics(SM_CXVSCROLL);
336         rc.top = rc.bottom - GetSystemMetrics(SM_CYHSCROLL);
337         dc.DrawFrameControl(&rc, DFC_SCROLL, DFCS_SCROLLSIZEGRIP);
338
339         CFormView::OnPaint();
340 }
341
342 void COpenView::OnLButtonDown(UINT nFlags, CPoint point)
343 {
344
345         if (m_rectTracker.Track(this, point, FALSE, GetParentFrame()))
346         {
347                 CRect rc = m_rectTracker.m_rect;
348                 MapWindowPoints(GetParentFrame(), &rc);
349                 CRect rcFrame;
350                 GetParentFrame()->GetClientRect(&rcFrame);
351                 int width = rc.Width() > rcFrame.Width() ? rcFrame.Width() : rc.Width();
352                 if (width < m_sizeOrig.cx)
353                         width = m_sizeOrig.cx;
354                 rc.right = rc.left + width;
355                 rc.bottom = rc.top + m_sizeOrig.cy;
356                 m_rectTracker.m_rect.right = m_rectTracker.m_rect.left + width;
357                 m_rectTracker.m_rect.bottom = m_rectTracker.m_rect.top + m_sizeOrig.cy;
358                 SetWindowPos(NULL, rc.left, rc.top, rc.Width(), rc.Height(), SWP_NOZORDER);
359                 m_constraint.UpdateSizes();
360         }
361 }
362
363 void COpenView::OnWindowPosChanged(WINDOWPOS* lpwndpos)
364 {
365         CRect rc;
366         GetClientRect(&rc);
367         m_rectTracker.m_rect = rc;
368         CFormView::OnWindowPosChanged(lpwndpos);
369 }
370
371 void COpenView::OnDestroy()
372 {
373         if (m_pDropHandler)
374                 RevokeDragDrop(m_hWnd);
375
376         m_constraint.Persist(true, false);
377
378         CFormView::OnDestroy();
379 }
380
381 BOOL COpenView::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
382 {
383         if (pWnd == this && m_rectTracker.SetCursor(this, nHitTest))
384                 return TRUE;
385
386         return CView::OnSetCursor(pWnd, nHitTest, message);
387 }
388
389 void COpenView::OnButton(int index)
390 {
391         String s;
392         String sfolder;
393         UpdateData(TRUE); 
394
395         paths::PATH_EXISTENCE existence = paths::DoesPathExist(m_strPath[index]);
396         switch (existence)
397         {
398         case paths::IS_EXISTING_DIR:
399                 sfolder = m_strPath[index];
400                 break;
401         case paths::IS_EXISTING_FILE:
402                 sfolder = paths::GetPathOnly(m_strPath[index]);
403                 break;
404         case paths::DOES_NOT_EXIST:
405                 // Do nothing, empty foldername will be passed to dialog
406                 break;
407         default:
408                 _RPTF0(_CRT_ERROR, "Invalid return value from paths::DoesPathExist()");
409                 break;
410         }
411
412         if (SelectFileOrFolder(GetSafeHwnd(), s, sfolder.c_str()))
413         {
414                 m_strPath[index] = s;
415                 m_strBrowsePath[index] = s;
416                 UpdateData(FALSE);
417                 UpdateButtonStates();
418         }       
419 }
420
421 /** 
422  * @brief Called when "Browse..." button is selected for N path.
423  */
424 template <int N>
425 void COpenView::OnPathButton()
426 {
427         OnButton(N);
428 }
429
430 template<int id1, int id2>
431 void COpenView::OnSwapButton() 
432 {
433         String s1, s2;
434         GetDlgItemText(id1, s1);
435         GetDlgItemText(id2, s2);
436         std::swap(s1, s2);
437         SetDlgItemText(id1, s1);
438         SetDlgItemText(id2, s2);
439 }
440
441 /** 
442  * @brief Called when dialog is closed with "OK".
443  *
444  * Checks that paths are valid and sets filters.
445  */
446 void COpenView::OnOK() 
447 {
448         int pathsType; // enum from paths::PATH_EXISTENCE in paths.h
449         const String filterPrefix = _("[F] ");
450
451         UpdateData(TRUE);
452         TrimPaths();
453
454         int index;
455         int nFiles = 0;
456         for (index = 0; index < countof(m_strPath); index++)
457         {
458                 if (index == 2 && m_strPath[index].empty())
459                         break;
460                 m_files.SetSize(nFiles + 1);
461                 m_files[nFiles] = m_strPath[index];
462                 m_dwFlags[nFiles] &= ~FFILEOPEN_READONLY;
463                 m_dwFlags[nFiles] |= m_bReadOnly[index] ? FFILEOPEN_READONLY : 0;
464                 nFiles++;
465         }
466         // If left path is a project-file, load it
467         String ext;
468         paths::SplitFilename(m_strPath[0], NULL, NULL, &ext);
469         if (m_strPath[1].empty() && strutils::compare_nocase(ext, ProjectFile::PROJECTFILE_EXT) == 0)
470                 LoadProjectFile(m_strPath[0]);
471
472         pathsType = paths::GetPairComparability(m_files, IsArchiveFile);
473
474         if (pathsType == paths::DOES_NOT_EXIST)
475         {
476                 LangMessageBox(IDS_ERROR_INCOMPARABLE, MB_ICONSTOP);
477                 return;
478         }
479
480         for (index = 0; index < nFiles; index++)
481         {
482                 // If user has edited path by hand, expand environment variables
483                 bool bExpand = false;
484                 if (strutils::compare_nocase(m_strBrowsePath[index], m_files[index]) != 0)
485                         bExpand = true;
486
487                 if (!paths::IsURLorCLSID(m_files[index]))
488                 {
489                         m_files[index] = paths::GetLongPath(m_files[index], bExpand);
490         
491                         // Add trailing '\' for directories if its missing
492                         if (paths::DoesPathExist(m_files[index]) == paths::IS_EXISTING_DIR)
493                                 m_files[index] = paths::AddTrailingSlash(m_files[index]);
494                         m_strPath[index] = m_files[index];
495                 }
496         }
497
498         UpdateData(FALSE);
499         KillTimer(IDT_CHECKFILES);
500
501         String filter(strutils::trim_ws(m_strExt));
502
503         // If prefix found from start..
504         if (filter.substr(0, filterPrefix.length()) == filterPrefix)
505         {
506                 // Remove prefix + space
507                 filter.erase(0, filterPrefix.length());
508                 if (!theApp.m_pGlobalFileFilter->SetFilter(filter))
509                 {
510                         // If filtername is not found use default *.* mask
511                         theApp.m_pGlobalFileFilter->SetFilter(_T("*.*"));
512                         filter = _T("*.*");
513                 }
514                 GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, filter);
515         }
516         else
517         {
518                 BOOL bFilterSet = theApp.m_pGlobalFileFilter->SetFilter(filter);
519                 if (!bFilterSet)
520                         m_strExt = theApp.m_pGlobalFileFilter->GetFilterNameOrMask();
521                 GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, filter);
522         }
523
524         SaveComboboxStates();
525         GetOptionsMgr()->SaveOption(OPT_CMP_INCLUDE_SUBDIRS, m_bRecurse);
526         LoadComboboxStates();
527
528         m_constraint.Persist(true, false);
529
530         COpenDoc *pDoc = GetDocument();
531         pDoc->m_files = m_files;
532         pDoc->m_bRecurse = m_bRecurse;
533         pDoc->m_strExt = m_strExt;
534         pDoc->m_strUnpacker = m_strUnpacker;
535         pDoc->m_infoHandler = m_infoHandler;
536         pDoc->m_dwFlags[0] = m_dwFlags[0];
537         pDoc->m_dwFlags[1] = m_dwFlags[1];
538         pDoc->m_dwFlags[2] = m_dwFlags[2];
539
540         if (GetOptionsMgr()->GetBool(OPT_CLOSE_WITH_OK))
541                 GetParentFrame()->PostMessage(WM_CLOSE);
542
543         PathContext tmpPathContext(pDoc->m_files);
544         PackingInfo tmpPackingInfo(pDoc->m_infoHandler);
545         GetMainFrame()->DoFileOpen(
546                 &tmpPathContext, std::array<DWORD, 3>(pDoc->m_dwFlags).data(), 
547                 NULL, _T(""), !!pDoc->m_bRecurse, NULL, _T(""), &tmpPackingInfo);
548 }
549
550 /** 
551  * @brief Called when dialog is closed via Cancel.
552  *
553  * Open-dialog is closed when `Cancel` button is selected or the
554  * `Esc` key is pressed.  Save combobox states, since user may have
555  * removed items from them (with `shift-del`) and doesn't want them 
556  * to re-appear.
557  * This is *not* called when the program is terminated, even if the 
558  * dialog is visible at the time.
559  */
560 void COpenView::OnCancel()
561 {
562         SaveComboboxStates();
563         AfxGetMainWnd()->PostMessage(WM_COMMAND, ID_FILE_CLOSE);
564 }
565
566 /** 
567  * @brief Callled when Open-button for project file is selected.
568  */
569 void COpenView::OnLoadProject()
570 {
571         String fileName = AskProjectFileName(true);
572         if (fileName.empty())
573                 return;
574
575         ProjectFile project;
576         if (!theApp.LoadProjectFile(fileName, project))
577                 return;
578
579         PathContext paths;
580         project.GetPaths(paths, m_bRecurse);
581         project.GetLeftReadOnly();
582         if (paths.size() < 3)
583         {
584                 m_strPath[0] = paths[0];
585                 m_strPath[1] = paths[1];
586                 m_strPath[2] = _T("");
587                 m_bReadOnly[0] = project.GetLeftReadOnly();
588                 m_bReadOnly[1] = project.GetRightReadOnly();
589                 m_bReadOnly[2] = false;
590         }
591         else
592         {
593                 m_strPath[0] = paths[0];
594                 m_strPath[1] = paths[1];
595                 m_strPath[2] = paths[2];
596                 m_bReadOnly[0] = project.GetLeftReadOnly();
597                 m_bReadOnly[1] = project.GetMiddleReadOnly();
598                 m_bReadOnly[2] = project.GetRightReadOnly();
599         }
600         m_strExt = project.GetFilter();
601
602         UpdateData(FALSE);
603         LangMessageBox(IDS_PROJFILE_LOAD_SUCCESS, MB_ICONINFORMATION);
604 }
605
606 /** 
607  * @brief Called when Save-button for project file is selected.
608  */
609 void COpenView::OnSaveProject()
610 {
611         UpdateData(TRUE);
612
613         String fileName = AskProjectFileName(false);
614         if (fileName.empty())
615                 return;
616
617         ProjectFile project;
618
619         if (!m_strPath[0].empty())
620                 project.SetLeft(m_strPath[0], &m_bReadOnly[0]);
621         if (m_strPath[2].empty())
622         {
623                 if (!m_strPath[1].empty())
624                         project.SetRight(m_strPath[1], &m_bReadOnly[1]);
625         }
626         else
627         {
628                 if (!m_strPath[1].empty())
629                         project.SetMiddle(m_strPath[1], &m_bReadOnly[1]);
630                 if (!m_strPath[2].empty())
631                         project.SetRight(m_strPath[2], &m_bReadOnly[2]);
632         }
633         if (!m_strExt.empty())
634         {
635                 // Remove possbile prefix from the filter name
636                 String prefix = _("[F] ");
637                 String strExt = m_strExt;
638                 size_t ind = strExt.find(prefix, 0);
639                 if (ind == 0)
640                 {
641                         strExt.erase(0, prefix.length());
642                 }
643                 strExt = strutils::trim_ws_begin(strExt);
644                 project.SetFilter(strExt);
645         }
646         project.SetSubfolders(m_bRecurse);
647
648         if (!theApp.SaveProjectFile(fileName, project))
649                 return;
650
651         LangMessageBox(IDS_PROJFILE_SAVE_SUCCESS, MB_ICONINFORMATION);
652 }
653
654 void COpenView::OnDropDownSaveProject(NMHDR *pNMHDR, LRESULT *pResult)
655 {
656         CRect rcButton, rcView;
657         GetDlgItem(ID_SAVE_PROJECT)->GetWindowRect(&rcButton);
658         BCMenu menu;
659         VERIFY(menu.LoadMenu(IDR_POPUP_PROJECT));
660         theApp.TranslateMenu(menu.m_hMenu);
661         CMenu* pPopup = menu.GetSubMenu(0);
662         if (NULL != pPopup)
663         {
664                 pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, 
665                         rcButton.left, rcButton.bottom, GetMainFrame());
666         }
667         *pResult = 0;
668 }
669
670 /** 
671  * @brief Allow user to select a file to open/save.
672  */
673 String COpenView::AskProjectFileName(bool bOpen)
674 {
675         // get the default projects path
676         String strProjectFileName;
677         String strProjectPath = GetOptionsMgr()->GetString(OPT_PROJECTS_PATH);
678
679         if (!::SelectFile(GetSafeHwnd(), strProjectFileName, bOpen, strProjectPath.c_str(),
680                         _T(""), _("WinMerge Project Files (*.WinMerge)|*.WinMerge||"), _T(".WinMerge")))
681                 return _T("");
682
683         if (strProjectFileName.empty())
684                 return _T("");
685
686         // get the path part from the filename
687         strProjectPath = paths::GetParentPath(strProjectFileName);
688         // store this as the new project path
689         GetOptionsMgr()->SaveOption(OPT_PROJECTS_PATH, strProjectPath);
690         return strProjectFileName;
691 }
692
693 /** 
694  * @brief Load File- and filter-combobox states.
695  */
696 void COpenView::LoadComboboxStates()
697 {
698         m_ctlPath[0].LoadState(_T("Files\\Left"));
699         m_ctlPath[1].LoadState(_T("Files\\Right"));
700         m_ctlPath[2].LoadState(_T("Files\\Option"));
701         m_ctlExt.LoadState(_T("Files\\Ext"));
702 }
703
704 /** 
705  * @brief Save File- and filter-combobox states.
706  */
707 void COpenView::SaveComboboxStates()
708 {
709         m_ctlPath[0].SaveState(_T("Files\\Left"));
710         m_ctlPath[1].SaveState(_T("Files\\Right"));
711         m_ctlPath[2].SaveState(_T("Files\\Option"));
712         m_ctlExt.SaveState(_T("Files\\Ext"));
713 }
714
715 struct UpdateButtonStatesThreadParams
716 {
717         HWND m_hWnd;
718         PathContext m_paths;
719 };
720
721 static UINT UpdateButtonStatesThread(LPVOID lpParam)
722 {
723         MSG msg;
724         BOOL bRet;
725
726         CoInitialize(NULL);
727         CAssureScriptsForThread scriptsForRescan;
728
729         while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
730         { 
731                 if (bRet == -1)
732                         break;
733                 if (msg.message != WM_USER + 2)
734                         continue;
735
736                 BOOL bButtonEnabled = TRUE;
737                 BOOL bInvalid[3] = {FALSE, FALSE, FALSE};
738                 int iStatusMsgId = 0;
739                 int iUnpackerStatusMsgId = 0;
740
741                 UpdateButtonStatesThreadParams *pParams = reinterpret_cast<UpdateButtonStatesThreadParams *>(msg.wParam);
742                 PathContext paths = pParams->m_paths;
743                 HWND hWnd = pParams->m_hWnd;
744                 delete pParams;
745
746                 // Check if we have project file as left side path
747                 BOOL bProject = FALSE;
748                 String ext;
749                 paths::SplitFilename(paths[0], NULL, NULL, &ext);
750                 if (paths[1].empty() && strutils::compare_nocase(ext, ProjectFile::PROJECTFILE_EXT) == 0)
751                         bProject = TRUE;
752
753                 if (!bProject)
754                 {
755                         if (paths::DoesPathExist(paths[0], IsArchiveFile) == paths::DOES_NOT_EXIST)
756                                 bInvalid[0] = TRUE;
757                         if (paths::DoesPathExist(paths[1], IsArchiveFile) == paths::DOES_NOT_EXIST)
758                                 bInvalid[1] = TRUE;
759                         if (paths.GetSize() > 2 && paths::DoesPathExist(paths[2], IsArchiveFile) == paths::DOES_NOT_EXIST)
760                                 bInvalid[2] = TRUE;
761                 }
762
763                 // Enable buttons as appropriate
764                 if (GetOptionsMgr()->GetBool(OPT_VERIFY_OPEN_PATHS))
765                 {
766                         paths::PATH_EXISTENCE pathsType = paths::DOES_NOT_EXIST;
767
768                         if (paths.GetSize() <= 2)
769                         {
770                                 if (bInvalid[0] && bInvalid[1])
771                                         iStatusMsgId = IDS_OPEN_BOTHINVALID;
772                                 else if (bInvalid[0])
773                                         iStatusMsgId = IDS_OPEN_LEFTINVALID;
774                                 else if (bInvalid[1])
775                                         iStatusMsgId = IDS_OPEN_RIGHTINVALID2;
776                                 else if (!bInvalid[0] && !bInvalid[1])
777                                 {
778                                         pathsType = paths::GetPairComparability(paths, IsArchiveFile);
779                                         if (pathsType == paths::DOES_NOT_EXIST)
780                                                 iStatusMsgId = IDS_OPEN_MISMATCH;
781                                         else
782                                                 iStatusMsgId = IDS_OPEN_FILESDIRS;
783                                 }
784                         }
785                         else
786                         {
787                                 if (bInvalid[0] && bInvalid[1] && bInvalid[2])
788                                         iStatusMsgId = IDS_OPEN_ALLINVALID;
789                                 else if (!bInvalid[0] && bInvalid[1] && bInvalid[2])
790                                         iStatusMsgId = IDS_OPEN_MIDDLERIGHTINVALID;
791                                 else if (bInvalid[0] && !bInvalid[1] && bInvalid[2])
792                                         iStatusMsgId = IDS_OPEN_LEFTRIGHTINVALID;
793                                 else if (!bInvalid[0] && !bInvalid[1] && bInvalid[2])
794                                         iStatusMsgId = IDS_OPEN_RIGHTINVALID3;
795                                 else if (bInvalid[0] && bInvalid[1] && !bInvalid[2])
796                                         iStatusMsgId = IDS_OPEN_LEFTMIDDLEINVALID;
797                                 else if (!bInvalid[0] && bInvalid[1] && !bInvalid[2])
798                                         iStatusMsgId = IDS_OPEN_MIDDLEINVALID;
799                                 else if (bInvalid[0] && !bInvalid[1] && !bInvalid[2])
800                                         iStatusMsgId = IDS_OPEN_LEFTINVALID;
801                                 else if (!bInvalid[0] && !bInvalid[1] && !bInvalid[2])
802                                 {
803                                         pathsType = paths::GetPairComparability(paths, IsArchiveFile);
804                                         if (pathsType == paths::DOES_NOT_EXIST)
805                                                 iStatusMsgId = IDS_OPEN_MISMATCH;
806                                         else
807                                                 iStatusMsgId = IDS_OPEN_FILESDIRS;
808                                 }
809                         }
810                         if (pathsType == paths::IS_EXISTING_FILE || bProject)
811                                 iUnpackerStatusMsgId = 0;       //Empty field
812                         else
813                                 iUnpackerStatusMsgId = IDS_OPEN_UNPACKERDISABLED;
814
815                         if (bProject)
816                                 bButtonEnabled = TRUE;
817                         else
818                                 bButtonEnabled = (pathsType != paths::DOES_NOT_EXIST);
819                 }
820
821                 PostMessage(hWnd, WM_USER + 1, bButtonEnabled, MAKELPARAM(iStatusMsgId, iUnpackerStatusMsgId)); 
822         }
823
824         CoUninitialize();
825
826         return 0;
827 }
828
829 /** 
830  * @brief Enable/disable components based on validity of paths.
831  */
832 void COpenView::UpdateButtonStates()
833 {
834         UpdateData(TRUE); // load member variables from screen
835         KillTimer(IDT_CHECKFILES);
836         TrimPaths();
837         
838         if (!m_pUpdateButtonStatusThread)
839         {
840                 m_pUpdateButtonStatusThread = AfxBeginThread(
841                         UpdateButtonStatesThread, NULL, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
842                 m_pUpdateButtonStatusThread->m_bAutoDelete = FALSE;
843                 m_pUpdateButtonStatusThread->ResumeThread();
844                 while (PostThreadMessage(m_pUpdateButtonStatusThread->m_nThreadID, WM_NULL, 0, 0) == FALSE)
845                         Sleep(1);
846         }
847
848         UpdateButtonStatesThreadParams *pParams = new UpdateButtonStatesThreadParams;
849         pParams->m_hWnd = this->m_hWnd;
850         if (m_strPath[2].empty())
851                 pParams->m_paths = PathContext(m_strPath[0], m_strPath[1]);
852         else
853                 pParams->m_paths = PathContext(m_strPath[0], m_strPath[1], m_strPath[2]);
854
855         PostThreadMessage(m_pUpdateButtonStatusThread->m_nThreadID, WM_USER + 2, (WPARAM)pParams, 0);
856 }
857
858 void COpenView::TerminateThreadIfRunning()
859 {
860         if (!m_pUpdateButtonStatusThread)
861                 return;
862
863         PostThreadMessage(m_pUpdateButtonStatusThread->m_nThreadID, WM_QUIT, 0, 0);
864         DWORD dwResult = WaitForSingleObject(m_pUpdateButtonStatusThread->m_hThread, 100);
865         if (dwResult != WAIT_OBJECT_0)
866         {
867                 m_pUpdateButtonStatusThread->SuspendThread();
868                 TerminateThread(m_pUpdateButtonStatusThread->m_hThread, 0);
869         }
870         delete m_pUpdateButtonStatusThread;
871         m_pUpdateButtonStatusThread = NULL;
872 }
873
874 /**
875  * @brief Called when user changes selection in left/middle/right path's combo box.
876  */
877 void COpenView::OnSelchangeCombo(int index) 
878 {
879         int sel = m_ctlPath[index].GetCurSel();
880         if (sel != CB_ERR)
881         {
882                 CString cstrPath;
883                 m_ctlPath[index].GetLBText(sel, cstrPath);
884                 m_strPath[index] = cstrPath;
885                 m_ctlPath[index].SetWindowText(cstrPath);
886                 UpdateData(TRUE);
887         }
888         UpdateButtonStates();
889 }
890
891 template <int N>
892 void COpenView::OnSelchangePathCombo() 
893 {
894         OnSelchangeCombo(N);
895 }
896
897 void COpenView::OnSetfocusPathCombo(UINT id, NMHDR *pNMHDR, LRESULT *pResult) 
898 {
899         if (!m_bAutoCompleteReady[id - IDC_PATH0_COMBO])
900         {
901                 int nSource = GetOptionsMgr()->GetInt(OPT_AUTO_COMPLETE_SOURCE);
902                 if (nSource > 0)
903                         m_ctlPath[id - IDC_PATH0_COMBO].SetAutoComplete(nSource);
904                 m_bAutoCompleteReady[id - IDC_PATH0_COMBO] = true;
905         }
906         *pResult = 0;
907 }
908
909 /** 
910  * @brief Called every time paths are edited.
911  */
912 void COpenView::OnEditEvent()
913 {
914         // (Re)start timer to path validity check delay
915         // If timer starting fails, update buttonstates immediately
916         if (!SetTimer(IDT_CHECKFILES, CHECKFILES_TIMEOUT, NULL))
917                 UpdateButtonStates();
918 }
919
920 /**
921  * @brief Handle timer events.
922  * Checks if paths are valid and sets control states accordingly.
923  * @param [in] nIDEvent Timer ID that fired.
924  */
925 void COpenView::OnTimer(UINT_PTR nIDEvent)
926 {
927         if (nIDEvent == IDT_CHECKFILES)
928                 UpdateButtonStates();
929
930         CFormView::OnTimer(nIDEvent);
931 }
932
933 /**
934  * @brief Called when users selects plugin browse button.
935  */
936 void COpenView::OnSelectUnpacker()
937 {
938         paths::PATH_EXISTENCE pathsType;
939         UpdateData(TRUE);
940
941         int index;
942         int nFiles = 0;
943         for (index = 0; index < countof(m_strPath); index++)
944         {
945                 if (index == 2 && m_strPath[index].empty())
946                         break;
947                 m_files.SetSize(nFiles + 1);
948                 m_files[nFiles] = m_strPath[index];
949                 nFiles++;
950         }
951         pathsType = paths::GetPairComparability(m_files);
952
953         if (pathsType != paths::IS_EXISTING_FILE) 
954                 return;
955
956         // let the user select a handler
957         CSelectUnpackerDlg dlg(m_files[0], this);
958         PackingInfo infoUnpacker(PLUGIN_AUTO);
959         dlg.SetInitialInfoHandler(&infoUnpacker);
960
961         if (dlg.DoModal() == IDOK)
962         {
963                 m_infoHandler = dlg.GetInfoHandler();
964
965                 m_strUnpacker = m_infoHandler.pluginName;
966
967                 UpdateData(FALSE);
968         }
969 }
970
971 LRESULT COpenView::OnUpdateStatus(WPARAM wParam, LPARAM lParam)
972 {
973         bool bEnabledButtons = wParam != 0;
974
975         EnableDlgItem(IDOK, bEnabledButtons);
976         EnableDlgItem(IDC_UNPACKER_EDIT, bEnabledButtons);
977         EnableDlgItem(IDC_SELECT_UNPACKER, bEnabledButtons);
978
979         SetStatus(HIWORD(lParam));
980         SetStatus(LOWORD(lParam));
981
982         return 0;
983 }
984
985 /**
986  * @brief Sets the path status text.
987  * The open dialog shows a status text of selected paths. This function
988  * is used to set that status text.
989  * @param [in] msgID Resource ID of status text to set.
990  */
991 void COpenView::SetStatus(UINT msgID)
992 {
993         String msg = theApp.LoadString(msgID);
994         SetDlgItemText(IDC_OPEN_STATUS, msg);
995 }
996
997 /**
998  * @brief Set the plugin edit box text.
999  * Plugin edit box is at the same time a plugin status view. This function
1000  * sets the status text.
1001  * @param [in] msgID Resource ID of status text to set.
1002  */
1003 void COpenView::SetUnpackerStatus(UINT msgID)
1004 {
1005         String msg = theApp.LoadString(msgID);
1006         SetDlgItemText(IDC_UNPACKER_EDIT, msg);
1007 }
1008
1009 /** 
1010  * @brief Called when "Select..." button for filters is selected.
1011  */
1012 void COpenView::OnSelectFilter()
1013 {
1014         String filterPrefix = _("[F] ");
1015         String curFilter;
1016
1017         const BOOL bUseMask = theApp.m_pGlobalFileFilter->IsUsingMask();
1018         GetDlgItemText(IDC_EXT_COMBO, curFilter);
1019         curFilter = strutils::trim_ws(curFilter);
1020
1021         GetMainFrame()->SelectFilter();
1022         
1023         String filterNameOrMask = theApp.m_pGlobalFileFilter->GetFilterNameOrMask();
1024         if (theApp.m_pGlobalFileFilter->IsUsingMask())
1025         {
1026                 // If we had filter chosen and now has mask we can overwrite filter
1027                 if (!bUseMask || curFilter[0] != '*')
1028                 {
1029                         SetDlgItemText(IDC_EXT_COMBO, filterNameOrMask);
1030                 }
1031         }
1032         else
1033         {
1034                 filterNameOrMask = filterPrefix + filterNameOrMask;
1035                 SetDlgItemText(IDC_EXT_COMBO, filterNameOrMask);
1036         }
1037 }
1038
1039 void COpenView::OnOptions()
1040 {
1041         GetMainFrame()->PostMessage(WM_COMMAND, ID_OPTIONS);
1042 }
1043
1044 void COpenView::OnDropDownOptions(NMHDR *pNMHDR, LRESULT *pResult)
1045 {
1046         NMTOOLBAR dropDown = { 0 };
1047         dropDown.hdr.code = TBN_DROPDOWN;
1048         dropDown.hdr.hwndFrom = GetMainFrame()->GetDescendantWindow(AFX_IDW_TOOLBAR)->GetSafeHwnd();
1049         dropDown.hdr.idFrom = AFX_IDW_TOOLBAR;
1050         GetDlgItem(IDC_OPTIONS)->GetWindowRect(&dropDown.rcButton);
1051         GetMainFrame()->ScreenToClient(&dropDown.rcButton);
1052         GetMainFrame()->SendMessage(WM_NOTIFY, dropDown.hdr.idFrom, reinterpret_cast<LPARAM>(&dropDown));
1053         *pResult = 0;
1054 }
1055
1056 /** 
1057  * @brief Read paths and filter from project file.
1058  * Reads the given project file. After the file is read, found paths and
1059  * filter is updated to dialog GUI. Other possible settings found in the
1060  * project file are kept in memory and used later when loading paths
1061  * selected.
1062  * @param [in] path Path to the project file.
1063  * @return TRUE if the project file was successfully loaded, FALSE otherwise.
1064  */
1065 BOOL COpenView::LoadProjectFile(const String &path)
1066 {
1067         String filterPrefix = _("[F] ");
1068         ProjectFile prj;
1069
1070         if (!theApp.LoadProjectFile(path, prj))
1071                 return FALSE;
1072
1073         bool recurse;
1074         prj.GetPaths(m_files, recurse);
1075         m_bRecurse = recurse;
1076         m_dwFlags[0] &= ~FFILEOPEN_READONLY;
1077         m_dwFlags[0] |= prj.GetLeftReadOnly() ? FFILEOPEN_READONLY : 0;
1078         if (m_files.GetSize() < 3)
1079         {
1080                 m_dwFlags[1] &= ~FFILEOPEN_READONLY;
1081                 m_dwFlags[1] |= prj.GetRightReadOnly() ? FFILEOPEN_READONLY : 0;
1082         }
1083         else
1084         {
1085                 m_dwFlags[1] &= ~FFILEOPEN_READONLY;
1086                 m_dwFlags[1] |= prj.GetMiddleReadOnly() ? FFILEOPEN_READONLY : 0;
1087                 m_dwFlags[2] &= ~FFILEOPEN_READONLY;
1088                 m_dwFlags[2] |= prj.GetRightReadOnly() ? FFILEOPEN_READONLY : 0;
1089         }
1090         if (prj.HasFilter())
1091         {
1092                 m_strExt = strutils::trim_ws(prj.GetFilter());
1093                 if (m_strExt[0] != '*')
1094                         m_strExt.insert(0, filterPrefix);
1095         }
1096         return TRUE;
1097 }
1098
1099 /** 
1100  * @brief Removes whitespaces from left and right paths
1101  * @note Assumes UpdateData(TRUE) is called before this function.
1102  */
1103 void COpenView::TrimPaths()
1104 {
1105         for (int index = 0; index < countof(m_strPath); index++)
1106                 m_strPath[index] = strutils::trim_ws(m_strPath[index]);
1107 }
1108
1109 /** 
1110  * @brief Update control states when dialog is activated.
1111  *
1112  * Update control states when user re-activates dialog. User might have
1113  * switched for other program to e.g. update files/folders and then
1114  * swiches back to WinMerge. Its nice to see WinMerge detects updated
1115  * files/folders.
1116  */
1117 void COpenView::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized)
1118 {
1119         CFormView::OnActivate(nState, pWndOther, bMinimized);
1120
1121         if (nState == WA_ACTIVE || nState == WA_CLICKACTIVE)
1122                 UpdateButtonStates();
1123 }
1124
1125 template <int MSG, int WPARAM, int LPARAM>
1126 void COpenView::OnEditAction()
1127 {
1128         CWnd *pCtl = GetFocus();
1129         if (pCtl)
1130                 pCtl->PostMessage(MSG, WPARAM, LPARAM);
1131 }
1132
1133 /**
1134  * @brief Open help from mainframe when user presses F1.
1135  */
1136 void COpenView::OnHelp()
1137 {
1138         theApp.ShowHelp(OpenDlgHelpLocation);
1139 }
1140
1141 /////////////////////////////////////////////////////////////////////////////
1142 //
1143 //      OnDropFiles code from CDropEdit
1144 //      Copyright 1997 Chris Losinger
1145 //
1146 //      shortcut expansion code modified from :
1147 //      CShortcut, 1996 Rob Warner
1148 //
1149
1150 /**
1151  * @brief Drop paths(s) to the dialog.
1152  * One or two paths can be dropped to the dialog. The behaviour is:
1153  *   If 1 file:
1154  *     - drop to empty path edit box (check left first)
1155  *     - if both boxes have a path, drop to left path
1156  *   If two files:
1157  *    - overwrite both paths, empty or not
1158  * @param [in] dropInfo Dropped data, including paths.
1159  */
1160 void COpenView::OnDropFiles(const std::vector<String>& files)
1161 {
1162         const size_t fileCount = files.size();
1163
1164         // Add dropped paths to the dialog
1165         UpdateData(TRUE);
1166         if (fileCount == 3)
1167         {
1168                 m_strPath[0] = files[0];
1169                 m_strPath[1] = files[1];
1170                 m_strPath[2] = files[2];
1171                 UpdateData(FALSE);
1172                 UpdateButtonStates();
1173         }
1174         else if (fileCount == 2)
1175         {
1176                 m_strPath[0] = files[0];
1177                 m_strPath[1] = files[1];
1178                 UpdateData(FALSE);
1179                 UpdateButtonStates();
1180         }
1181         else if (fileCount == 1)
1182         {
1183                 if (m_strPath[0].empty())
1184                         m_strPath[0] = files[0];
1185                 else if (m_strPath[1].empty())
1186                         m_strPath[1] = files[0];
1187                 else if (m_strPath[2].empty())
1188                         m_strPath[2] = files[0];
1189                 else
1190                         m_strPath[0] = files[0];
1191                 UpdateData(FALSE);
1192                 UpdateButtonStates();
1193         }
1194 }
1195
1196 BOOL COpenView::PreTranslateMessage(MSG* pMsg)
1197 {
1198         if (pMsg->message == WM_SYSKEYDOWN)
1199         {
1200                 if (::GetAsyncKeyState(VK_MENU))
1201                 {
1202                         UINT id = 0;
1203                         switch (pMsg->wParam)
1204                         {
1205                         case '1': id = IDC_PATH0_COMBO; goto LABEL_NUM_KEY;
1206                         case '2': id = IDC_PATH1_COMBO; goto LABEL_NUM_KEY;
1207                         case '3': id = IDC_PATH2_COMBO;
1208                         LABEL_NUM_KEY:
1209                                 SetDlgItemFocus(id);
1210                                 return TRUE;
1211                         case 's':
1212                         case 'S': id = IDC_SELECT_UNPACKER;
1213                                 PostMessage(WM_COMMAND, id, 0);
1214                                 return TRUE;
1215                         }
1216                 }
1217         }
1218         return CFormView::PreTranslateMessage(pMsg);
1219 }