1 /////////////////////////////////////////////////////////////////////////////
2 // WinMerge: an interactive diff/merge utility
3 // Copyright (C) 1997-2000 Thingamahoochie Software
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.
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.
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.
20 /////////////////////////////////////////////////////////////////////////////
24 * @brief Implementation of the COpenDlg class
26 // ID line follows -- this is updated by SVN
30 #include <sys/types.h>
32 #include "UnicodeString.h"
34 #include "ProjectFile.h"
36 #include "coretools.h"
38 #include "SelectUnpackerDlg.h"
39 #include "OptionsDef.h"
41 #include "OptionsMgr.h"
42 #include "FileOrFolderSelect.h"
44 #ifdef COMPILE_MULTIMON_STUBS
45 #undef COMPILE_MULTIMON_STUBS
52 static char THIS_FILE[] = __FILE__;
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(".*");
60 /** @brief Location for Open-dialog specific help to open. */
61 static TCHAR OpenDlgHelpLocation[] = _T("::/htmlhelp/Open_paths.html");
63 /////////////////////////////////////////////////////////////////////////////
67 * @brief Standard constructor.
69 COpenDlg::COpenDlg(CWnd* pParent /*=NULL*/)
70 : CDialog(COpenDlg::IDD, pParent)
71 , m_pathsType(DOES_NOT_EXIST)
72 , m_bOverwriteRecursive(FALSE)
74 , m_pProjectFile(NULL)
79 * @brief Standard destructor.
83 delete m_pProjectFile;
86 void COpenDlg::DoDataExchange(CDataExchange* pDX)
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);
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)
118 ON_BN_CLICKED(IDC_SELECT_FILTER, OnSelectFilter)
120 ON_COMMAND(ID_HELP, OnHelp)
125 /////////////////////////////////////////////////////////////////////////////
126 // COpenDlg message handlers
129 * @brief Handler for WM_INITDIALOG; conventional location to initialize controls
130 * At this point dialog and control windows exist
132 BOOL COpenDlg::OnInitDialog()
134 theApp.TranslateDialog(m_hWnd);
135 CDialog::OnInitDialog();
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
157 CMainFrame::CenterToMainFrame(this);
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());
164 int nSource = GetOptionsMgr()->GetInt(OPT_AUTO_COMPLETE_SOURCE);
167 m_ctlLeft.SetAutoComplete(nSource);
168 m_ctlRight.SetAutoComplete(nSource);
171 String filterNameOrMask = theApp.m_globalFileFilter.GetFilterNameOrMask();
172 BOOL bMask = theApp.m_globalFileFilter.IsUsingMask();
176 String filterPrefix = theApp.LoadString(IDS_FILTER_PREFIX);
177 filterNameOrMask = filterPrefix + filterNameOrMask;
180 int ind = m_ctlExt.FindStringExact(0, filterNameOrMask.c_str());
182 m_ctlExt.SetCurSel(ind);
185 ind = m_ctlExt.InsertString(0, filterNameOrMask.c_str());
187 m_ctlExt.SetCurSel(ind);
189 LogErrorString(_T("Failed to add string to filters combo list!"));
192 if (!GetOptionsMgr()->GetBool(OPT_VERIFY_OPEN_PATHS))
194 m_ctlOk.EnableWindow(TRUE);
195 m_ctlUnpacker.EnableWindow(TRUE);
196 m_ctlSelectUnpacker.EnableWindow(TRUE);
199 UpdateButtonStates();
201 if (!m_bOverwriteRecursive)
202 m_bRecurse = theApp.GetProfileInt(_T("Settings"), _T("Recurse"), 0) == 1;
204 m_strUnpacker = m_infoHandler.pluginName.c_str();
206 SetStatus(IDS_OPEN_FILESDIRS);
207 SetUnpackerStatus(IDS_OPEN_UNPACKERDISABLED);
212 * @brief Called when "Browse..." button is selected for left path.
214 void COpenDlg::OnLeftButton()
220 PATH_EXISTENCE existence = paths_DoesPathExist(m_strLeft);
223 case IS_EXISTING_DIR:
226 case IS_EXISTING_FILE:
227 sfolder = GetPathOnly(m_strLeft);
230 // Do nothing, empty foldername will be passed to dialog
233 _RPTF0(_CRT_ERROR, "Invalid return value from paths_DoesPathExist()");
237 if (SelectFileOrFolder(GetSafeHwnd(), s, sfolder.c_str()))
240 m_strLeftBrowsePath = s;
242 UpdateButtonStates();
247 * @brief Called when "Browse..." button is selected for right path.
249 void COpenDlg::OnRightButton()
255 PATH_EXISTENCE existence = paths_DoesPathExist(m_strRight);
258 case IS_EXISTING_DIR:
259 sfolder = m_strRight;
261 case IS_EXISTING_FILE:
262 sfolder = GetPathOnly(m_strRight);
265 // Do nothing, empty foldername will be passed to dialog
268 _RPTF0(_CRT_ERROR, "Invalid return value from paths_DoesPathExist()");
272 if (SelectFileOrFolder(GetSafeHwnd(), s, sfolder.c_str()))
275 m_strRightBrowsePath = s;
277 UpdateButtonStates();
282 * @brief Called when dialog is closed with "OK".
284 * Checks that paths are valid and sets filters.
286 void COpenDlg::OnOK()
288 const String filterPrefix = theApp.LoadString(IDS_FILTER_PREFIX);
293 // If left path is a project-file, load it
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);
300 m_pathsType = GetPairComparability(m_strLeft, m_strRight);
302 if (m_pathsType == DOES_NOT_EXIST)
304 LangMessageBox(IDS_ERROR_INCOMPARABLE, MB_ICONSTOP);
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)
313 if (m_strRightBrowsePath.CompareNoCase(m_strRight) != 0)
316 m_strRight = paths_GetLongPath(m_strRight, bExpandRight).c_str();
317 m_strLeft = paths_GetLongPath(m_strLeft, bExpandLeft).c_str();
319 // Add trailing '\' for directories if its missing
320 if (m_pathsType == IS_EXISTING_DIR)
322 if (!paths_EndsWithSlash(m_strLeft))
324 if (!paths_EndsWithSlash(m_strRight))
329 KillTimer(IDT_CHECKFILES);
331 String filter((LPCTSTR)m_strExt);
332 filter = string_trim_ws(filter);
334 // If prefix found from start..
335 if (filter.find(filterPrefix, 0) == 0)
337 // Remove prefix + space
338 filter.erase(0, filterPrefix.length());
339 if (!theApp.m_globalFileFilter.SetFilter(filter))
341 // If filtername is not found use default *.* mask
342 theApp.m_globalFileFilter.SetFilter(_T("*.*"));
345 GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, filter.c_str());
349 BOOL bFilterSet = theApp.m_globalFileFilter.SetFilter(filter);
351 m_strExt = theApp.m_globalFileFilter.GetFilterNameOrMask().c_str();
352 GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, filter.c_str());
355 SaveComboboxStates();
356 theApp.WriteProfileInt(_T("Settings"), _T("Recurse"), m_bRecurse);
362 * @brief Called when dialog is closed via Cancel.
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.
368 void COpenDlg::OnCancel()
370 SaveComboboxStates();
375 * @brief Save File- and filter-combobox states.
377 void COpenDlg::SaveComboboxStates()
379 m_ctlLeft.SaveState(_T("Files\\Left"));
380 m_ctlRight.SaveState(_T("Files\\Right"));
381 m_ctlExt.SaveState(_T("Files\\Ext"));
385 * @brief Enable/disable components based on validity of paths.
387 void COpenDlg::UpdateButtonStates()
389 BOOL bLeftInvalid = FALSE;
390 BOOL bRightInvalid = FALSE;
392 UpdateData(TRUE); // load member variables from screen
393 KillTimer(IDT_CHECKFILES);
396 // Check if we have project file as left side path
397 BOOL bProject = FALSE;
399 SplitFilename(m_strLeft, NULL, NULL, &ext);
400 CString sExt(ext.c_str());
401 if (m_strRight.IsEmpty() && sExt.CompareNoCase(PROJECTFILE_EXT) == 0)
404 // Enable buttons as appropriate
405 if (GetOptionsMgr()->GetBool(OPT_VERIFY_OPEN_PATHS))
407 PATH_EXISTENCE pathsType = GetPairComparability(m_strLeft, m_strRight);
411 m_ctlOk.EnableWindow(TRUE);
412 m_ctlUnpacker.EnableWindow(TRUE);
413 m_ctlSelectUnpacker.EnableWindow(TRUE);
417 m_ctlOk.EnableWindow(pathsType != DOES_NOT_EXIST);
418 m_ctlUnpacker.EnableWindow(pathsType == IS_EXISTING_FILE);
419 m_ctlSelectUnpacker.EnableWindow(pathsType == IS_EXISTING_FILE);
424 if (paths_DoesPathExist(m_strLeft) == DOES_NOT_EXIST)
426 if (paths_DoesPathExist(m_strRight) == DOES_NOT_EXIST)
427 bRightInvalid = TRUE;
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);
439 SetStatus(IDS_OPEN_FILESDIRS);
441 if (pathsType == IS_EXISTING_FILE || bProject)
442 SetUnpackerStatus(0); //Empty field
444 SetUnpackerStatus(IDS_OPEN_UNPACKERDISABLED);
449 * @brief Called when user changes selection in left path's combo box.
451 void COpenDlg::OnSelchangeLeftCombo()
453 int sel = m_ctlLeft.GetCurSel();
456 m_ctlLeft.GetLBText(sel, m_strLeft);
457 m_ctlLeft.SetWindowText(m_strLeft);
460 UpdateButtonStates();
464 * @brief Called when user changes selection in right path's combo box.
466 void COpenDlg::OnSelchangeRightCombo()
468 int sel = m_ctlRight.GetCurSel();
471 m_ctlRight.GetLBText(sel, m_strRight);
472 m_ctlRight.SetWindowText(m_strRight);
475 UpdateButtonStates();
479 * @brief Called every time paths are edited.
481 void COpenDlg::OnEditEvent()
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();
490 * @brief Handle timer events.
491 * Checks if paths are valid and sets control states accordingly.
492 * @param [in] nIDEvent Timer ID that fired.
494 void COpenDlg::OnTimer(UINT_PTR nIDEvent)
496 if (nIDEvent == IDT_CHECKFILES)
497 UpdateButtonStates();
499 CDialog::OnTimer(nIDEvent);
503 * @brief Called when users selects plugin browse button.
505 void COpenDlg::OnSelectUnpacker()
509 m_pathsType = GetPairComparability(m_strLeft, m_strRight);
511 if (m_pathsType != IS_EXISTING_FILE)
514 // let the user select a handler
515 CSelectUnpackerDlg dlg(m_strLeft, m_strRight, this);
516 dlg.SetInitialInfoHandler(&m_infoHandler);
518 if (dlg.DoModal() == IDOK)
520 m_infoHandler = dlg.GetInfoHandler();
522 m_strUnpacker = m_infoHandler.pluginName.c_str();
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.
534 void COpenDlg::SetStatus(UINT msgID)
536 String msg = theApp.LoadString(msgID);
537 SetDlgItemText(IDC_OPEN_STATUS, msg.c_str());
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.
546 void COpenDlg::SetUnpackerStatus(UINT msgID)
548 String msg = theApp.LoadString(msgID);
549 SetDlgItemText(IDC_UNPACKER_EDIT, msg.c_str());
553 * @brief Called when "Select..." button for filters is selected.
555 void COpenDlg::OnSelectFilter()
557 String filterPrefix = theApp.LoadString(IDS_FILTER_PREFIX);
560 const BOOL bUseMask = theApp.m_globalFileFilter.IsUsingMask();
561 GetDlgItemText(IDC_EXT_COMBO, curFilter);
562 curFilter.TrimLeft();
563 curFilter.TrimRight();
565 GetMainFrame()->SelectFilter();
567 String filterNameOrMask = theApp.m_globalFileFilter.GetFilterNameOrMask();
568 if (theApp.m_globalFileFilter.IsUsingMask())
570 // If we had filter chosen and now has mask we can overwrite filter
571 if (!bUseMask || curFilter[0] != '*')
573 SetDlgItemText(IDC_EXT_COMBO, filterNameOrMask.c_str());
578 filterNameOrMask = filterPrefix + filterNameOrMask;
579 SetDlgItemText(IDC_EXT_COMBO, filterNameOrMask.c_str());
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
590 * @param [in] path Path to the project file.
591 * @return TRUE if the project file was successfully loaded, FALSE otherwise.
593 BOOL COpenDlg::LoadProjectFile(const CString &path)
595 String filterPrefix = theApp.LoadString(IDS_FILTER_PREFIX);
598 m_pProjectFile = new ProjectFile;
599 if (m_pProjectFile == NULL)
602 if (!m_pProjectFile->Read(path, &err))
607 LangFormatString2(msg, IDS_ERROR_FILEOPEN, path, err.c_str());
608 AfxMessageBox(msg, MB_ICONSTOP);
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())
619 m_strExt = m_pProjectFile->GetFilter().c_str();
621 m_strExt.TrimRight();
622 if (m_strExt[0] != '*')
623 m_strExt.Insert(0, filterPrefix.c_str());
630 * @brief Removes whitespaces from left and right paths
631 * @note Assumes UpdateData(TRUE) is called before this function.
633 void COpenDlg::TrimPaths()
635 m_strLeft.TrimLeft();
636 m_strLeft.TrimRight();
637 m_strRight.TrimLeft();
638 m_strRight.TrimRight();
642 * @brief Update control states when dialog is activated.
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
649 void COpenDlg::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized)
651 CDialog::OnActivate(nState, pWndOther, bMinimized);
653 if (nState == WA_ACTIVE || nState == WA_CLICKACTIVE)
654 UpdateButtonStates();
658 * @brief Open help from mainframe when user presses F1.
660 void COpenDlg::OnHelp()
662 GetMainFrame()->ShowHelp(OpenDlgHelpLocation);
665 /////////////////////////////////////////////////////////////////////////////
667 // OnDropFiles code from CDropEdit
668 // Copyright 1997 Chris Losinger
670 // shortcut expansion code modified from :
671 // CShortcut, 1996 Rob Warner
675 * @brief Drop paths(s) to the dialog.
676 * One or two paths can be dropped to the dialog. The behaviour is:
678 * - drop to empty path edit box (check left first)
679 * - if both boxes have a path, drop to left path
681 * - overwrite both paths, empty or not
682 * @param [in] dropInfo Dropped data, including paths.
684 void COpenDlg::OnDropFiles(HDROP dropInfo)
686 // Get the number of pathnames that have been dropped
687 UINT wNumFilesDropped = DragQueryFile(dropInfo, 0xFFFFFFFF, NULL, 0);
691 // get all file names. but we'll only need the first one.
692 for (WORD x = 0 ; x < wNumFilesDropped; x++)
694 // Get the number of bytes required by the file's full pathname
695 UINT wPathnameSize = DragQueryFile(dropInfo, x, NULL, 0);
697 // Allocate memory to contain full pathname & zero byte
699 LPTSTR npszFile = (TCHAR *) new TCHAR[wPathnameSize];
701 // If not enough memory, skip this one
702 if (npszFile == NULL)
705 // Copy the pathname into the buffer
706 DragQueryFile(dropInfo, x, npszFile, wPathnameSize);
716 // Free the memory block containing the dropped-file information
717 DragFinish(dropInfo);
719 for (UINT i = 0; i < fileCount; i++)
721 if (paths_IsShortcut((LPCTSTR)files[i]))
723 // if this was a shortcut, we need to expand it to the target path
724 CString expandedFile = ExpandShortcut((LPCTSTR)files[i]).c_str();
726 // if that worked, we should have a real file name
727 if (!expandedFile.IsEmpty())
728 files[i] = expandedFile;
732 // Add dropped paths to the dialog
736 m_strLeft = files[0];
737 m_strRight = files[1];
739 UpdateButtonStates();
741 else if (fileCount == 1)
743 if (m_strLeft.IsEmpty())
744 m_strLeft = files[0];
745 else if (m_strRight.IsEmpty())
746 m_strRight = files[0];
748 m_strLeft = files[0];
750 UpdateButtonStates();