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 COpenView class
31 #include "UnicodeString.h"
34 #include "ProjectFile.h"
36 #include "SelectUnpackerDlg.h"
37 #include "OptionsDef.h"
39 #include "OptionsMgr.h"
40 #include "FileOrFolderSelect.h"
42 #include "Constants.h"
44 #include "DropHandler.h"
45 #include "FileFilterHelper.h"
54 #define BCN_DROPDOWN (BCN_FIRST + 0x0002)
57 // Timer ID and timeout for delaying path validity check
58 const UINT IDT_CHECKFILES = 1;
59 const UINT CHECKFILES_TIMEOUT = 1000; // milliseconds
60 static const TCHAR EMPTY_EXTENSION[] = _T(".*");
62 /** @brief Location for Open-dialog specific help to open. */
63 static TCHAR OpenDlgHelpLocation[] = _T("::/htmlhelp/Open_paths.html");
67 IMPLEMENT_DYNCREATE(COpenView, CFormView)
69 BEGIN_MESSAGE_MAP(COpenView, CFormView)
70 //{{AFX_MSG_MAP(COpenView)
71 ON_BN_CLICKED(IDC_PATH0_BUTTON, OnPathButton<0>)
72 ON_BN_CLICKED(IDC_PATH1_BUTTON, OnPathButton<1>)
73 ON_BN_CLICKED(IDC_PATH2_BUTTON, OnPathButton<2>)
74 ON_BN_CLICKED(IDC_SWAP01_BUTTON, (OnSwapButton<IDC_PATH0_COMBO, IDC_PATH1_COMBO>))
75 ON_BN_CLICKED(IDC_SWAP12_BUTTON, (OnSwapButton<IDC_PATH1_COMBO, IDC_PATH2_COMBO>))
76 ON_BN_CLICKED(IDC_SWAP02_BUTTON, (OnSwapButton<IDC_PATH0_COMBO, IDC_PATH2_COMBO>))
77 ON_CBN_SELCHANGE(IDC_PATH0_COMBO, OnSelchangePathCombo<0>)
78 ON_CBN_SELCHANGE(IDC_PATH1_COMBO, OnSelchangePathCombo<1>)
79 ON_CBN_SELCHANGE(IDC_PATH2_COMBO, OnSelchangePathCombo<2>)
80 ON_CBN_EDITCHANGE(IDC_PATH0_COMBO, OnEditEvent)
81 ON_CBN_EDITCHANGE(IDC_PATH1_COMBO, OnEditEvent)
82 ON_CBN_EDITCHANGE(IDC_PATH2_COMBO, OnEditEvent)
83 ON_BN_CLICKED(IDC_SELECT_UNPACKER, OnSelectUnpacker)
84 ON_CBN_SELENDCANCEL(IDC_PATH0_COMBO, UpdateButtonStates)
85 ON_CBN_SELENDCANCEL(IDC_PATH1_COMBO, UpdateButtonStates)
86 ON_CBN_SELENDCANCEL(IDC_PATH2_COMBO, UpdateButtonStates)
87 ON_NOTIFY_RANGE(CBEN_BEGINEDIT, IDC_PATH0_COMBO, IDC_PATH2_COMBO, OnSetfocusPathCombo)
89 ON_BN_CLICKED(IDC_SELECT_FILTER, OnSelectFilter)
90 ON_BN_CLICKED(IDC_OPTIONS, OnOptions)
91 ON_NOTIFY(BCN_DROPDOWN, IDC_OPTIONS, OnDropDownOptions)
93 ON_COMMAND(ID_LOAD_PROJECT, OnLoadProject)
94 ON_COMMAND(ID_SAVE_PROJECT, OnSaveProject)
95 ON_NOTIFY(BCN_DROPDOWN, ID_SAVE_PROJECT, OnDropDownSaveProject)
96 ON_COMMAND(IDOK, OnOK)
97 ON_COMMAND(IDCANCEL, OnCancel)
98 ON_COMMAND(ID_HELP, OnHelp)
99 ON_COMMAND(ID_EDIT_COPY, OnEditAction<WM_COPY>)
100 ON_COMMAND(ID_EDIT_PASTE, OnEditAction<WM_PASTE>)
101 ON_COMMAND(ID_EDIT_CUT, OnEditAction<WM_CUT>)
102 ON_COMMAND(ID_EDIT_UNDO, OnEditAction<WM_UNDO>)
103 ON_COMMAND(ID_EDIT_SELECT_ALL, (OnEditAction<EM_SETSEL, 0, -1>))
104 ON_MESSAGE(WM_USER + 1, OnUpdateStatus)
107 ON_WM_WINDOWPOSCHANGED()
113 // COpenView construction/destruction
115 COpenView::COpenView()
116 : CFormView(COpenView::IDD)
117 , m_pUpdateButtonStatusThread(NULL)
119 , m_pDropHandler(NULL)
121 , m_bAutoCompleteReady()
125 COpenView::~COpenView()
127 TerminateThreadIfRunning();
130 void COpenView::DoDataExchange(CDataExchange* pDX)
132 CFormView::DoDataExchange(pDX);
133 //{{AFX_DATA_MAP(COpenView)
134 DDX_Control(pDX, IDC_EXT_COMBO, m_ctlExt);
135 DDX_Control(pDX, IDC_PATH0_COMBO, m_ctlPath[0]);
136 DDX_Control(pDX, IDC_PATH1_COMBO, m_ctlPath[1]);
137 DDX_Control(pDX, IDC_PATH2_COMBO, m_ctlPath[2]);
138 DDX_CBStringExact(pDX, IDC_PATH0_COMBO, m_strPath[0]);
139 DDX_CBStringExact(pDX, IDC_PATH1_COMBO, m_strPath[1]);
140 DDX_CBStringExact(pDX, IDC_PATH2_COMBO, m_strPath[2]);
141 DDX_Check(pDX, IDC_PATH0_READONLY, m_bReadOnly[0]);
142 DDX_Check(pDX, IDC_PATH1_READONLY, m_bReadOnly[1]);
143 DDX_Check(pDX, IDC_PATH2_READONLY, m_bReadOnly[2]);
144 DDX_Check(pDX, IDC_RECURS_CHECK, m_bRecurse);
145 DDX_CBStringExact(pDX, IDC_EXT_COMBO, m_strExt);
146 DDX_Text(pDX, IDC_UNPACKER_EDIT, m_strUnpacker);
150 BOOL COpenView::PreCreateWindow(CREATESTRUCT& cs)
152 // TODO: Modify the Window class or styles here by modifying
153 // the CREATESTRUCT cs
154 cs.style &= ~WS_BORDER;
155 cs.dwExStyle &= ~WS_EX_CLIENTEDGE;
156 return CFormView::PreCreateWindow(cs);
159 void COpenView::OnInitialUpdate()
161 m_sizeOrig = GetTotalSize();
163 theApp.TranslateDialog(m_hWnd);
165 if (!m_picture.Load(IDR_LOGO))
168 CFormView::OnInitialUpdate();
171 // set caption to "swap paths" button
173 GetDlgItem(IDC_SWAP01_BUTTON)->GetFont()->GetObject(sizeof(lf), &lf);
174 lf.lfCharSet = SYMBOL_CHARSET;
175 lstrcpy(lf.lfFaceName, _T("Wingdings"));
176 m_fontSwapButton.CreateFontIndirect(&lf);
177 const int ids[] = {IDC_SWAP01_BUTTON, IDC_SWAP12_BUTTON, IDC_SWAP02_BUTTON};
178 for (int i = 0; i < sizeof(ids)/sizeof(ids[0]); ++i)
180 GetDlgItem(ids[i])->SetFont(&m_fontSwapButton);
181 SetDlgItemText(ids[i], _T("\xf4"));
184 m_constraint.InitializeCurrentSize(this);
185 m_constraint.InitializeSpecificSize(this, m_sizeOrig.cx, m_sizeOrig.cy);
186 m_constraint.SetMaxSizePixels(-1, m_sizeOrig.cy);
187 m_constraint.SetScrollScale(this, 1.0, 1.0);
188 m_constraint.SetSizeGrip(prdlg::CMoveConstraint::SG_NONE);
189 // configure how individual controls adjust when dialog resizes
190 m_constraint.ConstrainItem(IDC_PATH0_COMBO, 0, 1, 0, 0); // grows right
191 m_constraint.ConstrainItem(IDC_PATH1_COMBO, 0, 1, 0, 0); // grows right
192 m_constraint.ConstrainItem(IDC_PATH2_COMBO, 0, 1, 0, 0); // grows right
193 m_constraint.ConstrainItem(IDC_EXT_COMBO, 0, 1, 0, 0); // grows right
194 m_constraint.ConstrainItem(IDC_UNPACKER_EDIT, 0, 1, 0, 0); // grows right
195 m_constraint.ConstrainItem(IDC_FILES_DIRS_GROUP, 0, 1, 0, 0); // grows right
196 m_constraint.ConstrainItem(IDC_PATH0_BUTTON, 1, 0, 0, 0); // slides right
197 m_constraint.ConstrainItem(IDC_PATH1_BUTTON, 1, 0, 0, 0); // slides right
198 m_constraint.ConstrainItem(IDC_PATH2_BUTTON, 1, 0, 0, 0); // slides right
199 m_constraint.ConstrainItem(IDC_PATH0_READONLY, 1, 0, 0, 0); // slides right
200 m_constraint.ConstrainItem(IDC_PATH1_READONLY, 1, 0, 0, 0); // slides right
201 m_constraint.ConstrainItem(IDC_PATH2_READONLY, 1, 0, 0, 0); // slides right
202 m_constraint.ConstrainItem(IDC_SWAP01_BUTTON, 1, 0, 0, 0); // slides right
203 m_constraint.ConstrainItem(IDC_SWAP12_BUTTON, 1, 0, 0, 0); // slides right
204 m_constraint.ConstrainItem(IDC_SWAP02_BUTTON, 1, 0, 0, 0); // slides right
205 m_constraint.ConstrainItem(IDC_SELECT_UNPACKER, 1, 0, 0, 0); // slides right
206 m_constraint.ConstrainItem(IDC_OPEN_STATUS, 0, 1, 0, 0); // grows right
207 m_constraint.ConstrainItem(IDC_SELECT_FILTER, 1, 0, 0, 0); // slides right
208 m_constraint.ConstrainItem(IDC_OPTIONS, 1, 0, 0, 0); // slides right
209 m_constraint.ConstrainItem(ID_SAVE_PROJECT, 1, 0, 0, 0); // slides right
210 m_constraint.ConstrainItem(IDOK, 1, 0, 0, 0); // slides right
211 m_constraint.ConstrainItem(IDCANCEL, 1, 0, 0, 0); // slides right
212 m_constraint.ConstrainItem(ID_HELP, 1, 0, 0, 0); // slides right
213 m_constraint.DisallowHeightGrowth();
214 //m_constraint.SubclassWnd(); // install subclassing
216 m_constraint.LoadPosition(_T("ResizeableDialogs"), _T("OpenView"), false); // persist size via registry
217 m_constraint.UpdateSizes();
219 COpenDoc *pDoc = GetDocument();
222 GetWindowText(strTitle);
223 pDoc->SetTitle(strTitle);
225 m_files = pDoc->m_files;
226 m_bRecurse = pDoc->m_bRecurse;
227 m_strExt = pDoc->m_strExt;
228 m_strUnpacker = pDoc->m_strUnpacker;
229 m_infoHandler = pDoc->m_infoHandler;
230 m_dwFlags[0] = pDoc->m_dwFlags[0];
231 m_dwFlags[1] = pDoc->m_dwFlags[1];
232 m_dwFlags[2] = pDoc->m_dwFlags[2];
234 m_ctlPath[0].SetFileControlStates();
235 m_ctlPath[1].SetFileControlStates();
236 m_ctlPath[2].SetFileControlStates(true);
238 for (int file = 0; file < m_files.GetSize(); file++)
240 m_strPath[file] = m_files[file];
241 m_ctlPath[file].SetWindowText(m_files[file].c_str());
242 m_bReadOnly[file] = (m_dwFlags[file] & FFILEOPEN_READONLY) != 0;
245 m_ctlPath[0].AttachSystemImageList();
246 m_ctlPath[1].AttachSystemImageList();
247 m_ctlPath[2].AttachSystemImageList();
248 LoadComboboxStates();
250 BOOL bDoUpdateData = TRUE;
251 for (int index = 0; index < countof(m_strPath); index++)
253 if (!m_strPath[index].empty())
254 bDoUpdateData = FALSE;
256 UpdateData(bDoUpdateData);
258 String filterNameOrMask = theApp.m_pGlobalFileFilter->GetFilterNameOrMask();
259 BOOL bMask = theApp.m_pGlobalFileFilter->IsUsingMask();
263 String filterPrefix = _("[F] ");
264 filterNameOrMask = filterPrefix + filterNameOrMask;
267 int ind = m_ctlExt.FindStringExact(0, filterNameOrMask.c_str());
269 m_ctlExt.SetCurSel(ind);
272 ind = m_ctlExt.InsertString(0, filterNameOrMask.c_str());
274 m_ctlExt.SetCurSel(ind);
276 LogErrorString(_T("Failed to add string to filters combo list!"));
279 if (!GetOptionsMgr()->GetBool(OPT_VERIFY_OPEN_PATHS))
281 EnableDlgItem(IDOK, true);
282 EnableDlgItem(IDC_UNPACKER_EDIT, true);
283 EnableDlgItem(IDC_SELECT_UNPACKER, true);
286 UpdateButtonStates();
288 BOOL bOverwriteRecursive = FALSE;
289 if (m_dwFlags[0] & FFILEOPEN_PROJECT || m_dwFlags[1] & FFILEOPEN_PROJECT)
290 bOverwriteRecursive = TRUE;
291 if (m_dwFlags[0] & FFILEOPEN_CMDLINE || m_dwFlags[1] & FFILEOPEN_CMDLINE)
292 bOverwriteRecursive = TRUE;
293 if (!bOverwriteRecursive)
294 m_bRecurse = GetOptionsMgr()->GetBool(OPT_CMP_INCLUDE_SUBDIRS);
296 m_strUnpacker = m_infoHandler.pluginName;
298 SetStatus(IDS_OPEN_FILESDIRS);
299 SetUnpackerStatus(IDS_OPEN_UNPACKERDISABLED);
301 m_pDropHandler = new DropHandler(std::bind(&COpenView::OnDropFiles, this, std::placeholders::_1));
302 RegisterDragDrop(m_hWnd, m_pDropHandler);
305 void COpenView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
307 m_bRecurse = GetDocument()->m_bRecurse;
311 // COpenView diagnostics
314 COpenDoc* COpenView::GetDocument() const // non-debug version is inline
316 ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(COpenDoc)));
317 return (COpenDoc*)m_pDocument;
322 /////////////////////////////////////////////////////////////////////////////
323 // COpenView message handlers
325 void COpenView::OnPaint()
328 CSize size = m_picture.GetImageSize(&dc);
329 CRect rcImage(0, 0, size.cx * GetSystemMetrics(SM_CXSMICON) / 16, size.cy * GetSystemMetrics(SM_CYSMICON) / 16);
331 m_picture.Render(&dc, rcImage);
333 dc.PatBlt(rcImage.Width(), 0, rc.Width() - rcImage.Width(), rcImage.Height(), PATCOPY);
335 rc.left = rc.right - GetSystemMetrics(SM_CXVSCROLL);
336 rc.top = rc.bottom - GetSystemMetrics(SM_CYHSCROLL);
337 dc.DrawFrameControl(&rc, DFC_SCROLL, DFCS_SCROLLSIZEGRIP);
339 CFormView::OnPaint();
342 void COpenView::OnLButtonDown(UINT nFlags, CPoint point)
345 if (m_rectTracker.Track(this, point, FALSE, GetParentFrame()))
347 CRect rc = m_rectTracker.m_rect;
348 MapWindowPoints(GetParentFrame(), &rc);
350 GetParentFrame()->GetClientRect(&rcFrame);
351 int width = rc.Width() > rcFrame.Width() ? rcFrame.Width() : rc.Width();
352 if (width < m_sizeOrig.cx)
353 width = m_sizeOrig.cx;
354 rc.right = rc.left + width;
355 rc.bottom = rc.top + m_sizeOrig.cy;
356 m_rectTracker.m_rect.right = m_rectTracker.m_rect.left + width;
357 m_rectTracker.m_rect.bottom = m_rectTracker.m_rect.top + m_sizeOrig.cy;
358 SetWindowPos(NULL, rc.left, rc.top, rc.Width(), rc.Height(), SWP_NOZORDER);
359 m_constraint.UpdateSizes();
363 void COpenView::OnWindowPosChanged(WINDOWPOS* lpwndpos)
367 m_rectTracker.m_rect = rc;
368 CFormView::OnWindowPosChanged(lpwndpos);
371 void COpenView::OnDestroy()
374 RevokeDragDrop(m_hWnd);
376 m_constraint.Persist(true, false);
378 CFormView::OnDestroy();
381 BOOL COpenView::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
383 if (pWnd == this && m_rectTracker.SetCursor(this, nHitTest))
386 return CView::OnSetCursor(pWnd, nHitTest, message);
389 void COpenView::OnButton(int index)
395 paths::PATH_EXISTENCE existence = paths::DoesPathExist(m_strPath[index]);
398 case paths::IS_EXISTING_DIR:
399 sfolder = m_strPath[index];
401 case paths::IS_EXISTING_FILE:
402 sfolder = paths::GetPathOnly(m_strPath[index]);
404 case paths::DOES_NOT_EXIST:
405 // Do nothing, empty foldername will be passed to dialog
408 _RPTF0(_CRT_ERROR, "Invalid return value from paths::DoesPathExist()");
412 if (SelectFileOrFolder(GetSafeHwnd(), s, sfolder.c_str()))
414 m_strPath[index] = s;
415 m_strBrowsePath[index] = s;
417 UpdateButtonStates();
422 * @brief Called when "Browse..." button is selected for N path.
425 void COpenView::OnPathButton()
430 template<int id1, int id2>
431 void COpenView::OnSwapButton()
434 GetDlgItemText(id1, s1);
435 GetDlgItemText(id2, s2);
437 SetDlgItemText(id1, s1);
438 SetDlgItemText(id2, s2);
442 * @brief Called when dialog is closed with "OK".
444 * Checks that paths are valid and sets filters.
446 void COpenView::OnOK()
448 int pathsType; // enum from paths::PATH_EXISTENCE in paths.h
449 const String filterPrefix = _("[F] ");
456 for (index = 0; index < countof(m_strPath); index++)
458 if (index == 2 && m_strPath[index].empty())
460 m_files.SetSize(nFiles + 1);
461 m_files[nFiles] = m_strPath[index];
462 m_dwFlags[nFiles] &= ~FFILEOPEN_READONLY;
463 m_dwFlags[nFiles] |= m_bReadOnly[index] ? FFILEOPEN_READONLY : 0;
466 // If left path is a project-file, load it
468 paths::SplitFilename(m_strPath[0], NULL, NULL, &ext);
469 if (m_strPath[1].empty() && strutils::compare_nocase(ext, ProjectFile::PROJECTFILE_EXT) == 0)
470 LoadProjectFile(m_strPath[0]);
472 pathsType = paths::GetPairComparability(m_files, IsArchiveFile);
474 if (pathsType == paths::DOES_NOT_EXIST)
476 LangMessageBox(IDS_ERROR_INCOMPARABLE, MB_ICONSTOP);
480 for (index = 0; index < nFiles; index++)
482 // If user has edited path by hand, expand environment variables
483 bool bExpand = false;
484 if (strutils::compare_nocase(m_strBrowsePath[index], m_files[index]) != 0)
487 if (!paths::IsURLorCLSID(m_files[index]))
489 m_files[index] = paths::GetLongPath(m_files[index], bExpand);
491 // Add trailing '\' for directories if its missing
492 if (paths::DoesPathExist(m_files[index]) == paths::IS_EXISTING_DIR)
493 m_files[index] = paths::AddTrailingSlash(m_files[index]);
494 m_strPath[index] = m_files[index];
499 KillTimer(IDT_CHECKFILES);
501 String filter(strutils::trim_ws(m_strExt));
503 // If prefix found from start..
504 if (filter.substr(0, filterPrefix.length()) == filterPrefix)
506 // Remove prefix + space
507 filter.erase(0, filterPrefix.length());
508 if (!theApp.m_pGlobalFileFilter->SetFilter(filter))
510 // If filtername is not found use default *.* mask
511 theApp.m_pGlobalFileFilter->SetFilter(_T("*.*"));
514 GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, filter);
518 BOOL bFilterSet = theApp.m_pGlobalFileFilter->SetFilter(filter);
520 m_strExt = theApp.m_pGlobalFileFilter->GetFilterNameOrMask();
521 GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, filter);
524 SaveComboboxStates();
525 GetOptionsMgr()->SaveOption(OPT_CMP_INCLUDE_SUBDIRS, m_bRecurse);
526 LoadComboboxStates();
528 m_constraint.Persist(true, false);
530 COpenDoc *pDoc = GetDocument();
531 pDoc->m_files = m_files;
532 pDoc->m_bRecurse = m_bRecurse;
533 pDoc->m_strExt = m_strExt;
534 pDoc->m_strUnpacker = m_strUnpacker;
535 pDoc->m_infoHandler = m_infoHandler;
536 pDoc->m_dwFlags[0] = m_dwFlags[0];
537 pDoc->m_dwFlags[1] = m_dwFlags[1];
538 pDoc->m_dwFlags[2] = m_dwFlags[2];
540 if (GetOptionsMgr()->GetBool(OPT_CLOSE_WITH_OK))
541 GetParentFrame()->PostMessage(WM_CLOSE);
543 PathContext tmpPathContext(pDoc->m_files);
544 PackingInfo tmpPackingInfo(pDoc->m_infoHandler);
545 GetMainFrame()->DoFileOpen(
546 &tmpPathContext, std::array<DWORD, 3>(pDoc->m_dwFlags).data(),
547 NULL, _T(""), !!pDoc->m_bRecurse, NULL, _T(""), &tmpPackingInfo);
551 * @brief Called when dialog is closed via Cancel.
553 * Open-dialog is closed when `Cancel` button is selected or the
554 * `Esc` key is pressed. Save combobox states, since user may have
555 * removed items from them (with `shift-del`) and doesn't want them
557 * This is *not* called when the program is terminated, even if the
558 * dialog is visible at the time.
560 void COpenView::OnCancel()
562 SaveComboboxStates();
563 AfxGetMainWnd()->PostMessage(WM_COMMAND, ID_FILE_CLOSE);
567 * @brief Callled when Open-button for project file is selected.
569 void COpenView::OnLoadProject()
571 String fileName = AskProjectFileName(true);
572 if (fileName.empty())
576 if (!theApp.LoadProjectFile(fileName, project))
580 project.GetPaths(paths, m_bRecurse);
581 project.GetLeftReadOnly();
582 if (paths.size() < 3)
584 m_strPath[0] = paths[0];
585 m_strPath[1] = paths[1];
586 m_strPath[2] = _T("");
587 m_bReadOnly[0] = project.GetLeftReadOnly();
588 m_bReadOnly[1] = project.GetRightReadOnly();
589 m_bReadOnly[2] = false;
593 m_strPath[0] = paths[0];
594 m_strPath[1] = paths[1];
595 m_strPath[2] = paths[2];
596 m_bReadOnly[0] = project.GetLeftReadOnly();
597 m_bReadOnly[1] = project.GetMiddleReadOnly();
598 m_bReadOnly[2] = project.GetRightReadOnly();
600 m_strExt = project.GetFilter();
603 LangMessageBox(IDS_PROJFILE_LOAD_SUCCESS, MB_ICONINFORMATION);
607 * @brief Called when Save-button for project file is selected.
609 void COpenView::OnSaveProject()
613 String fileName = AskProjectFileName(false);
614 if (fileName.empty())
619 if (!m_strPath[0].empty())
620 project.SetLeft(m_strPath[0], &m_bReadOnly[0]);
621 if (m_strPath[2].empty())
623 if (!m_strPath[1].empty())
624 project.SetRight(m_strPath[1], &m_bReadOnly[1]);
628 if (!m_strPath[1].empty())
629 project.SetMiddle(m_strPath[1], &m_bReadOnly[1]);
630 if (!m_strPath[2].empty())
631 project.SetRight(m_strPath[2], &m_bReadOnly[2]);
633 if (!m_strExt.empty())
635 // Remove possbile prefix from the filter name
636 String prefix = _("[F] ");
637 String strExt = m_strExt;
638 size_t ind = strExt.find(prefix, 0);
641 strExt.erase(0, prefix.length());
643 strExt = strutils::trim_ws_begin(strExt);
644 project.SetFilter(strExt);
646 project.SetSubfolders(m_bRecurse);
648 if (!theApp.SaveProjectFile(fileName, project))
651 LangMessageBox(IDS_PROJFILE_SAVE_SUCCESS, MB_ICONINFORMATION);
654 void COpenView::OnDropDownSaveProject(NMHDR *pNMHDR, LRESULT *pResult)
656 CRect rcButton, rcView;
657 GetDlgItem(ID_SAVE_PROJECT)->GetWindowRect(&rcButton);
659 VERIFY(menu.LoadMenu(IDR_POPUP_PROJECT));
660 theApp.TranslateMenu(menu.m_hMenu);
661 CMenu* pPopup = menu.GetSubMenu(0);
664 pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,
665 rcButton.left, rcButton.bottom, GetMainFrame());
671 * @brief Allow user to select a file to open/save.
673 String COpenView::AskProjectFileName(bool bOpen)
675 // get the default projects path
676 String strProjectFileName;
677 String strProjectPath = GetOptionsMgr()->GetString(OPT_PROJECTS_PATH);
679 if (!::SelectFile(GetSafeHwnd(), strProjectFileName, bOpen, strProjectPath.c_str(),
680 _T(""), _("WinMerge Project Files (*.WinMerge)|*.WinMerge||"), _T(".WinMerge")))
683 if (strProjectFileName.empty())
686 // get the path part from the filename
687 strProjectPath = paths::GetParentPath(strProjectFileName);
688 // store this as the new project path
689 GetOptionsMgr()->SaveOption(OPT_PROJECTS_PATH, strProjectPath);
690 return strProjectFileName;
694 * @brief Load File- and filter-combobox states.
696 void COpenView::LoadComboboxStates()
698 m_ctlPath[0].LoadState(_T("Files\\Left"));
699 m_ctlPath[1].LoadState(_T("Files\\Right"));
700 m_ctlPath[2].LoadState(_T("Files\\Option"));
701 m_ctlExt.LoadState(_T("Files\\Ext"));
705 * @brief Save File- and filter-combobox states.
707 void COpenView::SaveComboboxStates()
709 m_ctlPath[0].SaveState(_T("Files\\Left"));
710 m_ctlPath[1].SaveState(_T("Files\\Right"));
711 m_ctlPath[2].SaveState(_T("Files\\Option"));
712 m_ctlExt.SaveState(_T("Files\\Ext"));
715 struct UpdateButtonStatesThreadParams
721 static UINT UpdateButtonStatesThread(LPVOID lpParam)
727 CAssureScriptsForThread scriptsForRescan;
729 while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
733 if (msg.message != WM_USER + 2)
736 BOOL bButtonEnabled = TRUE;
737 BOOL bInvalid[3] = {FALSE, FALSE, FALSE};
738 int iStatusMsgId = 0;
739 int iUnpackerStatusMsgId = 0;
741 UpdateButtonStatesThreadParams *pParams = reinterpret_cast<UpdateButtonStatesThreadParams *>(msg.wParam);
742 PathContext paths = pParams->m_paths;
743 HWND hWnd = pParams->m_hWnd;
746 // Check if we have project file as left side path
747 BOOL bProject = FALSE;
749 paths::SplitFilename(paths[0], NULL, NULL, &ext);
750 if (paths[1].empty() && strutils::compare_nocase(ext, ProjectFile::PROJECTFILE_EXT) == 0)
755 if (paths::DoesPathExist(paths[0], IsArchiveFile) == paths::DOES_NOT_EXIST)
757 if (paths::DoesPathExist(paths[1], IsArchiveFile) == paths::DOES_NOT_EXIST)
759 if (paths.GetSize() > 2 && paths::DoesPathExist(paths[2], IsArchiveFile) == paths::DOES_NOT_EXIST)
763 // Enable buttons as appropriate
764 if (GetOptionsMgr()->GetBool(OPT_VERIFY_OPEN_PATHS))
766 paths::PATH_EXISTENCE pathsType = paths::DOES_NOT_EXIST;
768 if (paths.GetSize() <= 2)
770 if (bInvalid[0] && bInvalid[1])
771 iStatusMsgId = IDS_OPEN_BOTHINVALID;
772 else if (bInvalid[0])
773 iStatusMsgId = IDS_OPEN_LEFTINVALID;
774 else if (bInvalid[1])
775 iStatusMsgId = IDS_OPEN_RIGHTINVALID2;
776 else if (!bInvalid[0] && !bInvalid[1])
778 pathsType = paths::GetPairComparability(paths, IsArchiveFile);
779 if (pathsType == paths::DOES_NOT_EXIST)
780 iStatusMsgId = IDS_OPEN_MISMATCH;
782 iStatusMsgId = IDS_OPEN_FILESDIRS;
787 if (bInvalid[0] && bInvalid[1] && bInvalid[2])
788 iStatusMsgId = IDS_OPEN_ALLINVALID;
789 else if (!bInvalid[0] && bInvalid[1] && bInvalid[2])
790 iStatusMsgId = IDS_OPEN_MIDDLERIGHTINVALID;
791 else if (bInvalid[0] && !bInvalid[1] && bInvalid[2])
792 iStatusMsgId = IDS_OPEN_LEFTRIGHTINVALID;
793 else if (!bInvalid[0] && !bInvalid[1] && bInvalid[2])
794 iStatusMsgId = IDS_OPEN_RIGHTINVALID3;
795 else if (bInvalid[0] && bInvalid[1] && !bInvalid[2])
796 iStatusMsgId = IDS_OPEN_LEFTMIDDLEINVALID;
797 else if (!bInvalid[0] && bInvalid[1] && !bInvalid[2])
798 iStatusMsgId = IDS_OPEN_MIDDLEINVALID;
799 else if (bInvalid[0] && !bInvalid[1] && !bInvalid[2])
800 iStatusMsgId = IDS_OPEN_LEFTINVALID;
801 else if (!bInvalid[0] && !bInvalid[1] && !bInvalid[2])
803 pathsType = paths::GetPairComparability(paths, IsArchiveFile);
804 if (pathsType == paths::DOES_NOT_EXIST)
805 iStatusMsgId = IDS_OPEN_MISMATCH;
807 iStatusMsgId = IDS_OPEN_FILESDIRS;
810 if (pathsType == paths::IS_EXISTING_FILE || bProject)
811 iUnpackerStatusMsgId = 0; //Empty field
813 iUnpackerStatusMsgId = IDS_OPEN_UNPACKERDISABLED;
816 bButtonEnabled = TRUE;
818 bButtonEnabled = (pathsType != paths::DOES_NOT_EXIST);
821 PostMessage(hWnd, WM_USER + 1, bButtonEnabled, MAKELPARAM(iStatusMsgId, iUnpackerStatusMsgId));
830 * @brief Enable/disable components based on validity of paths.
832 void COpenView::UpdateButtonStates()
834 UpdateData(TRUE); // load member variables from screen
835 KillTimer(IDT_CHECKFILES);
838 if (!m_pUpdateButtonStatusThread)
840 m_pUpdateButtonStatusThread = AfxBeginThread(
841 UpdateButtonStatesThread, NULL, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
842 m_pUpdateButtonStatusThread->m_bAutoDelete = FALSE;
843 m_pUpdateButtonStatusThread->ResumeThread();
844 while (PostThreadMessage(m_pUpdateButtonStatusThread->m_nThreadID, WM_NULL, 0, 0) == FALSE)
848 UpdateButtonStatesThreadParams *pParams = new UpdateButtonStatesThreadParams;
849 pParams->m_hWnd = this->m_hWnd;
850 if (m_strPath[2].empty())
851 pParams->m_paths = PathContext(m_strPath[0], m_strPath[1]);
853 pParams->m_paths = PathContext(m_strPath[0], m_strPath[1], m_strPath[2]);
855 PostThreadMessage(m_pUpdateButtonStatusThread->m_nThreadID, WM_USER + 2, (WPARAM)pParams, 0);
858 void COpenView::TerminateThreadIfRunning()
860 if (!m_pUpdateButtonStatusThread)
863 PostThreadMessage(m_pUpdateButtonStatusThread->m_nThreadID, WM_QUIT, 0, 0);
864 DWORD dwResult = WaitForSingleObject(m_pUpdateButtonStatusThread->m_hThread, 100);
865 if (dwResult != WAIT_OBJECT_0)
867 m_pUpdateButtonStatusThread->SuspendThread();
868 TerminateThread(m_pUpdateButtonStatusThread->m_hThread, 0);
870 delete m_pUpdateButtonStatusThread;
871 m_pUpdateButtonStatusThread = NULL;
875 * @brief Called when user changes selection in left/middle/right path's combo box.
877 void COpenView::OnSelchangeCombo(int index)
879 int sel = m_ctlPath[index].GetCurSel();
883 m_ctlPath[index].GetLBText(sel, cstrPath);
884 m_strPath[index] = cstrPath;
885 m_ctlPath[index].SetWindowText(cstrPath);
888 UpdateButtonStates();
892 void COpenView::OnSelchangePathCombo()
897 void COpenView::OnSetfocusPathCombo(UINT id, NMHDR *pNMHDR, LRESULT *pResult)
899 if (!m_bAutoCompleteReady[id - IDC_PATH0_COMBO])
901 int nSource = GetOptionsMgr()->GetInt(OPT_AUTO_COMPLETE_SOURCE);
903 m_ctlPath[id - IDC_PATH0_COMBO].SetAutoComplete(nSource);
904 m_bAutoCompleteReady[id - IDC_PATH0_COMBO] = true;
910 * @brief Called every time paths are edited.
912 void COpenView::OnEditEvent()
914 // (Re)start timer to path validity check delay
915 // If timer starting fails, update buttonstates immediately
916 if (!SetTimer(IDT_CHECKFILES, CHECKFILES_TIMEOUT, NULL))
917 UpdateButtonStates();
921 * @brief Handle timer events.
922 * Checks if paths are valid and sets control states accordingly.
923 * @param [in] nIDEvent Timer ID that fired.
925 void COpenView::OnTimer(UINT_PTR nIDEvent)
927 if (nIDEvent == IDT_CHECKFILES)
928 UpdateButtonStates();
930 CFormView::OnTimer(nIDEvent);
934 * @brief Called when users selects plugin browse button.
936 void COpenView::OnSelectUnpacker()
938 paths::PATH_EXISTENCE pathsType;
943 for (index = 0; index < countof(m_strPath); index++)
945 if (index == 2 && m_strPath[index].empty())
947 m_files.SetSize(nFiles + 1);
948 m_files[nFiles] = m_strPath[index];
951 pathsType = paths::GetPairComparability(m_files);
953 if (pathsType != paths::IS_EXISTING_FILE)
956 // let the user select a handler
957 CSelectUnpackerDlg dlg(m_files[0], this);
958 PackingInfo infoUnpacker(PLUGIN_AUTO);
959 dlg.SetInitialInfoHandler(&infoUnpacker);
961 if (dlg.DoModal() == IDOK)
963 m_infoHandler = dlg.GetInfoHandler();
965 m_strUnpacker = m_infoHandler.pluginName;
971 LRESULT COpenView::OnUpdateStatus(WPARAM wParam, LPARAM lParam)
973 bool bEnabledButtons = wParam != 0;
975 EnableDlgItem(IDOK, bEnabledButtons);
976 EnableDlgItem(IDC_UNPACKER_EDIT, bEnabledButtons);
977 EnableDlgItem(IDC_SELECT_UNPACKER, bEnabledButtons);
979 SetStatus(HIWORD(lParam));
980 SetStatus(LOWORD(lParam));
986 * @brief Sets the path status text.
987 * The open dialog shows a status text of selected paths. This function
988 * is used to set that status text.
989 * @param [in] msgID Resource ID of status text to set.
991 void COpenView::SetStatus(UINT msgID)
993 String msg = theApp.LoadString(msgID);
994 SetDlgItemText(IDC_OPEN_STATUS, msg);
998 * @brief Set the plugin edit box text.
999 * Plugin edit box is at the same time a plugin status view. This function
1000 * sets the status text.
1001 * @param [in] msgID Resource ID of status text to set.
1003 void COpenView::SetUnpackerStatus(UINT msgID)
1005 String msg = theApp.LoadString(msgID);
1006 SetDlgItemText(IDC_UNPACKER_EDIT, msg);
1010 * @brief Called when "Select..." button for filters is selected.
1012 void COpenView::OnSelectFilter()
1014 String filterPrefix = _("[F] ");
1017 const BOOL bUseMask = theApp.m_pGlobalFileFilter->IsUsingMask();
1018 GetDlgItemText(IDC_EXT_COMBO, curFilter);
1019 curFilter = strutils::trim_ws(curFilter);
1021 GetMainFrame()->SelectFilter();
1023 String filterNameOrMask = theApp.m_pGlobalFileFilter->GetFilterNameOrMask();
1024 if (theApp.m_pGlobalFileFilter->IsUsingMask())
1026 // If we had filter chosen and now has mask we can overwrite filter
1027 if (!bUseMask || curFilter[0] != '*')
1029 SetDlgItemText(IDC_EXT_COMBO, filterNameOrMask);
1034 filterNameOrMask = filterPrefix + filterNameOrMask;
1035 SetDlgItemText(IDC_EXT_COMBO, filterNameOrMask);
1039 void COpenView::OnOptions()
1041 GetMainFrame()->PostMessage(WM_COMMAND, ID_OPTIONS);
1044 void COpenView::OnDropDownOptions(NMHDR *pNMHDR, LRESULT *pResult)
1046 NMTOOLBAR dropDown = { 0 };
1047 dropDown.hdr.code = TBN_DROPDOWN;
1048 dropDown.hdr.hwndFrom = GetMainFrame()->GetDescendantWindow(AFX_IDW_TOOLBAR)->GetSafeHwnd();
1049 dropDown.hdr.idFrom = AFX_IDW_TOOLBAR;
1050 GetDlgItem(IDC_OPTIONS)->GetWindowRect(&dropDown.rcButton);
1051 GetMainFrame()->ScreenToClient(&dropDown.rcButton);
1052 GetMainFrame()->SendMessage(WM_NOTIFY, dropDown.hdr.idFrom, reinterpret_cast<LPARAM>(&dropDown));
1057 * @brief Read paths and filter from project file.
1058 * Reads the given project file. After the file is read, found paths and
1059 * filter is updated to dialog GUI. Other possible settings found in the
1060 * project file are kept in memory and used later when loading paths
1062 * @param [in] path Path to the project file.
1063 * @return TRUE if the project file was successfully loaded, FALSE otherwise.
1065 BOOL COpenView::LoadProjectFile(const String &path)
1067 String filterPrefix = _("[F] ");
1070 if (!theApp.LoadProjectFile(path, prj))
1074 prj.GetPaths(m_files, recurse);
1075 m_bRecurse = recurse;
1076 m_dwFlags[0] &= ~FFILEOPEN_READONLY;
1077 m_dwFlags[0] |= prj.GetLeftReadOnly() ? FFILEOPEN_READONLY : 0;
1078 if (m_files.GetSize() < 3)
1080 m_dwFlags[1] &= ~FFILEOPEN_READONLY;
1081 m_dwFlags[1] |= prj.GetRightReadOnly() ? FFILEOPEN_READONLY : 0;
1085 m_dwFlags[1] &= ~FFILEOPEN_READONLY;
1086 m_dwFlags[1] |= prj.GetMiddleReadOnly() ? FFILEOPEN_READONLY : 0;
1087 m_dwFlags[2] &= ~FFILEOPEN_READONLY;
1088 m_dwFlags[2] |= prj.GetRightReadOnly() ? FFILEOPEN_READONLY : 0;
1090 if (prj.HasFilter())
1092 m_strExt = strutils::trim_ws(prj.GetFilter());
1093 if (m_strExt[0] != '*')
1094 m_strExt.insert(0, filterPrefix);
1100 * @brief Removes whitespaces from left and right paths
1101 * @note Assumes UpdateData(TRUE) is called before this function.
1103 void COpenView::TrimPaths()
1105 for (int index = 0; index < countof(m_strPath); index++)
1106 m_strPath[index] = strutils::trim_ws(m_strPath[index]);
1110 * @brief Update control states when dialog is activated.
1112 * Update control states when user re-activates dialog. User might have
1113 * switched for other program to e.g. update files/folders and then
1114 * swiches back to WinMerge. Its nice to see WinMerge detects updated
1117 void COpenView::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized)
1119 CFormView::OnActivate(nState, pWndOther, bMinimized);
1121 if (nState == WA_ACTIVE || nState == WA_CLICKACTIVE)
1122 UpdateButtonStates();
1125 template <int MSG, int WPARAM, int LPARAM>
1126 void COpenView::OnEditAction()
1128 CWnd *pCtl = GetFocus();
1130 pCtl->PostMessage(MSG, WPARAM, LPARAM);
1134 * @brief Open help from mainframe when user presses F1.
1136 void COpenView::OnHelp()
1138 theApp.ShowHelp(OpenDlgHelpLocation);
1141 /////////////////////////////////////////////////////////////////////////////
1143 // OnDropFiles code from CDropEdit
1144 // Copyright 1997 Chris Losinger
1146 // shortcut expansion code modified from :
1147 // CShortcut, 1996 Rob Warner
1151 * @brief Drop paths(s) to the dialog.
1152 * One or two paths can be dropped to the dialog. The behaviour is:
1154 * - drop to empty path edit box (check left first)
1155 * - if both boxes have a path, drop to left path
1157 * - overwrite both paths, empty or not
1158 * @param [in] dropInfo Dropped data, including paths.
1160 void COpenView::OnDropFiles(const std::vector<String>& files)
1162 const size_t fileCount = files.size();
1164 // Add dropped paths to the dialog
1168 m_strPath[0] = files[0];
1169 m_strPath[1] = files[1];
1170 m_strPath[2] = files[2];
1172 UpdateButtonStates();
1174 else if (fileCount == 2)
1176 m_strPath[0] = files[0];
1177 m_strPath[1] = files[1];
1179 UpdateButtonStates();
1181 else if (fileCount == 1)
1183 if (m_strPath[0].empty())
1184 m_strPath[0] = files[0];
1185 else if (m_strPath[1].empty())
1186 m_strPath[1] = files[0];
1187 else if (m_strPath[2].empty())
1188 m_strPath[2] = files[0];
1190 m_strPath[0] = files[0];
1192 UpdateButtonStates();
1196 BOOL COpenView::PreTranslateMessage(MSG* pMsg)
1198 if (pMsg->message == WM_SYSKEYDOWN)
1200 if (::GetAsyncKeyState(VK_MENU))
1203 switch (pMsg->wParam)
1205 case '1': id = IDC_PATH0_COMBO; goto LABEL_NUM_KEY;
1206 case '2': id = IDC_PATH1_COMBO; goto LABEL_NUM_KEY;
1207 case '3': id = IDC_PATH2_COMBO;
1209 SetDlgItemFocus(id);
1212 case 'S': id = IDC_SELECT_UNPACKER;
1213 PostMessage(WM_COMMAND, id, 0);
1218 return CFormView::PreTranslateMessage(pMsg);