OSDN Git Service

Merge rev.7791
[winmerge-jp/winmerge-jp.git] / Src / OpenDlg.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  OpenDlg.cpp
23  *
24  * @brief Implementation of the COpenDlg class
25  */
26 // ID line follows -- this is updated by SVN
27 // $Id: OpenDlg.cpp 6861 2009-06-25 12:11:07Z kimmov $
28
29 #include "stdafx.h"
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include "UnicodeString.h"
33 #include "Merge.h"
34 #include "ProjectFile.h"
35 #include "OpenDlg.h"
36 #include "coretools.h"
37 #include "paths.h"
38 #include "SelectUnpackerDlg.h"
39 #include "OptionsDef.h"
40 #include "MainFrm.h"
41 #include "OptionsMgr.h"
42 #include "FileOrFolderSelect.h"
43 #include "7zCommon.h"
44
45 #ifdef COMPILE_MULTIMON_STUBS
46 #undef COMPILE_MULTIMON_STUBS
47 #endif
48 #include <multimon.h>
49
50 #ifdef _DEBUG
51 #define new DEBUG_NEW
52 #undef THIS_FILE
53 static char THIS_FILE[] = __FILE__;
54 #endif
55
56 // Timer ID and timeout for delaying path validity check
57 const UINT IDT_CHECKFILES = 1;
58 const UINT CHECKFILES_TIMEOUT = 1000; // milliseconds
59 static const TCHAR EMPTY_EXTENSION[] = _T(".*");
60
61 /** @brief Location for Open-dialog specific help to open. */
62 static TCHAR OpenDlgHelpLocation[] = _T("::/htmlhelp/Open_paths.html");
63
64 /////////////////////////////////////////////////////////////////////////////
65 // COpenDlg dialog
66
67 /**
68  * @brief Standard constructor.
69  */
70 COpenDlg::COpenDlg(CWnd* pParent /*=NULL*/)
71         : CDialog(COpenDlg::IDD, pParent)
72         , m_pathsType(DOES_NOT_EXIST)
73         , m_bOverwriteRecursive(FALSE)
74         , m_bRecurse(FALSE)
75         , m_pProjectFile(NULL)
76         , m_pUpdateButtonStatusThread(NULL)
77 {
78 }
79
80 /**
81  * @brief Standard destructor.
82  */
83 COpenDlg::~COpenDlg()
84 {
85         TerminateThreadIfRunning();
86         delete m_pProjectFile;
87 }
88
89 void COpenDlg::DoDataExchange(CDataExchange* pDX)
90 {
91         CDialog::DoDataExchange(pDX);
92         //{{AFX_DATA_MAP(COpenDlg)
93         DDX_Control(pDX, IDC_SELECT_UNPACKER, m_ctlSelectUnpacker);
94         DDX_Control(pDX, IDC_UNPACKER_EDIT, m_ctlUnpacker);
95         DDX_Control(pDX, IDC_EXT_COMBO, m_ctlExt);
96         DDX_Control(pDX, IDOK, m_ctlOk);
97         DDX_Control(pDX, IDC_RECURS_CHECK, m_ctlRecurse);
98         DDX_Control(pDX, IDC_PATH0_COMBO, m_ctlPath[0]);
99         DDX_Control(pDX, IDC_PATH1_COMBO, m_ctlPath[1]);
100         DDX_Control(pDX, IDC_PATH2_COMBO, m_ctlPath[2]);
101         DDX_CBStringExact(pDX, IDC_PATH0_COMBO, m_strPath[0]);
102         DDX_CBStringExact(pDX, IDC_PATH1_COMBO, m_strPath[1]);
103         DDX_CBStringExact(pDX, IDC_PATH2_COMBO, m_strPath[2]);
104         DDX_Check(pDX, IDC_RECURS_CHECK, m_bRecurse);
105         DDX_CBStringExact(pDX, IDC_EXT_COMBO, m_strExt);
106         DDX_Text(pDX, IDC_UNPACKER_EDIT, m_strUnpacker);
107         //}}AFX_DATA_MAP
108 }
109
110
111 BEGIN_MESSAGE_MAP(COpenDlg, CDialog)
112         //{{AFX_MSG_MAP(COpenDlg)
113         ON_BN_CLICKED(IDC_PATH0_BUTTON, OnPath0Button)
114         ON_BN_CLICKED(IDC_PATH1_BUTTON, OnPath1Button)
115         ON_BN_CLICKED(IDC_PATH2_BUTTON, OnPath2Button)
116         ON_CBN_SELCHANGE(IDC_PATH0_COMBO, OnSelchangePath0Combo)
117         ON_CBN_SELCHANGE(IDC_PATH1_COMBO, OnSelchangePath1Combo)
118         ON_CBN_SELCHANGE(IDC_PATH2_COMBO, OnSelchangePath2Combo)
119         ON_CBN_EDITCHANGE(IDC_PATH0_COMBO, OnEditEvent)
120         ON_CBN_EDITCHANGE(IDC_PATH1_COMBO, OnEditEvent)
121         ON_CBN_EDITCHANGE(IDC_PATH2_COMBO, OnEditEvent)
122         ON_BN_CLICKED(IDC_SELECT_UNPACKER, OnSelectUnpacker)
123         ON_CBN_SELENDCANCEL(IDC_PATH0_COMBO, UpdateButtonStates)
124         ON_CBN_SELENDCANCEL(IDC_PATH1_COMBO, UpdateButtonStates)
125         ON_CBN_SELENDCANCEL(IDC_PATH2_COMBO, UpdateButtonStates)
126         ON_WM_TIMER()
127         ON_BN_CLICKED(IDC_SELECT_FILTER, OnSelectFilter)
128         ON_WM_ACTIVATE()
129         ON_COMMAND(ID_HELP, OnHelp)
130         ON_WM_DROPFILES()
131         ON_MESSAGE(WM_USER + 1, OnUpdateStatus)
132         //}}AFX_MSG_MAP
133 END_MESSAGE_MAP()
134
135 /////////////////////////////////////////////////////////////////////////////
136 // COpenDlg message handlers
137
138 /**
139  * @brief Handler for WM_INITDIALOG; conventional location to initialize controls
140  * At this point dialog and control windows exist
141  */
142 BOOL COpenDlg::OnInitDialog() 
143 {
144         theApp.TranslateDialog(m_hWnd);
145         CDialog::OnInitDialog();
146         
147         // setup handler for resizing this dialog       
148         m_constraint.InitializeCurrentSize(this);
149         // configure how individual controls adjust when dialog resizes
150         m_constraint.ConstrainItem(IDC_PATH0_COMBO, 0, 1, 0, 0); // grows right
151         m_constraint.ConstrainItem(IDC_PATH1_COMBO, 0, 1, 0, 0); // grows right
152         m_constraint.ConstrainItem(IDC_PATH2_COMBO, 0, 1, 0, 0); // grows right
153         m_constraint.ConstrainItem(IDC_EXT_COMBO, 0, 1, 0, 0); // grows right
154         m_constraint.ConstrainItem(IDC_UNPACKER_EDIT, 0, 1, 0, 0); // grows right
155         m_constraint.ConstrainItem(IDC_FILES_DIRS_GROUP, 0, 1, 0, 0); // grows right
156         m_constraint.ConstrainItem(IDC_PATH0_BUTTON, 1, 0, 0, 0); // slides right
157         m_constraint.ConstrainItem(IDC_PATH1_BUTTON, 1, 0, 0, 0); // slides right
158         m_constraint.ConstrainItem(IDC_PATH2_BUTTON, 1, 0, 0, 0); // slides right
159         m_constraint.ConstrainItem(IDC_SELECT_UNPACKER, 1, 0, 0, 0); // slides right
160         m_constraint.ConstrainItem(IDC_OPEN_STATUS, 0, 1, 0, 0); // grows right
161         m_constraint.ConstrainItem(IDC_SELECT_FILTER, 1, 0, 0, 0); // slides right
162         m_constraint.ConstrainItem(IDOK, 1, 0, 0, 0); // slides right
163         m_constraint.ConstrainItem(IDCANCEL, 1, 0, 0, 0); // slides right
164         m_constraint.ConstrainItem(ID_HELP, 1, 0, 0, 0); // slides right
165         m_constraint.DisallowHeightGrowth();
166         m_constraint.SubclassWnd(); // install subclassing
167         m_constraint.LoadPosition(_T("ResizeableDialogs"), _T("OpenDlg"), false); // persist size via registry
168
169         CMainFrame::CenterToMainFrame(this);
170
171         for (int file = 0; file < m_files.GetSize(); file++)
172         {
173                 m_strPath[file] = m_files[file].c_str();
174                 m_ctlPath[file].SetWindowText(m_files[file].c_str());
175         }
176
177         m_ctlPath[0].AttachSystemImageList();
178         m_ctlPath[1].AttachSystemImageList();
179         m_ctlPath[2].AttachSystemImageList();
180         m_ctlPath[0].LoadState(_T("Files\\Left"));
181         m_ctlPath[1].LoadState(_T("Files\\Right"));
182         m_ctlPath[2].LoadState(_T("Files\\Option"));
183         m_ctlExt.LoadState(_T("Files\\Ext"));
184         
185         BOOL bIsEmptyThirdItem = theApp.GetProfileInt(_T("Files\\Option"), _T("Empty"), TRUE);
186         if (bIsEmptyThirdItem)
187                 m_ctlPath[2].SetWindowText(_T(""));
188         
189         BOOL bDoUpdateData = TRUE;
190         for (int index = 0; index < countof(m_strPath); index++)
191         {
192                 if (!m_strPath[index].IsEmpty())
193                         bDoUpdateData = FALSE;
194         }
195         UpdateData(bDoUpdateData);
196         
197         int nSource = GetOptionsMgr()->GetInt(OPT_AUTO_COMPLETE_SOURCE);
198         if (nSource > 0)
199         {
200                 m_ctlPath[0].SetAutoComplete(nSource);
201                 m_ctlPath[1].SetAutoComplete(nSource);
202                 m_ctlPath[2].SetAutoComplete(nSource);
203         }
204
205         String filterNameOrMask = theApp.m_globalFileFilter.GetFilterNameOrMask();
206         BOOL bMask = theApp.m_globalFileFilter.IsUsingMask();
207
208         if (!bMask)
209         {
210                 String filterPrefix = theApp.LoadString(IDS_FILTER_PREFIX);
211                 filterNameOrMask = filterPrefix + filterNameOrMask;
212         }
213
214         int ind = m_ctlExt.FindStringExact(0, filterNameOrMask.c_str());
215         if (ind != CB_ERR)
216                 m_ctlExt.SetCurSel(ind);
217         else
218         {
219                 ind = m_ctlExt.InsertString(0, filterNameOrMask.c_str());
220                 if (ind != CB_ERR)
221                         m_ctlExt.SetCurSel(ind);
222                 else
223                         LogErrorString(_T("Failed to add string to filters combo list!"));
224         }
225
226         if (!GetOptionsMgr()->GetBool(OPT_VERIFY_OPEN_PATHS))
227         {
228                 m_ctlOk.EnableWindow(TRUE);
229                 m_ctlUnpacker.EnableWindow(TRUE);
230                 m_ctlSelectUnpacker.EnableWindow(TRUE);
231         }
232
233         UpdateButtonStates();
234
235         if (!m_bOverwriteRecursive)
236                 m_bRecurse = theApp.GetProfileInt(_T("Settings"), _T("Recurse"), 0) == 1;
237
238         m_strUnpacker = m_infoHandler.pluginName.c_str();
239         UpdateData(FALSE);
240         SetStatus(IDS_OPEN_FILESDIRS);
241         SetUnpackerStatus(IDS_OPEN_UNPACKERDISABLED);
242         return TRUE;
243 }
244
245 void COpenDlg::OnButton(int index)
246 {
247         CString s;
248         String sfolder;
249         UpdateData(TRUE); 
250
251         PATH_EXISTENCE existence = paths_DoesPathExist(m_strPath[index]);
252         switch (existence)
253         {
254         case IS_EXISTING_DIR:
255                 sfolder = m_strPath[index];
256                 break;
257         case IS_EXISTING_FILE:
258                 sfolder = GetPathOnly(m_strPath[index]);
259                 break;
260         case DOES_NOT_EXIST:
261                 // Do nothing, empty foldername will be passed to dialog
262                 break;
263         default:
264                 _RPTF0(_CRT_ERROR, "Invalid return value from paths_DoesPathExist()");
265                 break;
266         }
267
268         if (SelectFileOrFolder(GetSafeHwnd(), s, sfolder.c_str()))
269         {
270                 m_strPath[index] = s;
271                 m_strBrowsePath[index] = s;
272                 UpdateData(FALSE);
273                 UpdateButtonStates();
274         }       
275 }
276
277 /** 
278  * @brief Called when "Browse..." button is selected for first path.
279  */
280 void COpenDlg::OnPath0Button()
281 {
282         OnButton(0);
283 }
284
285 /** 
286  * @brief Called when "Browse..." button is selected for second path.
287  */
288 void COpenDlg::OnPath1Button() 
289 {
290         OnButton(1);
291 }
292
293 /** 
294  * @brief Called when "Browse..." button is selected for third path.
295  */
296 void COpenDlg::OnPath2Button() 
297 {
298         OnButton(2);
299 }
300
301 /** 
302  * @brief Called when dialog is closed with "OK".
303  *
304  * Checks that paths are valid and sets filters.
305  */
306 void COpenDlg::OnOK() 
307 {
308         const String filterPrefix = theApp.LoadString(IDS_FILTER_PREFIX);
309
310         UpdateData(TRUE);
311         TrimPaths();
312
313         // If left path is a project-file, load it
314         String ext;
315         SplitFilename(m_strPath[0], NULL, NULL, &ext);
316         CString sExt(ext.c_str());
317         if (m_strPath[1].IsEmpty() && sExt.CompareNoCase(PROJECTFILE_EXT) == 0)
318                 LoadProjectFile(m_strPath[0]);
319
320         int index;
321         int nFiles = 0;
322         for (index = 0; index < countof(m_strPath); index++)
323         {
324                 if (index == 2 && m_strPath[index].IsEmpty())
325                         break;
326                 m_files.SetSize(nFiles + 1);
327                 m_files[nFiles] = m_strPath[index];
328                 nFiles++;
329         }
330         m_pathsType = GetPairComparability(m_files, IsArchiveFile);
331
332         if (m_pathsType == DOES_NOT_EXIST)
333         {
334                 LangMessageBox(IDS_ERROR_INCOMPARABLE, MB_ICONSTOP);
335                 return;
336         }
337
338         for (index = 0; index < nFiles; index++)
339         {
340                 // If user has edited path by hand, expand environment variables
341                 BOOL bExpand = FALSE;
342                 if (m_strBrowsePath[index].CompareNoCase(m_files[index].c_str()) != 0)
343                         bExpand = TRUE;
344
345                 m_files[index] = paths_GetLongPath(m_files[index].c_str(), bExpand);
346         
347                 // Add trailing '\' for directories if its missing
348                 if (paths_DoesPathExist(m_files[index].c_str()) == IS_EXISTING_DIR)
349                 {
350                         if (!paths_EndsWithSlash(m_files[index].c_str()))
351                                 m_files[index] += '\\';
352                 }
353         }
354
355         UpdateData(FALSE);
356         KillTimer(IDT_CHECKFILES);
357
358         String filter((LPCTSTR)m_strExt);
359         filter = string_trim_ws(filter);
360
361         // If prefix found from start..
362         if (filter.find(filterPrefix, 0) == 0)
363         {
364                 // Remove prefix + space
365                 filter.erase(0, filterPrefix.length());
366                 if (!theApp.m_globalFileFilter.SetFilter(filter))
367                 {
368                         // If filtername is not found use default *.* mask
369                         theApp.m_globalFileFilter.SetFilter(_T("*.*"));
370                         filter = _T("*.*");
371                 }
372                 GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, filter.c_str());
373         }
374         else
375         {
376                 BOOL bFilterSet = theApp.m_globalFileFilter.SetFilter(filter);
377                 if (!bFilterSet)
378                         m_strExt = theApp.m_globalFileFilter.GetFilterNameOrMask().c_str();
379                 GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, filter.c_str());
380         }
381
382         SaveComboboxStates();
383         theApp.WriteProfileInt(_T("Settings"), _T("Recurse"), m_bRecurse);
384
385         CDialog::OnOK();
386 }
387
388 /** 
389  * @brief Called when dialog is closed via Cancel.
390  *
391  * Open-dialog is canceled when 'Cancel' button is selected or
392  * Esc-key is pressed. Save combobox states, since user may have
393  * removed items from them and don't want them to re-appear.
394  */
395 void COpenDlg::OnCancel()
396 {
397         SaveComboboxStates();
398         CDialog::OnCancel();
399 }
400
401 /** 
402  * @brief Save File- and filter-combobox states.
403  */
404 void COpenDlg::SaveComboboxStates()
405 {
406         m_ctlPath[0].SaveState(_T("Files\\Left"));
407         m_ctlPath[1].SaveState(_T("Files\\Right"));
408         m_ctlPath[2].SaveState(_T("Files\\Option"));
409         m_ctlExt.SaveState(_T("Files\\Ext"));
410
411         CString strOption;
412         m_ctlPath[2].GetWindowText(strOption);
413         theApp.WriteProfileInt(_T("Files\\Option"), _T("Empty"), strOption.IsEmpty());
414 }
415
416 struct UpdateButtonStatesThreadParams
417 {
418         HWND m_hWnd;
419         PathContext m_paths;
420 };
421
422 UINT UpdateButtonStatesThread(LPVOID lpParam)
423 {
424         MSG msg;
425         BOOL bRet;
426         while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
427         { 
428                 if (bRet == -1)
429                         break;
430                 if (msg.message != WM_USER)
431                         continue;
432
433                 BOOL bButtonEnabled = TRUE;
434                 BOOL bInvalid[3] = {FALSE, FALSE, FALSE};
435                 int iStatusMsgId;
436                 int iUnpackerStatusMsgId;
437
438                 UpdateButtonStatesThreadParams *pParams = (UpdateButtonStatesThreadParams *)msg.wParam;
439                 PathContext paths = pParams->m_paths;
440                 HWND hWnd = pParams->m_hWnd;
441                 delete pParams;
442
443                 // Check if we have project file as left side path
444                 BOOL bProject = FALSE;
445                 String ext;
446                 SplitFilename(paths[0].c_str(), NULL, NULL, &ext);
447                 CString sExt(ext.c_str());
448                 if (paths[1].empty() && sExt.CompareNoCase(PROJECTFILE_EXT) == 0)
449                         bProject = TRUE;
450
451                 if (!bProject)
452                 {
453                         if (paths_DoesPathExist(paths[0].c_str()) == DOES_NOT_EXIST)
454                                 bInvalid[0] = TRUE;
455                         if (paths_DoesPathExist(paths[1].c_str()) == DOES_NOT_EXIST)
456                                 bInvalid[1] = TRUE;
457                         if (paths.GetSize() > 2 && paths_DoesPathExist(paths[2].c_str()) == DOES_NOT_EXIST)
458                                 bInvalid[2] = TRUE;
459                 }
460
461                 // Enable buttons as appropriate
462                 if (GetOptionsMgr()->GetBool(OPT_VERIFY_OPEN_PATHS))
463                 {
464                         PATH_EXISTENCE pathsType = DOES_NOT_EXIST;
465
466                         if (paths.GetSize() <= 2)
467                         {
468                                 if (bInvalid[0] && bInvalid[1])
469                                         iStatusMsgId = IDS_OPEN_BOTHINVALID;
470                                 else if (bInvalid[0])
471                                         iStatusMsgId = IDS_OPEN_LEFTINVALID;
472                                 else if (bInvalid[1])
473                                         iStatusMsgId = IDS_OPEN_RIGHTINVALID;
474                                 else if (!bInvalid[0] && !bInvalid[1])
475                                 {
476                                         pathsType = GetPairComparability(paths, IsArchiveFile);
477                                         if (pathsType == DOES_NOT_EXIST)
478                                                 iStatusMsgId = IDS_OPEN_MISMATCH;
479                                         else
480                                                 iStatusMsgId = IDS_OPEN_FILESDIRS;
481                                 }
482                         }
483                         else
484                         {
485                                 if (bInvalid[0] && bInvalid[1] && bInvalid[2])
486                                         iStatusMsgId = IDS_OPEN_ALLINVALID;
487                                 else if (!bInvalid[0] && bInvalid[1] && bInvalid[2])
488                                         iStatusMsgId = IDS_OPEN_MIDDLERIGHTINVALID;
489                                 else if (bInvalid[0] && !bInvalid[1] && bInvalid[2])
490                                         iStatusMsgId = IDS_OPEN_LEFTRIGHTINVALID;
491                                 else if (!bInvalid[0] && !bInvalid[1] && bInvalid[2])
492                                         iStatusMsgId = IDS_OPEN_RIGHTINVALID;
493                                 else if (bInvalid[0] && bInvalid[1] && !bInvalid[2])
494                                         iStatusMsgId = IDS_OPEN_LEFTMIDDLEINVALID;
495                                 else if (!bInvalid[0] && bInvalid[1] && !bInvalid[2])
496                                         iStatusMsgId = IDS_OPEN_MIDDLEINVALID;
497                                 else if (bInvalid[0] && !bInvalid[1] && !bInvalid[2])
498                                         iStatusMsgId = IDS_OPEN_LEFTINVALID;
499                                 else if (!bInvalid[0] && !bInvalid[1] && !bInvalid[2])
500                                 {
501                                         pathsType = GetPairComparability(paths, IsArchiveFile);
502                                         if (pathsType == DOES_NOT_EXIST)
503                                                 iStatusMsgId = IDS_OPEN_MISMATCH;
504                                         else
505                                                 iStatusMsgId = IDS_OPEN_FILESDIRS;
506                                 }
507                         }
508                         if (pathsType == IS_EXISTING_FILE || bProject)
509                                 iUnpackerStatusMsgId = 0;       //Empty field
510                         else
511                                 iUnpackerStatusMsgId = IDS_OPEN_UNPACKERDISABLED;
512
513                         if (bProject)
514                                 bButtonEnabled = TRUE;
515                         else
516                                 bButtonEnabled = (pathsType != DOES_NOT_EXIST);
517                 }
518
519                 PostMessage(hWnd, WM_USER + 1, bButtonEnabled, MAKELPARAM(iStatusMsgId, iUnpackerStatusMsgId)); 
520         }
521
522         return 0;
523 }
524
525 /** 
526  * @brief Enable/disable components based on validity of paths.
527  */
528 void COpenDlg::UpdateButtonStates()
529 {
530         UpdateData(TRUE); // load member variables from screen
531         KillTimer(IDT_CHECKFILES);
532         TrimPaths();
533         
534         if (!m_pUpdateButtonStatusThread)
535         {
536                 m_pUpdateButtonStatusThread = AfxBeginThread(
537                         UpdateButtonStatesThread, NULL, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
538                 m_pUpdateButtonStatusThread->m_bAutoDelete = FALSE;
539                 m_pUpdateButtonStatusThread->ResumeThread();
540                 while (PostThreadMessage(m_pUpdateButtonStatusThread->m_nThreadID, WM_NULL, 0, 0) == FALSE)
541                         Sleep(1);
542         }
543
544         UpdateButtonStatesThreadParams *pParams = new UpdateButtonStatesThreadParams;
545         pParams->m_hWnd = this->m_hWnd;
546         if (m_strPath[2].IsEmpty())
547                 pParams->m_paths = PathContext(m_strPath[0], m_strPath[1]);
548         else
549                 pParams->m_paths = PathContext(m_strPath[0], m_strPath[1], m_strPath[2]);
550
551         PostThreadMessage(m_pUpdateButtonStatusThread->m_nThreadID, WM_USER, (WPARAM)pParams, 0);
552 }
553
554 void COpenDlg::TerminateThreadIfRunning()
555 {
556         if (!m_pUpdateButtonStatusThread)
557                 return;
558
559         PostThreadMessage(m_pUpdateButtonStatusThread->m_nThreadID, WM_QUIT, 0, 0);
560         DWORD dwResult = WaitForSingleObject(m_pUpdateButtonStatusThread->m_hThread, 100);
561         if (dwResult != WAIT_OBJECT_0)
562         {
563                 m_pUpdateButtonStatusThread->SuspendThread();
564                 TerminateThread(m_pUpdateButtonStatusThread->m_hThread, 0);
565         }
566         delete m_pUpdateButtonStatusThread;
567         m_pUpdateButtonStatusThread = NULL;
568 }
569
570 /**
571  * @brief Called when user changes selection in left/middle/right path's combo box.
572  */
573 void COpenDlg::OnSelchangeCombo(int index) 
574 {
575         int sel = m_ctlPath[index].GetCurSel();
576         if (sel != CB_ERR)
577         {
578                 m_ctlPath[index].GetLBText(sel, m_strPath[index]);
579                 m_ctlPath[index].SetWindowText(m_strPath[index]);
580                 UpdateData(TRUE);
581         }
582         UpdateButtonStates();
583 }
584
585 void COpenDlg::OnSelchangePath0Combo() 
586 {
587         OnSelchangeCombo(0);
588 }
589
590 void COpenDlg::OnSelchangePath1Combo() 
591 {
592         OnSelchangeCombo(1);
593 }
594
595 void COpenDlg::OnSelchangePath2Combo() 
596 {
597         OnSelchangeCombo(2);
598 }
599
600 /** 
601  * @brief Called every time paths are edited.
602  */
603 void COpenDlg::OnEditEvent()
604 {
605         // (Re)start timer to path validity check delay
606         // If timer starting fails, update buttonstates immediately
607         if (!SetTimer(IDT_CHECKFILES, CHECKFILES_TIMEOUT, NULL))
608                 UpdateButtonStates();
609 }
610
611 /**
612  * @brief Handle timer events.
613  * Checks if paths are valid and sets control states accordingly.
614  * @param [in] nIDEvent Timer ID that fired.
615  */
616 void COpenDlg::OnTimer(UINT_PTR nIDEvent)
617 {
618         if (nIDEvent == IDT_CHECKFILES)
619                 UpdateButtonStates();
620
621         CDialog::OnTimer(nIDEvent);
622 }
623
624 /**
625  * @brief Called when users selects plugin browse button.
626  */
627 void COpenDlg::OnSelectUnpacker()
628 {
629         UpdateData(TRUE);
630
631         int index;
632         int nFiles = 0;
633         for (index = 0; index < countof(m_strPath); index++)
634         {
635                 if (index == 2 && m_strPath[index].IsEmpty())
636                         break;
637                 m_files.SetSize(nFiles + 1);
638                 m_files[nFiles] = m_strPath[index];
639                 nFiles++;
640         }
641         m_pathsType = GetPairComparability(m_files);
642
643         if (m_pathsType != IS_EXISTING_FILE) 
644                 return;
645
646         // let the user select a handler
647         CSelectUnpackerDlg dlg(m_files[0].c_str(), this);
648         dlg.SetInitialInfoHandler(&m_infoHandler);
649
650         if (dlg.DoModal() == IDOK)
651         {
652                 m_infoHandler = dlg.GetInfoHandler();
653
654                 m_strUnpacker = m_infoHandler.pluginName.c_str();
655
656                 UpdateData(FALSE);
657         }
658 }
659
660 LRESULT COpenDlg::OnUpdateStatus(WPARAM wParam, LPARAM lParam)
661 {
662         BOOL bEnabledButtons = (BOOL)wParam;
663
664         m_ctlOk.EnableWindow(bEnabledButtons);
665         m_ctlUnpacker.EnableWindow(bEnabledButtons);
666         m_ctlSelectUnpacker.EnableWindow(bEnabledButtons);
667
668         SetStatus(HIWORD(lParam));
669         SetStatus(LOWORD(lParam));
670
671         return 0;
672 }
673
674 /**
675  * @brief Sets the path status text.
676  * The open dialog shows a status text of selected paths. This function
677  * is used to set that status text.
678  * @param [in] msgID Resource ID of status text to set.
679  */
680 void COpenDlg::SetStatus(UINT msgID)
681 {
682         String msg = theApp.LoadString(msgID);
683         SetDlgItemText(IDC_OPEN_STATUS, msg.c_str());
684 }
685
686 /**
687  * @brief Set the plugin edit box text.
688  * Plugin edit box is at the same time a plugin status view. This function
689  * sets the status text.
690  * @param [in] msgID Resource ID of status text to set.
691  */
692 void COpenDlg::SetUnpackerStatus(UINT msgID)
693 {
694         String msg = theApp.LoadString(msgID);
695         SetDlgItemText(IDC_UNPACKER_EDIT, msg.c_str());
696 }
697
698 /** 
699  * @brief Called when "Select..." button for filters is selected.
700  */
701 void COpenDlg::OnSelectFilter()
702 {
703         String filterPrefix = theApp.LoadString(IDS_FILTER_PREFIX);
704         CString curFilter;
705
706         const BOOL bUseMask = theApp.m_globalFileFilter.IsUsingMask();
707         GetDlgItemText(IDC_EXT_COMBO, curFilter);
708         curFilter.TrimLeft();
709         curFilter.TrimRight();
710
711         GetMainFrame()->SelectFilter();
712         
713         String filterNameOrMask = theApp.m_globalFileFilter.GetFilterNameOrMask();
714         if (theApp.m_globalFileFilter.IsUsingMask())
715         {
716                 // If we had filter chosen and now has mask we can overwrite filter
717                 if (!bUseMask || curFilter[0] != '*')
718                 {
719                         SetDlgItemText(IDC_EXT_COMBO, filterNameOrMask.c_str());
720                 }
721         }
722         else
723         {
724                 filterNameOrMask = filterPrefix + filterNameOrMask;
725                 SetDlgItemText(IDC_EXT_COMBO, filterNameOrMask.c_str());
726         }
727 }
728
729
730 /** 
731  * @brief Read paths and filter from project file.
732  * Reads the given project file. After the file is read, found paths and
733  * filter is updated to dialog GUI. Other possible settings found in the
734  * project file are kept in memory and used later when loading paths
735  * selected.
736  * @param [in] path Path to the project file.
737  * @return TRUE if the project file was successfully loaded, FALSE otherwise.
738  */
739 BOOL COpenDlg::LoadProjectFile(const CString &path)
740 {
741         String filterPrefix = theApp.LoadString(IDS_FILTER_PREFIX);
742         String err;
743
744         m_pProjectFile = new ProjectFile;
745         if (m_pProjectFile == NULL)
746                 return FALSE;
747
748         if (!m_pProjectFile->Read(path, &err))
749         {
750                 if (!err.empty())
751                 {
752                         CString msg;
753                         LangFormatString2(msg, IDS_ERROR_FILEOPEN, path, err.c_str());
754                         AfxMessageBox(msg, MB_ICONSTOP);
755                 }
756                 return FALSE;
757         }
758         else
759         {
760                 m_pProjectFile->GetPaths(m_files, m_bRecurse);
761                 if (m_pProjectFile->HasFilter())
762                 {
763                         m_strExt = m_pProjectFile->GetFilter().c_str();
764                         m_strExt.TrimLeft();
765                         m_strExt.TrimRight();
766                         if (m_strExt[0] != '*')
767                                 m_strExt.Insert(0, filterPrefix.c_str());
768                 }
769         }
770         return TRUE;
771 }
772
773 /** 
774  * @brief Removes whitespaces from left and right paths
775  * @note Assumes UpdateData(TRUE) is called before this function.
776  */
777 void COpenDlg::TrimPaths()
778 {
779         for (int index = 0; index < countof(m_strPath); index++)
780         {
781                 m_strPath[index].TrimLeft();
782                 m_strPath[index].TrimRight();
783         }
784 }
785
786 /** 
787  * @brief Update control states when dialog is activated.
788  *
789  * Update control states when user re-activates dialog. User might have
790  * switched for other program to e.g. update files/folders and then
791  * swiches back to WinMerge. Its nice to see WinMerge detects updated
792  * files/folders.
793  */
794 void COpenDlg::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized)
795 {
796         CDialog::OnActivate(nState, pWndOther, bMinimized);
797
798         if (nState == WA_ACTIVE || nState == WA_CLICKACTIVE)
799                 UpdateButtonStates();
800 }
801
802 /**
803  * @brief Open help from mainframe when user presses F1.
804  */
805 void COpenDlg::OnHelp()
806 {
807         GetMainFrame()->ShowHelp(OpenDlgHelpLocation);
808 }
809
810 /////////////////////////////////////////////////////////////////////////////
811 //
812 //      OnDropFiles code from CDropEdit
813 //      Copyright 1997 Chris Losinger
814 //
815 //      shortcut expansion code modified from :
816 //      CShortcut, 1996 Rob Warner
817 //
818
819 /**
820  * @brief Drop paths(s) to the dialog.
821  * One or two paths can be dropped to the dialog. The behaviour is:
822  *   If 1 file:
823  *     - drop to empty path edit box (check left first)
824  *     - if both boxes have a path, drop to left path
825  *   If two files:
826  *    - overwrite both paths, empty or not
827  * @param [in] dropInfo Dropped data, including paths.
828  */
829 void COpenDlg::OnDropFiles(HDROP dropInfo)
830 {
831         // Get the number of pathnames that have been dropped
832         UINT wNumFilesDropped = DragQueryFile(dropInfo, 0xFFFFFFFF, NULL, 0);
833         CString files[2];
834         UINT fileCount = 0;
835
836         // get all file names. but we'll only need the first one.
837         for (WORD x = 0 ; x < wNumFilesDropped; x++)
838         {
839                 // Get the number of bytes required by the file's full pathname
840                 UINT wPathnameSize = DragQueryFile(dropInfo, x, NULL, 0);
841
842                 // Allocate memory to contain full pathname & zero byte
843                 wPathnameSize += 1;
844                 LPTSTR npszFile = (TCHAR *) new TCHAR[wPathnameSize];
845
846                 // If not enough memory, skip this one
847                 if (npszFile == NULL)
848                         continue;
849
850                 // Copy the pathname into the buffer
851                 DragQueryFile(dropInfo, x, npszFile, wPathnameSize);
852
853                 if (x < 2)
854                 {
855                         files[x] = npszFile;
856                         fileCount++;
857                 }
858                 delete[] npszFile;
859         }
860
861         // Free the memory block containing the dropped-file information
862         DragFinish(dropInfo);
863
864         for (UINT i = 0; i < fileCount; i++)
865         {
866                 if (paths_IsShortcut((LPCTSTR)files[i]))
867                 {
868                         // if this was a shortcut, we need to expand it to the target path
869                         CString expandedFile = ExpandShortcut((LPCTSTR)files[i]).c_str();
870
871                         // if that worked, we should have a real file name
872                         if (!expandedFile.IsEmpty())
873                                 files[i] = expandedFile;
874                 }
875         }
876
877         // Add dropped paths to the dialog
878         UpdateData(TRUE);
879         if (fileCount == 3)
880         {
881                 m_strPath[0] = files[0];
882                 m_strPath[1] = files[1];
883                 m_strPath[2] = files[2];
884                 UpdateData(FALSE);
885                 UpdateButtonStates();
886         }
887         else if (fileCount == 2)
888         {
889                 m_strPath[0] = files[0];
890                 m_strPath[1] = files[1];
891                 UpdateData(FALSE);
892                 UpdateButtonStates();
893         }
894         else if (fileCount == 1)
895         {
896                 if (m_strPath[0].IsEmpty())
897                         m_strPath[0] = files[0];
898                 else if (m_strPath[1].IsEmpty())
899                         m_strPath[1] = files[0];
900                 else if (m_strPath[2].IsEmpty())
901                         m_strPath[2] = files[0];
902                 else
903                         m_strPath[0] = files[0];
904                 UpdateData(FALSE);
905                 UpdateButtonStates();
906         }
907 }