OSDN Git Service

Add missing file
[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$
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
44 #ifdef COMPILE_MULTIMON_STUBS
45 #undef COMPILE_MULTIMON_STUBS
46 #endif
47 #include <multimon.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 /////////////////////////////////////////////////////////////////////////////
64 // COpenDlg dialog
65
66 /**
67  * @brief Standard constructor.
68  */
69 COpenDlg::COpenDlg(CWnd* pParent /*=NULL*/)
70         : CDialog(COpenDlg::IDD, pParent)
71         , m_pathsType(DOES_NOT_EXIST)
72         , m_bOverwriteRecursive(FALSE)
73         , m_bRecurse(FALSE)
74         , m_pProjectFile(NULL)
75 {
76 }
77
78 /**
79  * @brief Standard destructor.
80  */
81 COpenDlg::~COpenDlg()
82 {
83         delete m_pProjectFile;
84 }
85
86 void COpenDlg::DoDataExchange(CDataExchange* pDX)
87 {
88         CDialog::DoDataExchange(pDX);
89         //{{AFX_DATA_MAP(COpenDlg)
90         DDX_Control(pDX, IDC_SELECT_UNPACKER, m_ctlSelectUnpacker);
91         DDX_Control(pDX, IDC_UNPACKER_EDIT, m_ctlUnpacker);
92         DDX_Control(pDX, IDC_EXT_COMBO, m_ctlExt);
93         DDX_Control(pDX, IDOK, m_ctlOk);
94         DDX_Control(pDX, IDC_RECURS_CHECK, m_ctlRecurse);
95         DDX_Control(pDX, IDC_RIGHT_COMBO, m_ctlRight);
96         DDX_Control(pDX, IDC_LEFT_COMBO, m_ctlLeft);
97         DDX_CBStringExact(pDX, IDC_LEFT_COMBO, m_strLeft);
98         DDX_CBStringExact(pDX, IDC_RIGHT_COMBO, m_strRight);
99         DDX_Check(pDX, IDC_RECURS_CHECK, m_bRecurse);
100         DDX_CBStringExact(pDX, IDC_EXT_COMBO, m_strExt);
101         DDX_Text(pDX, IDC_UNPACKER_EDIT, m_strUnpacker);
102         //}}AFX_DATA_MAP
103 }
104
105
106 BEGIN_MESSAGE_MAP(COpenDlg, CDialog)
107         //{{AFX_MSG_MAP(COpenDlg)
108         ON_BN_CLICKED(IDC_LEFT_BUTTON, OnLeftButton)
109         ON_BN_CLICKED(IDC_RIGHT_BUTTON, OnRightButton)
110         ON_CBN_SELCHANGE(IDC_LEFT_COMBO, OnSelchangeLeftCombo)
111         ON_CBN_SELCHANGE(IDC_RIGHT_COMBO, OnSelchangeRightCombo)
112         ON_CBN_EDITCHANGE(IDC_LEFT_COMBO, OnEditEvent)
113         ON_BN_CLICKED(IDC_SELECT_UNPACKER, OnSelectUnpacker)
114         ON_CBN_SELENDCANCEL(IDC_LEFT_COMBO, UpdateButtonStates)
115         ON_CBN_EDITCHANGE(IDC_RIGHT_COMBO, OnEditEvent)
116         ON_CBN_SELENDCANCEL(IDC_RIGHT_COMBO, UpdateButtonStates)
117         ON_WM_TIMER()
118         ON_BN_CLICKED(IDC_SELECT_FILTER, OnSelectFilter)
119         ON_WM_ACTIVATE()
120         ON_COMMAND(ID_HELP, OnHelp)
121         ON_WM_DROPFILES()
122         //}}AFX_MSG_MAP
123 END_MESSAGE_MAP()
124
125 /////////////////////////////////////////////////////////////////////////////
126 // COpenDlg message handlers
127
128 /**
129  * @brief Handler for WM_INITDIALOG; conventional location to initialize controls
130  * At this point dialog and control windows exist
131  */
132 BOOL COpenDlg::OnInitDialog() 
133 {
134         theApp.TranslateDialog(m_hWnd);
135         CDialog::OnInitDialog();
136         
137         // setup handler for resizing this dialog       
138         m_constraint.InitializeCurrentSize(this);
139         // configure how individual controls adjust when dialog resizes
140         m_constraint.ConstrainItem(IDC_LEFT_COMBO, 0, 1, 0, 0); // grows right
141         m_constraint.ConstrainItem(IDC_RIGHT_COMBO, 0, 1, 0, 0); // grows right
142         m_constraint.ConstrainItem(IDC_EXT_COMBO, 0, 1, 0, 0); // grows right
143         m_constraint.ConstrainItem(IDC_UNPACKER_EDIT, 0, 1, 0, 0); // grows right
144         m_constraint.ConstrainItem(IDC_FILES_DIRS_GROUP, 0, 1, 0, 0); // grows right
145         m_constraint.ConstrainItem(IDC_LEFT_BUTTON, 1, 0, 0, 0); // slides right
146         m_constraint.ConstrainItem(IDC_RIGHT_BUTTON, 1, 0, 0, 0); // slides right
147         m_constraint.ConstrainItem(IDC_SELECT_UNPACKER, 1, 0, 0, 0); // slides right
148         m_constraint.ConstrainItem(IDC_OPEN_STATUS, 0, 1, 0, 0); // grows right
149         m_constraint.ConstrainItem(IDC_SELECT_FILTER, 1, 0, 0, 0); // slides right
150         m_constraint.ConstrainItem(IDOK, 1, 0, 0, 0); // slides right
151         m_constraint.ConstrainItem(IDCANCEL, 1, 0, 0, 0); // slides right
152         m_constraint.ConstrainItem(ID_HELP, 1, 0, 0, 0); // slides right
153         m_constraint.DisallowHeightGrowth();
154         m_constraint.SubclassWnd(); // install subclassing
155         m_constraint.LoadPosition(_T("ResizeableDialogs"), _T("OpenDlg"), false); // persist size via registry
156
157         CMainFrame::CenterToMainFrame(this);
158
159         m_ctlLeft.LoadState(_T("Files\\Left"));
160         m_ctlRight.LoadState(_T("Files\\Right"));
161         m_ctlExt.LoadState(_T("Files\\Ext"));
162         UpdateData(m_strLeft.IsEmpty() && m_strRight.IsEmpty());
163
164         int nSource = GetOptionsMgr()->GetInt(OPT_AUTO_COMPLETE_SOURCE);
165         if (nSource > 0)
166         {
167                 m_ctlLeft.SetAutoComplete(nSource);
168                 m_ctlRight.SetAutoComplete(nSource);
169         }
170
171         String filterNameOrMask = theApp.m_globalFileFilter.GetFilterNameOrMask();
172         BOOL bMask = theApp.m_globalFileFilter.IsUsingMask();
173
174         if (!bMask)
175         {
176                 String filterPrefix = theApp.LoadString(IDS_FILTER_PREFIX);
177                 filterNameOrMask = filterPrefix + filterNameOrMask;
178         }
179
180         int ind = m_ctlExt.FindStringExact(0, filterNameOrMask.c_str());
181         if (ind != CB_ERR)
182                 m_ctlExt.SetCurSel(ind);
183         else
184         {
185                 ind = m_ctlExt.InsertString(0, filterNameOrMask.c_str());
186                 if (ind != CB_ERR)
187                         m_ctlExt.SetCurSel(ind);
188                 else
189                         LogErrorString(_T("Failed to add string to filters combo list!"));
190         }
191
192         if (!GetOptionsMgr()->GetBool(OPT_VERIFY_OPEN_PATHS))
193         {
194                 m_ctlOk.EnableWindow(TRUE);
195                 m_ctlUnpacker.EnableWindow(TRUE);
196                 m_ctlSelectUnpacker.EnableWindow(TRUE);
197         }
198
199         UpdateButtonStates();
200
201         if (!m_bOverwriteRecursive)
202                 m_bRecurse = theApp.GetProfileInt(_T("Settings"), _T("Recurse"), 0) == 1;
203
204         m_strUnpacker = m_infoHandler.pluginName.c_str();
205         UpdateData(FALSE);
206         SetStatus(IDS_OPEN_FILESDIRS);
207         SetUnpackerStatus(IDS_OPEN_UNPACKERDISABLED);
208         return TRUE;
209 }
210
211 /** 
212  * @brief Called when "Browse..." button is selected for left path.
213  */
214 void COpenDlg::OnLeftButton()
215 {
216         CString s;
217         String sfolder;
218         UpdateData(TRUE); 
219
220         PATH_EXISTENCE existence = paths_DoesPathExist(m_strLeft);
221         switch (existence)
222         {
223         case IS_EXISTING_DIR:
224                 sfolder = m_strLeft;
225                 break;
226         case IS_EXISTING_FILE:
227                 sfolder = GetPathOnly(m_strLeft);
228                 break;
229         case DOES_NOT_EXIST:
230                 // Do nothing, empty foldername will be passed to dialog
231                 break;
232         default:
233                 _RPTF0(_CRT_ERROR, "Invalid return value from paths_DoesPathExist()");
234                 break;
235         }
236
237         if (SelectFileOrFolder(GetSafeHwnd(), s, sfolder.c_str()))
238         {
239                 m_strLeft = s;
240                 m_strLeftBrowsePath = s;
241                 UpdateData(FALSE);
242                 UpdateButtonStates();
243         }       
244 }
245
246 /** 
247  * @brief Called when "Browse..." button is selected for right path.
248  */
249 void COpenDlg::OnRightButton() 
250 {
251         CString s;
252         String sfolder;
253         UpdateData(TRUE);
254
255         PATH_EXISTENCE existence = paths_DoesPathExist(m_strRight);
256         switch (existence)
257         {
258         case IS_EXISTING_DIR:
259                 sfolder = m_strRight;
260                 break;
261         case IS_EXISTING_FILE:
262                 sfolder = GetPathOnly(m_strRight);
263                 break;
264         case DOES_NOT_EXIST:
265                 // Do nothing, empty foldername will be passed to dialog
266                 break;
267         default:
268                 _RPTF0(_CRT_ERROR, "Invalid return value from paths_DoesPathExist()");
269                 break;
270         }
271
272         if (SelectFileOrFolder(GetSafeHwnd(), s, sfolder.c_str()))
273         {
274                 m_strRight = s;
275                 m_strRightBrowsePath = s;
276                 UpdateData(FALSE);
277                 UpdateButtonStates();
278         }       
279 }
280
281 /** 
282  * @brief Called when dialog is closed with "OK".
283  *
284  * Checks that paths are valid and sets filters.
285  */
286 void COpenDlg::OnOK() 
287 {
288         const String filterPrefix = theApp.LoadString(IDS_FILTER_PREFIX);
289
290         UpdateData(TRUE);
291         TrimPaths();
292
293         // If left path is a project-file, load it
294         String ext;
295         SplitFilename(m_strLeft, NULL, NULL, &ext);
296         CString sExt(ext.c_str());
297         if (m_strRight.IsEmpty() && sExt.CompareNoCase(PROJECTFILE_EXT) == 0)
298                 LoadProjectFile(m_strLeft);
299
300         m_pathsType = GetPairComparability(m_strLeft, m_strRight);
301
302         if (m_pathsType == DOES_NOT_EXIST)
303         {
304                 LangMessageBox(IDS_ERROR_INCOMPARABLE, MB_ICONSTOP);
305                 return;
306         }
307
308         // If user has edited path by hand, expand environment variables
309         BOOL bExpandLeft = FALSE;
310         BOOL bExpandRight = FALSE;
311         if (m_strLeftBrowsePath.CompareNoCase(m_strLeft) != 0)
312                 bExpandLeft = TRUE;
313         if (m_strRightBrowsePath.CompareNoCase(m_strRight) != 0)
314                 bExpandRight = TRUE;
315
316         m_strRight = paths_GetLongPath(m_strRight, bExpandRight).c_str();
317         m_strLeft = paths_GetLongPath(m_strLeft, bExpandLeft).c_str();
318
319         // Add trailing '\' for directories if its missing
320         if (m_pathsType == IS_EXISTING_DIR)
321         {
322                 if (!paths_EndsWithSlash(m_strLeft))
323                         m_strLeft += '\\';
324                 if (!paths_EndsWithSlash(m_strRight))
325                         m_strRight += '\\';
326         }
327
328         UpdateData(FALSE);
329         KillTimer(IDT_CHECKFILES);
330
331         String filter((LPCTSTR)m_strExt);
332         filter = string_trim_ws(filter);
333
334         // If prefix found from start..
335         if (filter.find(filterPrefix, 0) == 0)
336         {
337                 // Remove prefix + space
338                 filter.erase(0, filterPrefix.length());
339                 if (!theApp.m_globalFileFilter.SetFilter(filter))
340                 {
341                         // If filtername is not found use default *.* mask
342                         theApp.m_globalFileFilter.SetFilter(_T("*.*"));
343                         filter = _T("*.*");
344                 }
345                 GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, filter.c_str());
346         }
347         else
348         {
349                 BOOL bFilterSet = theApp.m_globalFileFilter.SetFilter(filter);
350                 if (!bFilterSet)
351                         m_strExt = theApp.m_globalFileFilter.GetFilterNameOrMask().c_str();
352                 GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, filter.c_str());
353         }
354
355         SaveComboboxStates();
356         theApp.WriteProfileInt(_T("Settings"), _T("Recurse"), m_bRecurse);
357
358         CDialog::OnOK();
359 }
360
361 /** 
362  * @brief Called when dialog is closed via Cancel.
363  *
364  * Open-dialog is canceled when 'Cancel' button is selected or
365  * Esc-key is pressed. Save combobox states, since user may have
366  * removed items from them and don't want them to re-appear.
367  */
368 void COpenDlg::OnCancel()
369 {
370         SaveComboboxStates();
371         CDialog::OnCancel();
372 }
373
374 /** 
375  * @brief Save File- and filter-combobox states.
376  */
377 void COpenDlg::SaveComboboxStates()
378 {
379         m_ctlLeft.SaveState(_T("Files\\Left"));
380         m_ctlRight.SaveState(_T("Files\\Right"));
381         m_ctlExt.SaveState(_T("Files\\Ext"));
382 }
383
384 /** 
385  * @brief Enable/disable components based on validity of paths.
386  */
387 void COpenDlg::UpdateButtonStates()
388 {
389         BOOL bLeftInvalid = FALSE;
390         BOOL bRightInvalid = FALSE;
391
392         UpdateData(TRUE); // load member variables from screen
393         KillTimer(IDT_CHECKFILES);
394         TrimPaths();
395
396         // Check if we have project file as left side path
397         BOOL bProject = FALSE;
398         String ext;
399         SplitFilename(m_strLeft, NULL, NULL, &ext);
400         CString sExt(ext.c_str());
401         if (m_strRight.IsEmpty() && sExt.CompareNoCase(PROJECTFILE_EXT) == 0)
402                 bProject = TRUE;
403
404         // Enable buttons as appropriate
405         if (GetOptionsMgr()->GetBool(OPT_VERIFY_OPEN_PATHS))
406         {
407                 PATH_EXISTENCE pathsType = GetPairComparability(m_strLeft, m_strRight);
408
409                 if (bProject)
410                 {
411                         m_ctlOk.EnableWindow(TRUE);
412                         m_ctlUnpacker.EnableWindow(TRUE);
413                         m_ctlSelectUnpacker.EnableWindow(TRUE);
414                 }
415                 else
416                 {
417                         m_ctlOk.EnableWindow(pathsType != DOES_NOT_EXIST);
418                         m_ctlUnpacker.EnableWindow(pathsType == IS_EXISTING_FILE);
419                         m_ctlSelectUnpacker.EnableWindow(pathsType == IS_EXISTING_FILE);
420                 }
421
422                 if (!bProject)
423                 {
424                         if (paths_DoesPathExist(m_strLeft) == DOES_NOT_EXIST)
425                                 bLeftInvalid = TRUE;
426                         if (paths_DoesPathExist(m_strRight) == DOES_NOT_EXIST)
427                                 bRightInvalid = TRUE;
428                 }
429
430                 if (bLeftInvalid && bRightInvalid)
431                         SetStatus(IDS_OPEN_BOTHINVALID);
432                 else if (bLeftInvalid)
433                         SetStatus(IDS_OPEN_LEFTINVALID);
434                 else if (bRightInvalid)
435                         SetStatus(IDS_OPEN_RIGHTINVALID);
436                 else if (!bLeftInvalid && !bRightInvalid && pathsType == DOES_NOT_EXIST)
437                         SetStatus(IDS_OPEN_MISMATCH);
438                 else
439                         SetStatus(IDS_OPEN_FILESDIRS);
440
441                 if (pathsType == IS_EXISTING_FILE || bProject)
442                         SetUnpackerStatus(0);   //Empty field
443                 else
444                         SetUnpackerStatus(IDS_OPEN_UNPACKERDISABLED);
445         }
446 }
447
448 /**
449  * @brief Called when user changes selection in left path's combo box.
450  */
451 void COpenDlg::OnSelchangeLeftCombo() 
452 {
453         int sel = m_ctlLeft.GetCurSel();
454         if (sel != CB_ERR)
455         {
456                 m_ctlLeft.GetLBText(sel, m_strLeft);
457                 m_ctlLeft.SetWindowText(m_strLeft);
458                 UpdateData(TRUE);
459         }
460         UpdateButtonStates();
461 }
462
463 /**
464  * @brief Called when user changes selection in right path's combo box.
465  */
466 void COpenDlg::OnSelchangeRightCombo() 
467 {
468         int sel = m_ctlRight.GetCurSel();
469         if (sel != CB_ERR)
470         {
471                 m_ctlRight.GetLBText(sel, m_strRight);
472                 m_ctlRight.SetWindowText(m_strRight);
473                 UpdateData(TRUE);
474         }
475         UpdateButtonStates();
476 }
477
478 /** 
479  * @brief Called every time paths are edited.
480  */
481 void COpenDlg::OnEditEvent()
482 {
483         // (Re)start timer to path validity check delay
484         // If timer starting fails, update buttonstates immediately
485         if (!SetTimer(IDT_CHECKFILES, CHECKFILES_TIMEOUT, NULL))
486                 UpdateButtonStates();
487 }
488
489 /**
490  * @brief Handle timer events.
491  * Checks if paths are valid and sets control states accordingly.
492  * @param [in] nIDEvent Timer ID that fired.
493  */
494 void COpenDlg::OnTimer(UINT_PTR nIDEvent)
495 {
496         if (nIDEvent == IDT_CHECKFILES)
497                 UpdateButtonStates();
498
499         CDialog::OnTimer(nIDEvent);
500 }
501
502 /**
503  * @brief Called when users selects plugin browse button.
504  */
505 void COpenDlg::OnSelectUnpacker()
506 {
507         UpdateData(TRUE);
508
509         m_pathsType = GetPairComparability(m_strLeft, m_strRight);
510
511         if (m_pathsType != IS_EXISTING_FILE) 
512                 return;
513
514         // let the user select a handler
515         CSelectUnpackerDlg dlg(m_strLeft, m_strRight, this);
516         dlg.SetInitialInfoHandler(&m_infoHandler);
517
518         if (dlg.DoModal() == IDOK)
519         {
520                 m_infoHandler = dlg.GetInfoHandler();
521
522                 m_strUnpacker = m_infoHandler.pluginName.c_str();
523
524                 UpdateData(FALSE);
525         }
526 }
527
528 /**
529  * @brief Sets the path status text.
530  * The open dialog shows a status text of selected paths. This function
531  * is used to set that status text.
532  * @param [in] msgID Resource ID of status text to set.
533  */
534 void COpenDlg::SetStatus(UINT msgID)
535 {
536         String msg = theApp.LoadString(msgID);
537         SetDlgItemText(IDC_OPEN_STATUS, msg.c_str());
538 }
539
540 /**
541  * @brief Set the plugin edit box text.
542  * Plugin edit box is at the same time a plugin status view. This function
543  * sets the status text.
544  * @param [in] msgID Resource ID of status text to set.
545  */
546 void COpenDlg::SetUnpackerStatus(UINT msgID)
547 {
548         String msg = theApp.LoadString(msgID);
549         SetDlgItemText(IDC_UNPACKER_EDIT, msg.c_str());
550 }
551
552 /** 
553  * @brief Called when "Select..." button for filters is selected.
554  */
555 void COpenDlg::OnSelectFilter()
556 {
557         String filterPrefix = theApp.LoadString(IDS_FILTER_PREFIX);
558         CString curFilter;
559
560         const BOOL bUseMask = theApp.m_globalFileFilter.IsUsingMask();
561         GetDlgItemText(IDC_EXT_COMBO, curFilter);
562         curFilter.TrimLeft();
563         curFilter.TrimRight();
564
565         GetMainFrame()->SelectFilter();
566         
567         String filterNameOrMask = theApp.m_globalFileFilter.GetFilterNameOrMask();
568         if (theApp.m_globalFileFilter.IsUsingMask())
569         {
570                 // If we had filter chosen and now has mask we can overwrite filter
571                 if (!bUseMask || curFilter[0] != '*')
572                 {
573                         SetDlgItemText(IDC_EXT_COMBO, filterNameOrMask.c_str());
574                 }
575         }
576         else
577         {
578                 filterNameOrMask = filterPrefix + filterNameOrMask;
579                 SetDlgItemText(IDC_EXT_COMBO, filterNameOrMask.c_str());
580         }
581 }
582
583
584 /** 
585  * @brief Read paths and filter from project file.
586  * Reads the given project file. After the file is read, found paths and
587  * filter is updated to dialog GUI. Other possible settings found in the
588  * project file are kept in memory and used later when loading paths
589  * selected.
590  * @param [in] path Path to the project file.
591  * @return TRUE if the project file was successfully loaded, FALSE otherwise.
592  */
593 BOOL COpenDlg::LoadProjectFile(const CString &path)
594 {
595         String filterPrefix = theApp.LoadString(IDS_FILTER_PREFIX);
596         String err;
597
598         m_pProjectFile = new ProjectFile;
599         if (m_pProjectFile == NULL)
600                 return FALSE;
601
602         if (!m_pProjectFile->Read(path, &err))
603         {
604                 if (!err.empty())
605                 {
606                         CString msg;
607                         LangFormatString2(msg, IDS_ERROR_FILEOPEN, path, err.c_str());
608                         AfxMessageBox(msg, MB_ICONSTOP);
609                 }
610                 return FALSE;
611         }
612         else
613         {
614                 m_strLeft = m_pProjectFile->GetLeft().c_str();
615                 m_strRight = m_pProjectFile->GetRight().c_str();
616                 m_bRecurse = m_pProjectFile->GetSubfolders();
617                 if (m_pProjectFile->HasFilter())
618                 {
619                         m_strExt = m_pProjectFile->GetFilter().c_str();
620                         m_strExt.TrimLeft();
621                         m_strExt.TrimRight();
622                         if (m_strExt[0] != '*')
623                                 m_strExt.Insert(0, filterPrefix.c_str());
624                 }
625         }
626         return TRUE;
627 }
628
629 /** 
630  * @brief Removes whitespaces from left and right paths
631  * @note Assumes UpdateData(TRUE) is called before this function.
632  */
633 void COpenDlg::TrimPaths()
634 {
635         m_strLeft.TrimLeft();
636         m_strLeft.TrimRight();
637         m_strRight.TrimLeft();
638         m_strRight.TrimRight();
639 }
640
641 /** 
642  * @brief Update control states when dialog is activated.
643  *
644  * Update control states when user re-activates dialog. User might have
645  * switched for other program to e.g. update files/folders and then
646  * swiches back to WinMerge. Its nice to see WinMerge detects updated
647  * files/folders.
648  */
649 void COpenDlg::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized)
650 {
651         CDialog::OnActivate(nState, pWndOther, bMinimized);
652
653         if (nState == WA_ACTIVE || nState == WA_CLICKACTIVE)
654                 UpdateButtonStates();
655 }
656
657 /**
658  * @brief Open help from mainframe when user presses F1.
659  */
660 void COpenDlg::OnHelp()
661 {
662         GetMainFrame()->ShowHelp(OpenDlgHelpLocation);
663 }
664
665 /////////////////////////////////////////////////////////////////////////////
666 //
667 //      OnDropFiles code from CDropEdit
668 //      Copyright 1997 Chris Losinger
669 //
670 //      shortcut expansion code modified from :
671 //      CShortcut, 1996 Rob Warner
672 //
673
674 /**
675  * @brief Drop paths(s) to the dialog.
676  * One or two paths can be dropped to the dialog. The behaviour is:
677  *   If 1 file:
678  *     - drop to empty path edit box (check left first)
679  *     - if both boxes have a path, drop to left path
680  *   If two files:
681  *    - overwrite both paths, empty or not
682  * @param [in] dropInfo Dropped data, including paths.
683  */
684 void COpenDlg::OnDropFiles(HDROP dropInfo)
685 {
686         // Get the number of pathnames that have been dropped
687         UINT wNumFilesDropped = DragQueryFile(dropInfo, 0xFFFFFFFF, NULL, 0);
688         CString files[2];
689         UINT fileCount = 0;
690
691         // get all file names. but we'll only need the first one.
692         for (WORD x = 0 ; x < wNumFilesDropped; x++)
693         {
694                 // Get the number of bytes required by the file's full pathname
695                 UINT wPathnameSize = DragQueryFile(dropInfo, x, NULL, 0);
696
697                 // Allocate memory to contain full pathname & zero byte
698                 wPathnameSize += 1;
699                 LPTSTR npszFile = (TCHAR *) new TCHAR[wPathnameSize];
700
701                 // If not enough memory, skip this one
702                 if (npszFile == NULL)
703                         continue;
704
705                 // Copy the pathname into the buffer
706                 DragQueryFile(dropInfo, x, npszFile, wPathnameSize);
707
708                 if (x < 2)
709                 {
710                         files[x] = npszFile;
711                         fileCount++;
712                 }
713                 delete[] npszFile;
714         }
715
716         // Free the memory block containing the dropped-file information
717         DragFinish(dropInfo);
718
719         for (UINT i = 0; i < fileCount; i++)
720         {
721                 if (paths_IsShortcut((LPCTSTR)files[i]))
722                 {
723                         // if this was a shortcut, we need to expand it to the target path
724                         CString expandedFile = ExpandShortcut((LPCTSTR)files[i]).c_str();
725
726                         // if that worked, we should have a real file name
727                         if (!expandedFile.IsEmpty())
728                                 files[i] = expandedFile;
729                 }
730         }
731
732         // Add dropped paths to the dialog
733         UpdateData(TRUE);
734         if (fileCount == 2)
735         {
736                 m_strLeft = files[0];
737                 m_strRight = files[1];
738                 UpdateData(FALSE);
739                 UpdateButtonStates();
740         }
741         else if (fileCount == 1)
742         {
743                 if (m_strLeft.IsEmpty())
744                         m_strLeft = files[0];
745                 else if (m_strRight.IsEmpty())
746                         m_strRight = files[0];
747                 else
748                         m_strLeft = files[0];
749                 UpdateData(FALSE);
750                 UpdateButtonStates();
751         }
752 }