1 #include "filezilla.h"
2 #include "filter_manager.h"
3 #include "filteredit.h"
4 #include "filezillaapp.h"
5 #include "inputdialog.h"
6 #include "Mainfrm.h"
7 #include "Options.h"
8 #include "state.h"
9 #include "xmlfunctions.h"
10
11 #include "../commonui/ipcmutex.h"
12
13 #include <libfilezilla/local_filesys.hpp>
14
15 #include <wx/statline.h>
16 #include <wx/statbox.h>
17
18 bool CFilterManager::m_loaded = false;
19 filter_data CFilterManager::global_filters_;
20 bool CFilterManager::m_filters_disabled = false;
21
BEGIN_EVENT_TABLE(CFilterDialog,wxDialogEx)22 BEGIN_EVENT_TABLE(CFilterDialog, wxDialogEx)
23 EVT_BUTTON(XRCID("wxID_OK"), CFilterDialog::OnOkOrApply)
24 EVT_BUTTON(XRCID("wxID_CANCEL"), CFilterDialog::OnCancel)
25 EVT_BUTTON(XRCID("wxID_APPLY"), CFilterDialog::OnOkOrApply)
26 EVT_BUTTON(XRCID("ID_EDIT"), CFilterDialog::OnEdit)
27 EVT_CHECKLISTBOX(wxID_ANY, CFilterDialog::OnFilterSelect)
28 EVT_BUTTON(XRCID("ID_SAVESET"), CFilterDialog::OnSaveAs)
29 EVT_BUTTON(XRCID("ID_RENAMESET"), CFilterDialog::OnRename)
30 EVT_BUTTON(XRCID("ID_DELETESET"), CFilterDialog::OnDeleteSet)
31 EVT_CHOICE(XRCID("ID_SETS"), CFilterDialog::OnSetSelect)
32
33 EVT_BUTTON(XRCID("ID_LOCAL_ENABLEALL"), CFilterDialog::OnChangeAll)
34 EVT_BUTTON(XRCID("ID_LOCAL_DISABLEALL"), CFilterDialog::OnChangeAll)
35 EVT_BUTTON(XRCID("ID_REMOTE_ENABLEALL"), CFilterDialog::OnChangeAll)
36 EVT_BUTTON(XRCID("ID_REMOTE_DISABLEALL"), CFilterDialog::OnChangeAll)
37 END_EVENT_TABLE()
38
39
40 CFilterDialog::CFilterDialog()
41 : m_filters(global_filters_.filters)
42 , m_filterSets(global_filters_.filter_sets)
43 , m_currentFilterSet(global_filters_.current_filter_set)
44 {
45 }
46
Create(CMainFrame * parent)47 bool CFilterDialog::Create(CMainFrame* parent)
48 {
49 m_pMainFrame = parent;
50
51 wxDialogEx::Create(parent, -1, _("Directory listing filters"));
52
53 auto & lay = layout();
54
55 auto main = lay.createMain(this, 1);
56 main->AddGrowableCol(0);
57
58 {
59 auto row = lay.createFlex(0, 1);
60 main->Add(row);
61
62 row->Add(new wxStaticText(this, -1, _("&Filter sets:")), lay.valign);
63 auto choice = new wxChoice(this, XRCID("ID_SETS"));
64 choice->SetFocus();
65 row->Add(choice, lay.valign);
66 row->Add(new wxButton(this, XRCID("ID_SAVESET"), _("&Save as...")), lay.valign);
67 row->Add(new wxButton(this, XRCID("ID_RENAMESET"), _("&Rename...")), lay.valign);
68 row->Add(new wxButton(this, XRCID("ID_DELETESET"), _("&Delete...")), lay.valign);
69
70 wxString name = _("Custom filter set");
71 choice->Append(_T("<") + name + _T(">"));
72 for (size_t i = 1; i < m_filterSets.size(); ++i) {
73 choice->Append(m_filterSets[i].name);
74 }
75 choice->SetSelection(m_currentFilterSet);
76 }
77
78 auto sides = lay.createGrid(2);
79 main->Add(sides, lay.grow);
80
81 {
82 auto [box, inner] = lay.createStatBox(sides, _("Local filters:"), 1);
83 inner->AddGrowableCol(0);
84 auto filters = new wxCheckListBox(box, XRCID("ID_LOCALFILTERS"), wxDefaultPosition, wxSize(-1, lay.dlgUnits(100)));
85 inner->Add(filters, 1, wxGROW);
86 auto row = lay.createFlex(0, 1);
87 inner->Add(row, 0, wxALIGN_CENTER_HORIZONTAL);
88 row->Add(new wxButton(box, XRCID("ID_LOCAL_ENABLEALL"), _("E&nable all")), lay.valign);
89 row->Add(new wxButton(box, XRCID("ID_LOCAL_DISABLEALL"), _("D&isable all")), lay.valign);
90
91 filters->Connect(wxID_ANY, wxEVT_LEFT_DOWN, wxMouseEventHandler(CFilterDialog::OnMouseEvent), 0, this);
92 filters->Connect(wxID_ANY, wxEVT_KEY_DOWN, wxKeyEventHandler(CFilterDialog::OnKeyEvent), 0, this);
93
94 }
95 {
96 auto [box, inner] = lay.createStatBox(sides, _("Remote filters:"), 1);
97 inner->AddGrowableCol(0);
98 auto filters = new wxCheckListBox(box, XRCID("ID_REMOTEFILTERS"), wxDefaultPosition, wxSize(-1, lay.dlgUnits(100)));
99 inner->Add(filters, 1, wxGROW);
100 auto row = lay.createFlex(0, 1);
101 inner->Add(row, 0, wxALIGN_CENTER_HORIZONTAL);
102 row->Add(new wxButton(box, XRCID("ID_REMOTE_ENABLEALL"), _("En&able all")), lay.valign);
103 row->Add(new wxButton(box, XRCID("ID_REMOTE_DISABLEALL"), _("Disa&ble all")), lay.valign);
104
105 filters->Connect(wxID_ANY, wxEVT_LEFT_DOWN, wxMouseEventHandler(CFilterDialog::OnMouseEvent), 0, this);
106 filters->Connect(wxID_ANY, wxEVT_KEY_DOWN, wxKeyEventHandler(CFilterDialog::OnKeyEvent), 0, this);
107 }
108
109
110 main->Add(new wxStaticText(this, -1, _("Hold the shift key to toggle the filter state on both sides simultaneously.")));
111
112 main->Add(new wxStaticLine(this), lay.grow);
113
114 {
115 auto row = lay.createFlex(0, 1);
116 row->AddGrowableCol(0);
117 main->Add(row, lay.grow);
118 row->Add(new wxButton(this, XRCID("ID_EDIT"), _("&Edit filter rules...")), lay.valign);
119 row->AddStretchSpacer();
120
121 auto buttons = lay.createGrid(0, 1);
122 row->Add(buttons, lay.valign);
123 auto ok = new wxButton(this, wxID_OK, _("OK"));
124 ok->SetDefault();
125 buttons->Add(ok, lay.valigng);
126 buttons->Add(new wxButton(this, wxID_CANCEL, _("Cancel")), lay.valigng);
127 buttons->Add(new wxButton(this, wxID_APPLY, _("Apply")), lay.valigng);
128 }
129
130 DisplayFilters();
131
132 SetCtrlState();
133
134 GetSizer()->Fit(this);
135
136 return true;
137 }
138
OnOkOrApply(wxCommandEvent & event)139 void CFilterDialog::OnOkOrApply(wxCommandEvent& event)
140 {
141 global_filters_.filters = m_filters;
142 global_filters_.filter_sets = m_filterSets;
143 global_filters_.current_filter_set = m_currentFilterSet;
144
145 SaveFilters();
146 m_filters_disabled = false;
147
148 CContextManager::Get()->NotifyAllHandlers(STATECHANGE_APPLYFILTER);
149
150 if (event.GetId() == wxID_OK) {
151 EndModal(wxID_OK);
152 }
153 }
154
OnCancel(wxCommandEvent &)155 void CFilterDialog::OnCancel(wxCommandEvent&)
156 {
157 EndModal(wxID_CANCEL);
158 }
159
OnEdit(wxCommandEvent &)160 void CFilterDialog::OnEdit(wxCommandEvent&)
161 {
162 CFilterEditDialog dlg;
163 if (!dlg.Create(this, m_filters, m_filterSets)) {
164 return;
165 }
166
167 if (dlg.ShowModal() != wxID_OK) {
168 return;
169 }
170
171 m_filters = dlg.GetFilters();
172 m_filterSets = dlg.GetFilterSets();
173
174 DisplayFilters();
175 }
176
DisplayFilters()177 void CFilterDialog::DisplayFilters()
178 {
179 wxCheckListBox* pLocalFilters = XRCCTRL(*this, "ID_LOCALFILTERS", wxCheckListBox);
180 wxCheckListBox* pRemoteFilters = XRCCTRL(*this, "ID_REMOTEFILTERS", wxCheckListBox);
181
182 pLocalFilters->Clear();
183 pRemoteFilters->Clear();
184
185 for (unsigned int i = 0; i < m_filters.size(); ++i) {
186 const CFilter& filter = m_filters[i];
187
188 const bool localOnly = filter.IsLocalFilter();
189
190 pLocalFilters->Append(filter.name);
191 pRemoteFilters->Append(filter.name);
192
193 pLocalFilters->Check(i, m_filterSets[m_currentFilterSet].local[i]);
194 pRemoteFilters->Check(i, localOnly ? false : m_filterSets[m_currentFilterSet].remote[i]);
195 }
196 }
197
OnMouseEvent(wxMouseEvent & event)198 void CFilterDialog::OnMouseEvent(wxMouseEvent& event)
199 {
200 m_shiftClick = event.ShiftDown();
201 event.Skip();
202 }
203
OnKeyEvent(wxKeyEvent & event)204 void CFilterDialog::OnKeyEvent(wxKeyEvent& event)
205 {
206 m_shiftClick = event.ShiftDown();
207 event.Skip();
208 }
209
OnFilterSelect(wxCommandEvent & event)210 void CFilterDialog::OnFilterSelect(wxCommandEvent& event)
211 {
212 wxCheckListBox* pLocal = XRCCTRL(*this, "ID_LOCALFILTERS", wxCheckListBox);
213 wxCheckListBox* pRemote = XRCCTRL(*this, "ID_REMOTEFILTERS", wxCheckListBox);
214
215 int item = event.GetSelection();
216
217 const CFilter& filter = m_filters[item];
218 const bool localOnly = filter.IsLocalFilter();
219 if (localOnly && event.GetEventObject() != pLocal) {
220 pRemote->Check(item, false);
221 wxMessageBoxEx(_("Selected filter only works for local files."), _("Cannot select filter"), wxICON_INFORMATION);
222 return;
223 }
224
225
226 if (m_shiftClick) {
227 if (event.GetEventObject() == pLocal) {
228 if (!localOnly) {
229 pRemote->Check(item, pLocal->IsChecked(event.GetSelection()));
230 }
231 }
232 else {
233 pLocal->Check(item, pRemote->IsChecked(event.GetSelection()));
234 }
235 }
236
237 if (m_currentFilterSet) {
238 m_filterSets[0] = m_filterSets[m_currentFilterSet];
239 m_currentFilterSet = 0;
240 wxChoice* pChoice = XRCCTRL(*this, "ID_SETS", wxChoice);
241 pChoice->SetSelection(0);
242 }
243
244 bool localChecked = pLocal->IsChecked(event.GetSelection());
245 bool remoteChecked = pRemote->IsChecked(event.GetSelection());
246 m_filterSets[0].local[item] = localChecked;
247 m_filterSets[0].remote[item] = remoteChecked;
248 }
249
OnSaveAs(wxCommandEvent &)250 void CFilterDialog::OnSaveAs(wxCommandEvent&)
251 {
252 CInputDialog dlg;
253 dlg.Create(this, _("Enter name for filterset"), _("Please enter a unique name for this filter set"), 255);
254 if (dlg.ShowModal() != wxID_OK) {
255 return;
256 }
257
258 std::wstring name = dlg.GetValue().ToStdWstring();
259 if (name.empty()) {
260 wxMessageBoxEx(_("No name for the filterset given."), _("Cannot save filterset"), wxICON_INFORMATION);
261 return;
262 }
263 wxChoice* pChoice = XRCCTRL(*this, "ID_SETS", wxChoice);
264
265 CFilterSet set;
266 int old_pos = pChoice->GetSelection();
267 if (old_pos > 0) {
268 set = m_filterSets[old_pos];
269 }
270 else {
271 set = m_filterSets[0];
272 }
273
274 int pos = pChoice->FindString(name);
275 if (pos != wxNOT_FOUND) {
276 if (wxMessageBoxEx(_("Given filterset name already exists, overwrite filter set?"), _("Filter set already exists"), wxICON_QUESTION | wxYES_NO) != wxYES) {
277 return;
278 }
279 }
280
281 if (pos == wxNOT_FOUND) {
282 pos = m_filterSets.size();
283 m_filterSets.push_back(set);
284 pChoice->Append(name);
285 }
286 else {
287 m_filterSets[pos] = set;
288 }
289
290 m_filterSets[pos].name = name;
291
292 pChoice->SetSelection(pos);
293 m_currentFilterSet = pos;
294
295 SetCtrlState();
296
297 GetSizer()->Fit(this);
298 }
299
OnRename(wxCommandEvent &)300 void CFilterDialog::OnRename(wxCommandEvent&)
301 {
302 wxChoice* pChoice = XRCCTRL(*this, "ID_SETS", wxChoice);
303 int old_pos = pChoice->GetSelection();
304 if (old_pos == -1) {
305 return;
306 }
307
308 if (!old_pos) {
309 wxMessageBoxEx(_("This filter set cannot be renamed."));
310 return;
311 }
312
313 CInputDialog dlg;
314
315 wxString msg = wxString::Format(_("Please enter a new name for the filter set \"%s\""), pChoice->GetStringSelection());
316
317 dlg.Create(this, _("Enter new name for filterset"), msg, 255);
318 if (dlg.ShowModal() != wxID_OK) {
319 return;
320 }
321
322 std::wstring name = dlg.GetValue().ToStdWstring();
323
324 if (name == pChoice->GetStringSelection()) {
325 // Nothing changed
326 return;
327 }
328
329 if (name.empty()) {
330 wxMessageBoxEx(_("No name for the filterset given."), _("Cannot save filterset"), wxICON_INFORMATION);
331 return;
332 }
333
334 int pos = pChoice->FindString(name);
335 if (pos != wxNOT_FOUND) {
336 if (wxMessageBoxEx(_("Given filterset name already exists, overwrite filter set?"), _("Filter set already exists"), wxICON_QUESTION | wxYES_NO) != wxYES) {
337 return;
338 }
339 }
340
341 // Remove old entry
342 pChoice->Delete(old_pos);
343 CFilterSet set = m_filterSets[old_pos];
344 m_filterSets.erase(m_filterSets.begin() + old_pos);
345
346 pos = pChoice->FindString(name);
347 if (pos == wxNOT_FOUND) {
348 pos = m_filterSets.size();
349 m_filterSets.push_back(set);
350 pChoice->Append(name);
351 }
352 else {
353 m_filterSets[pos] = set;
354 }
355
356 m_filterSets[pos].name = name;
357
358 pChoice->SetSelection(pos);
359 m_currentFilterSet = pos;
360
361 GetSizer()->Fit(this);
362 }
363
OnDeleteSet(wxCommandEvent &)364 void CFilterDialog::OnDeleteSet(wxCommandEvent&)
365 {
366 wxChoice* pChoice = XRCCTRL(*this, "ID_SETS", wxChoice);
367 int pos = pChoice->GetSelection();
368 if (pos < 0) {
369 return;
370 }
371
372 if (!pos || static_cast<size_t>(pos) >= m_filterSets.size()) {
373 wxMessageBoxEx(_("This filter set cannot be removed."));
374 return;
375 }
376
377 m_filterSets[0] = m_filterSets[pos];
378
379 pChoice->Delete(pos);
380 m_filterSets.erase(m_filterSets.begin() + pos);
381
382 pChoice->SetSelection(0);
383 m_currentFilterSet = 0;
384
385 SetCtrlState();
386 }
387
OnSetSelect(wxCommandEvent & event)388 void CFilterDialog::OnSetSelect(wxCommandEvent& event)
389 {
390 m_currentFilterSet = event.GetSelection();
391 DisplayFilters();
392 SetCtrlState();
393 }
394
OnChangeAll(wxCommandEvent & event)395 void CFilterDialog::OnChangeAll(wxCommandEvent& event)
396 {
397 bool check = true;
398 if (event.GetId() == XRCID("ID_LOCAL_DISABLEALL") || event.GetId() == XRCID("ID_REMOTE_DISABLEALL")) {
399 check = false;
400 }
401
402 bool local;
403 std::vector<unsigned char>* pValues;
404 wxCheckListBox* pListBox;
405 if (event.GetId() == XRCID("ID_LOCAL_ENABLEALL") || event.GetId() == XRCID("ID_LOCAL_DISABLEALL")) {
406 pListBox = XRCCTRL(*this, "ID_LOCALFILTERS", wxCheckListBox);
407 pValues = &m_filterSets[0].local;
408 local = true;
409 }
410 else {
411 pListBox = XRCCTRL(*this, "ID_REMOTEFILTERS", wxCheckListBox);
412 pValues = &m_filterSets[0].remote;
413 local = false;
414 }
415
416 if (m_currentFilterSet) {
417 m_filterSets[0] = m_filterSets[m_currentFilterSet];
418 m_currentFilterSet = 0;
419 wxChoice* pChoice = XRCCTRL(*this, "ID_SETS", wxChoice);
420 pChoice->SetSelection(0);
421 }
422
423 for (size_t i = 0; i < pListBox->GetCount(); ++i) {
424 if (!local && (m_filters[i].IsLocalFilter())) {
425 pListBox->Check(i, false);
426 (*pValues)[i] = false;
427 }
428 else {
429 pListBox->Check(i, check);
430 (*pValues)[i] = check;
431 }
432 }
433 }
434
SetCtrlState()435 void CFilterDialog::SetCtrlState()
436 {
437 wxChoice* pChoice = XRCCTRL(*this, "ID_SETS", wxChoice);
438
439 int sel = pChoice->GetSelection();
440 XRCCTRL(*this, "ID_RENAMESET", wxButton)->Enable(sel > 0);
441 XRCCTRL(*this, "ID_DELETESET", wxButton)->Enable(sel > 0);
442 }
443
CFilterManager()444 CFilterManager::CFilterManager()
445 {
446 LoadFilters();
447 }
448
HasActiveFilters(bool ignore_disabled)449 bool CFilterManager::HasActiveFilters(bool ignore_disabled)
450 {
451 if (!m_loaded) {
452 LoadFilters();
453 }
454
455 if (global_filters_.filter_sets.empty()) {
456 return false;
457 }
458
459 if (m_filters_disabled && !ignore_disabled) {
460 return false;
461 }
462
463 const CFilterSet& set = global_filters_.filter_sets[global_filters_.current_filter_set];
464 for (unsigned int i = 0; i < global_filters_.filters.size(); ++i) {
465 if (set.local[i]) {
466 return true;
467 }
468
469 if (set.remote[i]) {
470 return true;
471 }
472 }
473
474 return false;
475 }
476
HasSameLocalAndRemoteFilters() const477 bool CFilterManager::HasSameLocalAndRemoteFilters() const
478 {
479 CFilterSet const& set = global_filters_.filter_sets[global_filters_.current_filter_set];
480 return set.local == set.remote;
481
482 return true;
483 }
484
FilenameFiltered(std::wstring const & name,std::wstring const & path,bool dir,int64_t size,bool local,int attributes,fz::datetime const & date) const485 bool CFilterManager::FilenameFiltered(std::wstring const& name, std::wstring const& path, bool dir, int64_t size, bool local, int attributes, fz::datetime const& date) const
486 {
487 if (m_filters_disabled) {
488 return false;
489 }
490
491 CFilterSet const& set = global_filters_.filter_sets[global_filters_.current_filter_set];
492 auto const& active = local ? set.local : set.remote;
493
494 // Check active filters
495 for (unsigned int i = 0; i < global_filters_.filters.size(); ++i) {
496 if (active[i]) {
497 if (FilenameFilteredByFilter(global_filters_.filters[i], name, path, dir, size, attributes, date)) {
498 return true;
499 }
500 }
501 }
502
503 return false;
504 }
505
LoadFilters()506 void CFilterManager::LoadFilters()
507 {
508 if (m_loaded) {
509 return;
510 }
511
512 m_loaded = true;
513
514 CReentrantInterProcessMutexLocker mutex(MUTEX_FILTERS);
515
516 std::wstring file(wxGetApp().GetSettingsFile(L"filters"));
517 if (fz::local_filesys::get_size(fz::to_native(file)) < 1) {
518 file = wxGetApp().GetResourceDir().GetPath() + L"defaultfilters.xml";
519 }
520
521 CXmlFile xml(file);
522 auto element = xml.Load();
523 load_filters(element, global_filters_);
524
525 if (!element) {
526 wxString msg = xml.GetError() + _T("\n\n") + _("Any changes made to the filters will not be saved.");
527 wxMessageBoxEx(msg, _("Error loading xml file"), wxICON_ERROR);
528 }
529 }
530
Import(pugi::xml_node & element)531 void CFilterManager::Import(pugi::xml_node& element)
532 {
533 if (!element) {
534 return;
535 }
536
537 global_filters_.filters.clear();
538 global_filters_.filter_sets.clear();
539 global_filters_.current_filter_set = 0;
540 m_filters_disabled = false;
541
542 CReentrantInterProcessMutexLocker mutex(MUTEX_FILTERS);
543
544 LoadFilters(element);
545 SaveFilters();
546
547 CContextManager::Get()->NotifyAllHandlers(STATECHANGE_APPLYFILTER);
548 }
549
LoadFilters(pugi::xml_node & element)550 void CFilterManager::LoadFilters(pugi::xml_node& element)
551 {
552 load_filters(element, global_filters_);
553 if (global_filters_.filter_sets.empty()) {
554 CFilterSet set;
555 set.local.resize(global_filters_.filters.size(), false);
556 set.remote.resize(global_filters_.filters.size(), false);
557
558 global_filters_.filter_sets.push_back(set);
559 }
560 }
561
SaveFilters()562 void CFilterManager::SaveFilters()
563 {
564 CReentrantInterProcessMutexLocker mutex(MUTEX_FILTERS);
565
566 CXmlFile xml(wxGetApp().GetSettingsFile(_T("filters")));
567 auto element = xml.Load();
568 if (!element) {
569 wxString msg = xml.GetError() + _T("\n\n") + _("Any changes made to the filters could not be saved.");
570 wxMessageBoxEx(msg, _("Error loading xml file"), wxICON_ERROR);
571
572 return;
573 }
574
575 save_filters(element, global_filters_);
576
577 SaveWithErrorDialog(xml);
578 }
579
ToggleFilters()580 void CFilterManager::ToggleFilters()
581 {
582 if (m_filters_disabled) {
583 m_filters_disabled = false;
584 return;
585 }
586
587 if (HasActiveFilters(true)) {
588 m_filters_disabled = true;
589 }
590 }
591
HasActiveLocalFilters() const592 bool CFilterManager::HasActiveLocalFilters() const
593 {
594 if (!m_filters_disabled) {
595 CFilterSet const& set = global_filters_.filter_sets[global_filters_.current_filter_set];
596 // Check active filters
597 for (unsigned int i = 0; i < global_filters_.filters.size(); ++i) {
598 if (set.local[i]) {
599 return true;
600 }
601 }
602 }
603
604 return false;
605 }
606
HasActiveRemoteFilters() const607 bool CFilterManager::HasActiveRemoteFilters() const
608 {
609 if (!m_filters_disabled) {
610 CFilterSet const& set = global_filters_.filter_sets[global_filters_.current_filter_set];
611 // Check active filters
612 for (unsigned int i = 0; i < global_filters_.filters.size(); ++i) {
613 if (set.remote[i]) {
614 return true;
615 }
616 }
617 }
618
619 return false;
620 }
621
GetActiveFilters()622 ActiveFilters CFilterManager::GetActiveFilters()
623 {
624 ActiveFilters filters;
625
626 if (m_filters_disabled) {
627 return filters;
628 }
629
630 const CFilterSet& set = global_filters_.filter_sets[global_filters_.current_filter_set];
631
632 // Check active filters
633 for (unsigned int i = 0; i < global_filters_.filters.size(); ++i) {
634 if (set.local[i]) {
635 filters.first.push_back(global_filters_.filters[i]);
636 }
637 if (set.remote[i]) {
638 filters.second.push_back(global_filters_.filters[i]);
639 }
640 }
641
642 return filters;
643 }
644