1 /////////////////////////////////////////////////////////////////////////////
3 // This program is free software; you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation; either version 2 of the License, or (at
6 // your option) any later version.
8 // This program is distributed in the hope that it will be useful, but
9 // WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
13 // You should have received a copy of the GNU General Public License
14 // along with this program; if not, write to the Free Software
15 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
16 /////////////////////////////////////////////////////////////////////////////
18 * @file FileFiltersDlg.cpp
20 * @brief Implementation of FileFilters -dialog
24 #include "FileFiltersDlg.h"
26 #include "UnicodeString.h"
28 #include "FileFilterMgr.h"
29 #include "FileFilterHelper.h"
31 #include "SharedFilterDlg.h"
32 #include "TestFilterDlg.h"
33 #include "FileOrFolderSelect.h"
40 static char THIS_FILE[] = __FILE__;
43 /** @brief Template file used when creating new filefilter. */
44 static const TCHAR FILE_FILTER_TEMPLATE[] = _T("FileFilter.tmpl");
46 /** @brief Location for filters specific help to open. */
47 static const TCHAR FilterHelpLocation[] = _T("::/htmlhelp/Filters.html");
49 /////////////////////////////////////////////////////////////////////////////
51 IMPLEMENT_DYNCREATE(FileFiltersDlg, CPropertyPage)
56 FileFiltersDlg::FileFiltersDlg() : CPropertyPage(FileFiltersDlg::IDD)
58 m_strCaption = theApp.LoadDialogCaption(m_lpszTemplateName).c_str();
59 m_psp.pszTitle = m_strCaption;
60 m_psp.dwFlags |= PSP_USETITLE;
61 m_psp.hIcon = AfxGetApp()->LoadIcon(IDI_FILEFILTER);
62 m_psp.dwFlags |= PSP_USEHICON;
65 void FileFiltersDlg::DoDataExchange(CDataExchange* pDX)
67 CDialog::DoDataExchange(pDX);
68 //{{AFX_DATA_MAP(FileFiltersDlg)
69 DDX_Control(pDX, IDC_FILTERFILE_LIST, m_listFilters);
74 BEGIN_MESSAGE_MAP(FileFiltersDlg, CDialog)
75 //{{AFX_MSG_MAP(FileFiltersDlg)
76 ON_BN_CLICKED(IDC_FILTERFILE_EDITBTN, OnFiltersEditbtn)
77 ON_NOTIFY(NM_DBLCLK, IDC_FILTERFILE_LIST, OnDblclkFiltersList)
79 ON_BN_CLICKED(IDC_FILTERFILE_TEST_BTN, OnBnClickedFilterfileTestButton)
80 ON_BN_CLICKED(IDC_FILTERFILE_NEWBTN, OnBnClickedFilterfileNewbutton)
81 ON_BN_CLICKED(IDC_FILTERFILE_DELETEBTN, OnBnClickedFilterfileDelete)
82 ON_COMMAND(ID_HELP, OnHelp)
84 ON_NOTIFY(LVN_ITEMCHANGED, IDC_FILTERFILE_LIST, OnLvnItemchangedFilterfileList)
85 ON_NOTIFY(LVN_GETINFOTIP, IDC_FILTERFILE_LIST, OnInfoTip)
86 ON_BN_CLICKED(IDC_FILTERFILE_INSTALL, OnBnClickedFilterfileInstall)
89 /////////////////////////////////////////////////////////////////////////////
90 // CFiltersDlg message handlers
93 * @brief Set array of filters.
94 * @param [in] fileFilters Array of filters to show in the dialog.
95 * @note Call this before actually showing the dialog.
97 void FileFiltersDlg::SetFilterArray(vector<FileFilterInfo> * fileFilters)
99 m_Filters = fileFilters;
103 * @brief Returns path (cont. filename) of selected filter
104 * @return Full path to selected filter file.
106 String FileFiltersDlg::GetSelected()
108 return m_sFileFilterPath;
112 * @brief Set path of selected filter.
113 * @param [in] Path for selected filter.
114 * @note Call this before actually showing the dialog.
116 void FileFiltersDlg::SetSelected(const String & selected)
118 m_sFileFilterPath = selected;
122 * @brief Initialise listcontrol containing filters.
124 void FileFiltersDlg::InitList()
126 // Show selection across entire row.
127 // Also enable infotips.
128 m_listFilters.SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP);
130 String title = _("Name");
131 m_listFilters.InsertColumn(0, title.c_str(), LVCFMT_LEFT, 150);
132 title = _("Description");
133 m_listFilters.InsertColumn(1, title.c_str(), LVCFMT_LEFT, 350);
134 title = _("Location");
135 m_listFilters.InsertColumn(2, title.c_str(), LVCFMT_LEFT, 350);
138 m_listFilters.InsertItem(1, title.c_str());
139 m_listFilters.SetItemText(0, 1, title.c_str());
140 m_listFilters.SetItemText(0, 2, title.c_str());
142 const int count = (int) m_Filters->size();
144 for (int i = 0; i < count; i++)
151 * @brief Select filter by index in the listview.
152 * @param [in] index Index of filter to select.
154 void FileFiltersDlg::SelectFilterByIndex(int index)
156 m_listFilters.SetItemState(index, LVIS_SELECTED, LVIS_SELECTED);
157 bool bPartialOk = false;
158 m_listFilters.EnsureVisible(index, bPartialOk);
162 * @brief Called before dialog is shown.
163 * @return Always TRUE.
165 BOOL FileFiltersDlg::OnInitDialog()
167 theApp.TranslateDialog(m_hWnd);
168 CDialog::OnInitDialog();
172 if (m_sFileFilterPath.empty())
174 SelectFilterByIndex(0);
178 int count = m_listFilters.GetItemCount();
179 for (int i = 0; i < count; i++)
181 String desc = m_listFilters.GetItemText(i, 2);
182 if (string_compare_nocase(desc, m_sFileFilterPath) == 0)
184 SelectFilterByIndex(i);
188 return TRUE; // return TRUE unless you set the focus to a control
189 // EXCEPTION: OCX Property Pages should return FALSE
193 * @brief Add filter from filter-list index to dialog.
194 * @param [in] filterIndex Index of filter to add.
196 void FileFiltersDlg::AddToGrid(int filterIndex)
198 const FileFilterInfo & filterinfo = m_Filters->at(filterIndex);
199 const int item = filterIndex + 1;
201 m_listFilters.InsertItem(item, filterinfo.name.c_str());
202 m_listFilters.SetItemText(item, 1, filterinfo.description.c_str());
203 m_listFilters.SetItemText(item, 2, filterinfo.fullpath.c_str());
207 * @brief Called when dialog is closed with "OK" button.
209 void FileFiltersDlg::OnOK()
211 int sel = m_listFilters.GetNextItem(-1, LVNI_SELECTED);
212 m_sFileFilterPath = m_listFilters.GetItemText(sel, 2);
218 * @brief Open selected filter for editing.
220 * This opens selected file filter file for user to edit. Other WinMerge UI is
221 * not (anymore) blocked during editing. We let user continue working with
222 * WinMerge while editing filter(s). Before opening this dialog and before
223 * doing directory compare we re-load changed filter files from disk. So we
224 * always compare with latest saved filters.
225 * @sa CMainFrame::OnToolsFilters()
226 * @sa CDirDoc::Rescan()
227 * @sa FileFilterHelper::ReloadUpdatedFilters()
229 void FileFiltersDlg::OnFiltersEditbtn()
233 sel = m_listFilters.GetNextItem(sel, LVNI_SELECTED);
235 // Can't edit first "None"
238 String path = m_listFilters.GetItemText(sel, 2);
239 EditFileFilter(path);
244 * @brief Edit file filter in external editor.
245 * @param [in] path Full path to file filter to edit.
247 void FileFiltersDlg::EditFileFilter(const String& path)
249 theApp.OpenFileToExternalEditor(path);
253 * @brief Edit selected filter when its double-clicked.
254 * @param [in] pNMHDR List control item data.
255 * @param [out] pResult Result of the action is returned in here.
257 void FileFiltersDlg::OnDblclkFiltersList(NMHDR* pNMHDR, LRESULT* pResult)
259 UNREFERENCED_PARAMETER(pNMHDR);
266 * @brief Shortcut to enable or disable a control.
267 * @param [in] parent Pointer to dialog.
268 * @param [in] item Control's resourceID in dialog.
269 * @param [in] enable TRUE if item is enabled, FALSE if disabled.
271 static void EnableDlgItem(CWnd * parent, int item, bool enable)
273 parent->GetDlgItem(item)->EnableWindow(!!enable);
277 * @brief Is item in list the <None> item?
278 * @param [in] item Item to test.
279 * @return true if item is <None> item.
281 bool FileFiltersDlg::IsFilterItemNone(int item) const
283 String txtNone = _("<None>");
284 String txt = m_listFilters.GetItemText(item, 0);
286 return (string_compare_nocase(txt, txtNone) == 0);
290 * @brief Called when item state is changed.
292 * Disable Edit-button when "None" filter is selected.
293 * @param [in] pNMHDR Listview item data.
294 * @param [out] pResult Result of the action is returned in here.
296 void FileFiltersDlg::OnLvnItemchangedFilterfileList(NMHDR *pNMHDR, LRESULT *pResult)
298 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
300 // If item got selected
301 if (pNMLV->uNewState & LVIS_SELECTED)
303 String txtNone = _("<None>");
304 String txt = m_listFilters.GetItemText(pNMLV->iItem, 0);
306 bool isNone = string_compare_nocase(txt, txtNone) == 0;
308 EnableDlgItem(this, IDC_FILTERFILE_TEST_BTN, !isNone);
309 EnableDlgItem(this, IDC_FILTERFILE_EDITBTN, !isNone);
310 EnableDlgItem(this, IDC_FILTERFILE_DELETEBTN, !isNone);
316 * @brief Called before infotip is shown to get infotip text.
317 * @param [in] pNMHDR Listview item data.
318 * @param [out] pResult Result of the action is returned in here.
320 void FileFiltersDlg::OnInfoTip(NMHDR * pNMHDR, LRESULT * pResult)
322 LVHITTESTINFO lvhti = {0};
323 NMLVGETINFOTIP * pInfoTip = reinterpret_cast<NMLVGETINFOTIP*>(pNMHDR);
326 // Get subitem under mouse cursor
327 lvhti.pt = m_ptLastMousePos;
328 m_listFilters.SubItemHitTest(&lvhti);
330 if (lvhti.iSubItem > 1)
332 // Check that we are over icon or label
333 if ((lvhti.flags & LVHT_ONITEMICON) || (lvhti.flags & LVHT_ONITEMLABEL))
335 // Set item text to tooltip
336 String strText = m_listFilters.GetItemText(lvhti.iItem, lvhti.iSubItem);
337 _tcscpy(pInfoTip->pszText, strText.c_str());
343 * @brief Track mouse position for showing tooltips.
344 * @param [in] nFlags Mouse movement flags.
345 * @param [in] point Current mouse position.
347 void FileFiltersDlg::OnMouseMove(UINT nFlags, CPoint point)
349 m_ptLastMousePos = point;
350 CDialog::OnMouseMove(nFlags, point);
354 * @brief Called when user presses "Test" button.
356 * Asks filename for new filter from user (using standard
357 * file picker dialog) and copies template file to that
358 * name. Opens new filterfile for editing.
359 * @todo (At least) Warn if user puts filter to outside
360 * filter directories?
362 void FileFiltersDlg::OnBnClickedFilterfileTestButton()
366 int sel = m_listFilters.GetNextItem(-1, LVNI_SELECTED);
369 if (IsFilterItemNone(sel))
372 m_sFileFilterPath = m_listFilters.GetItemText(sel, 2);
374 // Ensure filter is up-to-date (user probably just edited it)
375 theApp.m_pGlobalFileFilter->ReloadUpdatedFilters();
377 FileFilterMgr *pMgr = theApp.m_pGlobalFileFilter->GetManager();
378 FileFilter * pFileFilter = pMgr->GetFilterByPath(m_sFileFilterPath);
382 CTestFilterDlg dlg(this, pFileFilter, pMgr);
387 * @brief Called when user presses "New..." button.
389 * Asks filename for new filter from user (using standard
390 * file picker dialog) and copies template file to that
391 * name. Opens new filterfile for editing.
392 * @todo (At least) Warn if user puts filter to outside
393 * filter directories?
394 * @todo Can global filter path be empty (I think not - Kimmo).
396 void FileFiltersDlg::OnBnClickedFilterfileNewbutton()
398 String globalPath = theApp.m_pGlobalFileFilter->GetGlobalFilterPathWithCreate();
399 String userPath = theApp.m_pGlobalFileFilter->GetUserFilterPathWithCreate();
401 if (globalPath.empty() && userPath.empty())
403 LangMessageBox(IDS_FILEFILTER_NO_USERFOLDER, MB_ICONSTOP);
407 // Format path to template file
408 String templatePath = paths_ConcatPath(globalPath, FILE_FILTER_TEMPLATE);
410 if (paths_DoesPathExist(templatePath) != IS_EXISTING_FILE)
412 String msg = string_format_string2(
413 _("Cannot find file filter template file!\n\nPlease copy file %1 to WinMerge/Filters -folder:\n%2."),
414 FILE_FILTER_TEMPLATE, templatePath);
415 AfxMessageBox(msg.c_str(), MB_ICONERROR);
419 String path = globalPath.empty() ? userPath : globalPath;
421 if (!globalPath.empty() && !userPath.empty())
423 path = CSharedFilterDlg::PromptForNewFilter(this, globalPath, userPath);
424 if (path.empty()) return;
428 path = paths_AddTrailingSlash(path);
431 if (SelectFile(GetSafeHwnd(), s, path.c_str(), _("Select filename for new filter"), _("File Filters (*.flt)|*.flt|All Files (*.*)|*.*||"),
434 // Fix file extension
435 TCHAR file[_MAX_FNAME] = {0};
436 TCHAR ext[_MAX_EXT] = {0};
437 TCHAR dir[_MAX_DIR] = {0};
438 TCHAR drive[_MAX_DRIVE] = {0};
439 _tsplitpath(s.c_str(), drive, dir, file, ext);
440 if (_tcslen(ext) == 0)
444 else if (_tcsicmp(ext, FileFilterExt) != 0)
452 // Open-dialog asks about overwriting, so we can overwrite filter file
453 // user has already allowed it.
454 if (!CopyFile(templatePath.c_str(), s.c_str(), FALSE))
456 String msg = string_format_string1(
457 _( "Cannot copy filter template file to filter folder:\n%1\n\nPlease make sure the folder exists and is writable."),
459 AfxMessageBox(msg.c_str(), MB_ICONERROR);
463 FileFilterMgr *pMgr = theApp.m_pGlobalFileFilter->GetManager();
464 int retval = pMgr->AddFilter(s.c_str());
465 if (retval == FILTER_OK)
467 // Remove all from filterslist and re-add so we can update UI
470 theApp.m_pGlobalFileFilter->LoadAllFileFilters();
471 theApp.m_pGlobalFileFilter->GetFileFilters(m_Filters, selected);
479 * @brief Delete selected filter.
481 void FileFiltersDlg::OnBnClickedFilterfileDelete()
486 sel = m_listFilters.GetNextItem(sel, LVNI_SELECTED);
488 // Can't delete first "None"
491 path = m_listFilters.GetItemText(sel, 2);
493 String sConfirm = string_format_string1(_("Are you sure you want to delete\n\n%1 ?"), path);
494 int res = AfxMessageBox(sConfirm.c_str(), MB_ICONWARNING | MB_YESNO);
497 if (DeleteFile(path.c_str()))
499 FileFilterMgr *pMgr = theApp.m_pGlobalFileFilter->GetManager();
500 pMgr->RemoveFilter(path);
502 // Remove all from filterslist and re-add so we can update UI
505 theApp.m_pGlobalFileFilter->GetFileFilters(m_Filters, selected);
511 String msg = string_format_string1(
512 _("Failed to delete the filter file:\n%1\n\nMaybe the file is read-only?"),
514 AfxMessageBox(msg.c_str(), MB_ICONSTOP);
521 * @brief Update filters to list.
523 void FileFiltersDlg::UpdateFiltersList()
525 int count = (int) m_Filters->size();
527 m_listFilters.DeleteAllItems();
529 String title = _("<None>");
530 m_listFilters.InsertItem(1, title.c_str());
531 m_listFilters.SetItemText(0, 1, title.c_str());
532 m_listFilters.SetItemText(0, 2, title.c_str());
534 for (int i = 0; i < count; i++)
541 * @brief Open help from mainframe when user presses F1
543 void FileFiltersDlg::OnHelp()
545 theApp.ShowHelp(FilterHelpLocation);
549 * @brief Install new filter.
550 * This function is called when user selects "Install" button from GUI.
551 * Function allows easy installation of new filters for user. For example
552 * when user has downloaded filter file from net. First we ask user to
553 * select filter to install. Then we copy selected filter to private
556 void FileFiltersDlg::OnBnClickedFilterfileInstall()
560 String userPath = theApp.m_pGlobalFileFilter->GetUserFilterPathWithCreate();
562 if (SelectFile(GetSafeHwnd(), s, path.c_str(), _("Locate filter file to install"), _("File Filters (*.flt)|*.flt|All Files (*.*)|*.*||"),
565 userPath = paths_ConcatPath(userPath, paths_FindFileName(s));
566 if (!CopyFile(s.c_str(), userPath.c_str(), TRUE))
568 // If file already exists, ask from user
569 // If user wants to, overwrite existing filter
570 if (paths_DoesPathExist(userPath) == IS_EXISTING_FILE)
572 int res = LangMessageBox(IDS_FILEFILTER_OVERWRITE, MB_YESNO |
576 if (!CopyFile(s.c_str(), userPath.c_str(), FALSE))
578 LangMessageBox(IDS_FILEFILTER_INSTALLFAIL, MB_ICONSTOP);
584 LangMessageBox(IDS_FILEFILTER_INSTALLFAIL, MB_ICONSTOP);
589 FileFilterMgr *pMgr = theApp.m_pGlobalFileFilter->GetManager();
590 pMgr->AddFilter(userPath.c_str());
592 // Remove all from filterslist and re-add so we can update UI
595 theApp.m_pGlobalFileFilter->GetFileFilters(m_Filters, selected);