OSDN Git Service

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