OSDN Git Service

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