OSDN Git Service

Merge with stable
[winmerge-jp/winmerge-jp.git] / Src / FileFiltersDlg.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 //    License (GPLv2+):
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.
7 //
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.
12 //
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 /////////////////////////////////////////////////////////////////////////////
17 /**
18  * @file  FileFiltersDlg.cpp
19  *
20  * @brief Implementation of FileFilters -dialog
21  */
22
23 #include "stdafx.h"
24 #include "FileFiltersDlg.h"
25 #include <vector>
26 #include "UnicodeString.h"
27 #include "Merge.h"
28 #include "FileFilterMgr.h"
29 #include "FileFilterHelper.h"
30 #include "paths.h"
31 #include "SharedFilterDlg.h"
32 #include "TestFilterDlg.h"
33 #include "FileOrFolderSelect.h"
34
35 using std::vector;
36
37 #ifdef _DEBUG
38 #define new DEBUG_NEW
39 #undef THIS_FILE
40 static char THIS_FILE[] = __FILE__;
41 #endif
42
43 /** @brief Template file used when creating new filefilter. */
44 static const TCHAR FILE_FILTER_TEMPLATE[] = _T("FileFilter.tmpl");
45
46 /** @brief Location for filters specific help to open. */
47 static const TCHAR FilterHelpLocation[] = _T("::/htmlhelp/Filters.html");
48
49 /////////////////////////////////////////////////////////////////////////////
50 // CFiltersDlg dialog
51 IMPLEMENT_DYNCREATE(FileFiltersDlg, CPropertyPage)
52
53 /**
54  * @brief Constructor.
55  */
56 FileFiltersDlg::FileFiltersDlg() : CPropertyPage(FileFiltersDlg::IDD)
57 {
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;
63 }
64
65 void FileFiltersDlg::DoDataExchange(CDataExchange* pDX)
66 {
67         CDialog::DoDataExchange(pDX);
68         //{{AFX_DATA_MAP(FileFiltersDlg)
69         DDX_Control(pDX, IDC_FILTERFILE_LIST, m_listFilters);
70         //}}AFX_DATA_MAP
71 }
72
73
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)
78         ON_WM_MOUSEMOVE()
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)
83         //}}AFX_MSG_MAP
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)
87 END_MESSAGE_MAP()
88
89 /////////////////////////////////////////////////////////////////////////////
90 // CFiltersDlg message handlers
91
92 /**
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.
96  */
97 void FileFiltersDlg::SetFilterArray(vector<FileFilterInfo> * fileFilters)
98 {
99         m_Filters = fileFilters;
100 }
101
102 /**
103  * @brief Returns path (cont. filename) of selected filter
104  * @return Full path to selected filter file.
105  */
106 String FileFiltersDlg::GetSelected()
107 {
108         return m_sFileFilterPath;
109 }
110
111 /**
112  * @brief Set path of selected filter.
113  * @param [in] Path for selected filter.
114  * @note Call this before actually showing the dialog.
115  */
116 void FileFiltersDlg::SetSelected(const String & selected)
117 {
118         m_sFileFilterPath = selected;
119 }
120
121 /**
122  * @brief Initialise listcontrol containing filters.
123  */
124 void FileFiltersDlg::InitList()
125 {
126         // Show selection across entire row.
127         // Also enable infotips.
128         m_listFilters.SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP);
129
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);
136
137         title = _("<None>");
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());
141
142         const int count = (int) m_Filters->size();
143
144         for (int i = 0; i < count; i++)
145         {
146                 AddToGrid(i);
147         }
148 }
149
150 /**
151  * @brief Select filter by index in the listview.
152  * @param [in] index Index of filter to select.
153  */
154 void FileFiltersDlg::SelectFilterByIndex(int index)
155 {
156         m_listFilters.SetItemState(index, LVIS_SELECTED, LVIS_SELECTED);
157         bool bPartialOk = false;
158         m_listFilters.EnsureVisible(index, bPartialOk);
159 }
160
161 /**
162  * @brief Called before dialog is shown.
163  * @return Always TRUE.
164  */
165 BOOL FileFiltersDlg::OnInitDialog()
166 {
167         theApp.TranslateDialog(m_hWnd);
168         CDialog::OnInitDialog();
169
170         InitList();
171
172         if (m_sFileFilterPath.empty())
173         {
174                 SelectFilterByIndex(0);
175                 return TRUE;
176         }
177
178         int count = m_listFilters.GetItemCount();
179         for (int i = 0; i < count; i++)
180         {
181                 String desc = m_listFilters.GetItemText(i, 2);
182                 if (string_compare_nocase(desc, m_sFileFilterPath) == 0)
183                 {
184                         SelectFilterByIndex(i);
185                 }
186         }
187
188         return TRUE;  // return TRUE unless you set the focus to a control
189                       // EXCEPTION: OCX Property Pages should return FALSE
190 }
191
192 /**
193  * @brief Add filter from filter-list index to dialog.
194  * @param [in] filterIndex Index of filter to add.
195  */
196 void FileFiltersDlg::AddToGrid(int filterIndex)
197 {
198         const FileFilterInfo & filterinfo = m_Filters->at(filterIndex);
199         const int item = filterIndex + 1;
200
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());
204 }
205
206 /**
207  * @brief Called when dialog is closed with "OK" button.
208  */
209 void FileFiltersDlg::OnOK()
210 {
211         int sel = m_listFilters.GetNextItem(-1, LVNI_SELECTED);
212         m_sFileFilterPath = m_listFilters.GetItemText(sel, 2);
213
214         CDialog::OnOK();
215 }
216
217 /**
218  * @brief Open selected filter for editing.
219  *
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()
228  */
229 void FileFiltersDlg::OnFiltersEditbtn()
230 {
231         int sel =- 1;
232
233         sel = m_listFilters.GetNextItem(sel, LVNI_SELECTED);
234
235         // Can't edit first "None"
236         if (sel > 0)
237         {
238                 String path = m_listFilters.GetItemText(sel, 2);
239                 EditFileFilter(path);
240         }
241 }
242
243 /**
244  * @brief Edit file filter in external editor.
245  * @param [in] path Full path to file filter to edit.
246  */
247 void FileFiltersDlg::EditFileFilter(const String& path)
248 {
249         theApp.OpenFileToExternalEditor(path);
250 }
251
252 /**
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.
256  */
257 void FileFiltersDlg::OnDblclkFiltersList(NMHDR* pNMHDR, LRESULT* pResult)
258 {
259         UNREFERENCED_PARAMETER(pNMHDR);
260
261         OnFiltersEditbtn();
262         *pResult = 0;
263 }
264
265 /**
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.
270  */
271 static void EnableDlgItem(CWnd * parent, int item, bool enable)
272 {
273         parent->GetDlgItem(item)->EnableWindow(!!enable);
274 }
275
276 /**
277  * @brief Is item in list the <None> item?
278  * @param [in] item Item to test.
279  * @return true if item is <None> item.
280  */
281 bool FileFiltersDlg::IsFilterItemNone(int item) const
282 {
283         String txtNone = _("<None>");
284         String txt = m_listFilters.GetItemText(item, 0);
285
286         return (string_compare_nocase(txt, txtNone) == 0);
287 }
288
289 /**
290  * @brief Called when item state is changed.
291  *
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.
295  */
296 void FileFiltersDlg::OnLvnItemchangedFilterfileList(NMHDR *pNMHDR, LRESULT *pResult)
297 {
298         LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
299
300         // If item got selected
301         if (pNMLV->uNewState & LVIS_SELECTED)
302         {
303                 String txtNone = _("<None>");
304                 String txt = m_listFilters.GetItemText(pNMLV->iItem, 0);
305
306                 bool isNone = string_compare_nocase(txt, txtNone) == 0;
307
308                 EnableDlgItem(this, IDC_FILTERFILE_TEST_BTN, !isNone);
309                 EnableDlgItem(this, IDC_FILTERFILE_EDITBTN, !isNone);
310                 EnableDlgItem(this, IDC_FILTERFILE_DELETEBTN, !isNone);
311         }
312         *pResult = 0;
313 }
314
315 /**
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.
319  */
320 void FileFiltersDlg::OnInfoTip(NMHDR * pNMHDR, LRESULT * pResult)
321 {
322         LVHITTESTINFO lvhti = {0};
323         NMLVGETINFOTIP * pInfoTip = reinterpret_cast<NMLVGETINFOTIP*>(pNMHDR);
324         ASSERT(pInfoTip);
325
326         // Get subitem under mouse cursor
327         lvhti.pt = m_ptLastMousePos;
328         m_listFilters.SubItemHitTest(&lvhti);
329
330         if (lvhti.iSubItem > 1)
331         {
332                 // Check that we are over icon or label
333                 if ((lvhti.flags & LVHT_ONITEMICON) || (lvhti.flags & LVHT_ONITEMLABEL))
334                 {
335                         // Set item text to tooltip
336                         String strText = m_listFilters.GetItemText(lvhti.iItem, lvhti.iSubItem);
337                         _tcscpy(pInfoTip->pszText, strText.c_str());
338                 }
339         }
340 }
341
342 /**
343  * @brief Track mouse position for showing tooltips.
344  * @param [in] nFlags Mouse movement flags.
345  * @param [in] point Current mouse position.
346  */
347 void FileFiltersDlg::OnMouseMove(UINT nFlags, CPoint point) 
348 {
349         m_ptLastMousePos = point;
350         CDialog::OnMouseMove(nFlags, point);
351 }
352
353 /**
354  * @brief Called when user presses "Test" button.
355  *
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?
361  */
362 void FileFiltersDlg::OnBnClickedFilterfileTestButton()
363 {
364         UpdateData(TRUE);
365
366         int sel = m_listFilters.GetNextItem(-1, LVNI_SELECTED);
367         if (sel == -1)
368                 return;
369         if (IsFilterItemNone(sel))
370                 return;
371         
372         m_sFileFilterPath = m_listFilters.GetItemText(sel, 2);
373
374         // Ensure filter is up-to-date (user probably just edited it)
375         theApp.m_pGlobalFileFilter->ReloadUpdatedFilters();
376
377         FileFilterMgr *pMgr = theApp.m_pGlobalFileFilter->GetManager();
378         FileFilter * pFileFilter = pMgr->GetFilterByPath(m_sFileFilterPath);
379         if (!pFileFilter)
380                 return;
381
382         CTestFilterDlg dlg(this, pFileFilter, pMgr);
383         dlg.DoModal();
384 }
385
386 /**
387  * @brief Called when user presses "New..." button.
388  *
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).
395  */
396 void FileFiltersDlg::OnBnClickedFilterfileNewbutton()
397 {
398         String globalPath = theApp.m_pGlobalFileFilter->GetGlobalFilterPathWithCreate();
399         String userPath = theApp.m_pGlobalFileFilter->GetUserFilterPathWithCreate();
400
401         if (globalPath.empty() && userPath.empty())
402         {
403                 LangMessageBox(IDS_FILEFILTER_NO_USERFOLDER, MB_ICONSTOP);
404                 return;
405         }
406
407         // Format path to template file
408         String templatePath = paths_ConcatPath(globalPath, FILE_FILTER_TEMPLATE);
409
410         if (paths_DoesPathExist(templatePath) != IS_EXISTING_FILE)
411         {
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);
416                 return;
417         }
418
419         String path = globalPath.empty() ? userPath : globalPath;
420
421         if (!globalPath.empty() && !userPath.empty())
422         {
423                 path = CSharedFilterDlg::PromptForNewFilter(this, globalPath, userPath);
424                 if (path.empty()) return;
425         }
426
427         if (path.length())
428                 path = paths_AddTrailingSlash(path);
429         
430         String s;
431         if (SelectFile(GetSafeHwnd(), s, path.c_str(), _("Select filename for new filter"), _("File Filters (*.flt)|*.flt|All Files (*.*)|*.*||"),
432                 FALSE))
433         {
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)
441                 {
442                         s += FileFilterExt;
443                 }
444                 else if (_tcsicmp(ext, FileFilterExt) != 0)
445                 {
446                         s = drive;
447                         s += dir;
448                         s += file;
449                         s += FileFilterExt;
450                 }
451
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))
455                 {
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."),
458                                 templatePath);
459                         AfxMessageBox(msg.c_str(), MB_ICONERROR);
460                         return;
461                 }
462                 EditFileFilter(s);
463                 FileFilterMgr *pMgr = theApp.m_pGlobalFileFilter->GetManager();
464                 int retval = pMgr->AddFilter(s.c_str());
465                 if (retval == FILTER_OK)
466                 {
467                         // Remove all from filterslist and re-add so we can update UI
468                         String selected;
469                         m_Filters->clear();
470                         theApp.m_pGlobalFileFilter->LoadAllFileFilters();
471                         theApp.m_pGlobalFileFilter->GetFileFilters(m_Filters, selected);
472
473                         UpdateFiltersList();
474                 }
475         }
476 }
477
478 /**
479  * @brief Delete selected filter.
480  */
481 void FileFiltersDlg::OnBnClickedFilterfileDelete()
482 {
483         String path;
484         int sel =- 1;
485
486         sel = m_listFilters.GetNextItem(sel, LVNI_SELECTED);
487
488         // Can't delete first "None"
489         if (sel > 0)
490         {
491                 path = m_listFilters.GetItemText(sel, 2);
492
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);
495                 if (res == IDYES)
496                 {
497                         if (DeleteFile(path.c_str()))
498                         {
499                                 FileFilterMgr *pMgr = theApp.m_pGlobalFileFilter->GetManager();
500                                 pMgr->RemoveFilter(path);
501                                 
502                                 // Remove all from filterslist and re-add so we can update UI
503                                 String selected;
504                                 m_Filters->clear();
505                                 theApp.m_pGlobalFileFilter->GetFileFilters(m_Filters, selected);
506
507                                 UpdateFiltersList();
508                         }
509                         else
510                         {
511                                 String msg = string_format_string1(
512                                         _("Failed to delete the filter file:\n%1\n\nMaybe the file is read-only?"),
513                                         path);
514                                 AfxMessageBox(msg.c_str(), MB_ICONSTOP);
515                         }
516                 }
517         }
518 }
519
520 /**
521  * @brief Update filters to list.
522  */
523 void FileFiltersDlg::UpdateFiltersList()
524 {
525         int count = (int) m_Filters->size();
526
527         m_listFilters.DeleteAllItems();
528
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());
533
534         for (int i = 0; i < count; i++)
535         {
536                 AddToGrid(i);
537         }
538 }
539
540 /**
541  * @brief Open help from mainframe when user presses F1
542  */
543 void FileFiltersDlg::OnHelp()
544 {
545         theApp.ShowHelp(FilterHelpLocation);
546 }
547
548 /**
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
554  * filters folder.
555  */
556 void FileFiltersDlg::OnBnClickedFilterfileInstall()
557 {
558         String s;
559         String path;
560         String userPath = theApp.m_pGlobalFileFilter->GetUserFilterPathWithCreate();
561
562         if (SelectFile(GetSafeHwnd(), s, path.c_str(), _("Locate filter file to install"), _("File Filters (*.flt)|*.flt|All Files (*.*)|*.*||"),
563                 TRUE))
564         {
565                 userPath = paths_ConcatPath(userPath, paths_FindFileName(s));
566                 if (!CopyFile(s.c_str(), userPath.c_str(), TRUE))
567                 {
568                         // If file already exists, ask from user
569                         // If user wants to, overwrite existing filter
570                         if (paths_DoesPathExist(userPath) == IS_EXISTING_FILE)
571                         {
572                                 int res = LangMessageBox(IDS_FILEFILTER_OVERWRITE, MB_YESNO |
573                                         MB_ICONWARNING);
574                                 if (res == IDYES)
575                                 {
576                                         if (!CopyFile(s.c_str(), userPath.c_str(), FALSE))
577                                         {
578                                                 LangMessageBox(IDS_FILEFILTER_INSTALLFAIL, MB_ICONSTOP);
579                                         }
580                                 }
581                         }
582                         else
583                         {
584                                 LangMessageBox(IDS_FILEFILTER_INSTALLFAIL, MB_ICONSTOP);
585                         }
586                 }
587                 else
588                 {
589                         FileFilterMgr *pMgr = theApp.m_pGlobalFileFilter->GetManager();
590                         pMgr->AddFilter(userPath.c_str());
591
592                         // Remove all from filterslist and re-add so we can update UI
593                         String selected;
594                         m_Filters->clear();
595                         theApp.m_pGlobalFileFilter->GetFileFilters(m_Filters, selected);
596
597                         UpdateFiltersList();
598                 }
599         }
600 }