1 #include "filezilla.h"
2 #include "filteredit.h"
3 #include "customheightlistctrl.h"
4 #include "window_state_manager.h"
5 #include "Options.h"
6 #include "textctrlex.h"
7
8 #include <libfilezilla/translate.hpp>
9
BEGIN_EVENT_TABLE(CFilterEditDialog,CFilterConditionsDialog)10 BEGIN_EVENT_TABLE(CFilterEditDialog, CFilterConditionsDialog)
11 EVT_BUTTON(XRCID("wxID_OK"), CFilterEditDialog::OnOK)
12 EVT_BUTTON(XRCID("wxID_CANCEL"), CFilterEditDialog::OnCancel)
13 EVT_BUTTON(XRCID("ID_NEW"), CFilterEditDialog::OnNew)
14 EVT_BUTTON(XRCID("ID_DELETE"), CFilterEditDialog::OnDelete)
15 EVT_BUTTON(XRCID("ID_RENAME"), CFilterEditDialog::OnRename)
16 EVT_BUTTON(XRCID("ID_COPY"), CFilterEditDialog::OnCopy)
17 EVT_LISTBOX(XRCID("ID_FILTERS"), CFilterEditDialog::OnFilterSelect)
18 END_EVENT_TABLE()
19
20 CFilterEditDialog::CFilterEditDialog()
21 {
22 }
23
~CFilterEditDialog()24 CFilterEditDialog::~CFilterEditDialog()
25 {
26 if (m_pWindowStateManager) {
27 m_pWindowStateManager->Remember(OPTION_FILTEREDIT_SIZE);
28 delete m_pWindowStateManager;
29 }
30 }
31
OnOK(wxCommandEvent &)32 void CFilterEditDialog::OnOK(wxCommandEvent&)
33 {
34 if (!Validate()) {
35 return;
36 }
37
38 if (m_currentSelection != -1) {
39 wxASSERT((unsigned int)m_currentSelection < m_filters.size());
40 SaveFilter(m_filters[m_currentSelection]);
41 }
42 for (unsigned int i = 0; i < m_filters.size(); ++i) {
43 if (!m_filters[i].HasConditionOfType(filter_permissions) && !m_filters[i].HasConditionOfType(filter_attributes)) {
44 continue;
45 }
46
47 for (unsigned int j = 0; j < m_filterSets.size(); ++j) {
48 m_filterSets[j].remote[i] = false;
49 }
50 }
51
52 EndModal(wxID_OK);
53 }
54
OnCancel(wxCommandEvent &)55 void CFilterEditDialog::OnCancel(wxCommandEvent&)
56 {
57 EndModal(wxID_CANCEL);
58 }
59
Create(wxWindow * parent,const std::vector<CFilter> & filters,const std::vector<CFilterSet> & filterSets)60 bool CFilterEditDialog::Create(wxWindow* parent, const std::vector<CFilter>& filters, const std::vector<CFilterSet>& filterSets)
61 {
62 bool has_foreign_type = false;
63 for (std::vector<CFilter>::const_iterator iter = filters.begin(); iter != filters.end(); ++iter) {
64 const CFilter& filter = *iter;
65 if (!filter.HasConditionOfType(filter_foreign)) {
66 continue;
67 }
68
69 has_foreign_type = true;
70 break;
71 }
72
73 wxDialogEx::Create(parent, -1, _("Edit filters"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
74
75 auto& lay = layout();
76
77 auto main = lay.createMain(this, 1);
78 main->AddGrowableCol(0);
79 main->AddGrowableRow(0);
80
81 auto sides = lay.createFlex(2);
82 main->Add(sides, lay.grow);
83 sides->AddGrowableRow(0);
84 sides->AddGrowableCol(1);
85
86 {
87 auto left = lay.createFlex(1);
88 sides->Add(left, lay.grow);
89 left->AddGrowableRow(1);
90
91 left->Add(new wxStaticText(this, -1, _("&Filters:")));
92 filterList_ = new wxListBox(this, XRCID("ID_FILTERS"));
93 filterList_->SetFocus();
94 left->Add(filterList_, lay.grow);
95
96 auto grid = lay.createGrid(2, 2);
97 left->Add(grid, 0, wxALIGN_CENTER_HORIZONTAL);
98
99 grid->Add(new wxButton(this, XRCID("ID_NEW"), _("&New")));
100 grid->Add(new wxButton(this, XRCID("ID_DELETE"), _("&Delete")));
101 grid->Add(new wxButton(this, XRCID("ID_RENAME"), _("&Rename")));
102 grid->Add(new wxButton(this, XRCID("ID_COPY"), _("&Duplicate")));
103 }
104 {
105 auto right = lay.createFlex(1);
106 sides->Add(right, lay.grow);
107 right->AddGrowableCol(0);
108 right->AddGrowableRow(2);
109
110 auto row = lay.createFlex(2);
111 right->Add(row, lay.grow);
112 row->AddGrowableCol(1);
113 row->Add(new wxStaticText(this, -1, _("F&ilter name:")), lay.valign);
114 row->Add(new wxTextCtrlEx(this, XRCID("ID_NAME"), wxString()), lay.valigng);
115
116 row = lay.createFlex(2);
117 right->Add(row, lay.grow);
118 row->AddGrowableCol(1);
119 row->Add(new wxStaticText(this, -1, _("&Filter conditions:")), lay.valign);
120 auto choice = new wxChoice(this, XRCID("ID_MATCHTYPE"));
121 row->Add(choice, lay.valigng);
122
123 choice->AppendString(_("Filter out items matching all of the following"));
124 choice->AppendString(_("Filter out items matching any of the following"));
125 choice->AppendString(_("Filter out items matching none of the following"));
126 choice->AppendString(_("Filter out items matching not all of the following"));
127
128 auto conditions = new wxCustomHeightListCtrl(this, XRCID("ID_CONDITIONS"), wxDefaultPosition, wxDefaultSize, wxVSCROLL | wxSUNKEN_BORDER | wxTAB_TRAVERSAL);
129 conditions->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
130 right->Add(conditions, lay.grow)->SetMinSize(wxSize(lay.dlgUnits(200), lay.dlgUnits(100)));
131
132 right->Add(new wxCheckBox(this, XRCID("ID_CASE"), _("Conditions are c&ase sensitive")));
133 row = lay.createFlex(0, 1);
134 right->Add(row);
135 row->Add(new wxStaticText(this, -1, _("Filter applies to:")));
136 row->Add(new wxCheckBox(this, XRCID("ID_FILES"), _("Fil&es")));
137 row->Add(new wxCheckBox(this, XRCID("ID_DIRS"), _("Dire&ctories")));
138 }
139 auto buttons = lay.createButtonSizer(this, main, false);
140 auto ok = new wxButton(this, wxID_OK, _("OK"));
141 ok->SetDefault();
142 buttons->AddButton(ok);
143 auto cancel = new wxButton(this, wxID_CANCEL, _("Cancel"));
144 buttons->AddButton(cancel);
145 buttons->Realize();
146
147 Layout();
148 GetSizer()->Fit(this);
149 SetMinSize(GetSize());
150
151 int conditions = filter_name | filter_size | filter_path | filter_meta | filter_date;
152 if (has_foreign_type) {
153 conditions |= filter_foreign;
154 }
155 if (!CreateListControl(conditions)) {
156 return false;
157 }
158
159 m_filters = filters;
160 m_filterSets = filterSets;
161 for (auto const& filter: filters) {
162 filterList_->Append(filter.name);
163 }
164
165 m_pWindowStateManager = new CWindowStateManager(this);
166 m_pWindowStateManager->Restore(OPTION_FILTEREDIT_SIZE, wxSize(750, 500));
167
168 Layout();
169
170 SetCtrlState(false);
171
172 return true;
173 }
174
SaveFilter(CFilter & filter)175 void CFilterEditDialog::SaveFilter(CFilter& filter)
176 {
177 bool const matchCase = XRCCTRL(*this, "ID_CASE", wxCheckBox)->GetValue();
178 filter = GetFilter(matchCase);
179 filter.matchCase = matchCase;
180
181 filter.filterFiles = XRCCTRL(*this, "ID_FILES", wxCheckBox)->GetValue();
182 filter.filterDirs = XRCCTRL(*this, "ID_DIRS", wxCheckBox)->GetValue();
183
184 filter.name = XRCCTRL(*this, "ID_NAME", wxTextCtrl)->GetValue().ToStdWstring();
185 if (filter.name != filterList_->GetString(m_currentSelection)) {
186 int oldSelection = m_currentSelection;
187 filterList_->Delete(oldSelection);
188 filterList_->Insert(filter.name, oldSelection);
189 filterList_->SetSelection(oldSelection);
190 }
191 }
192
OnNew(wxCommandEvent &)193 void CFilterEditDialog::OnNew(wxCommandEvent&)
194 {
195 if (m_currentSelection != -1) {
196 if (!Validate()) {
197 return;
198 }
199 SaveFilter(m_filters[m_currentSelection]);
200 }
201
202 int index = 1;
203 std::wstring name = fztranslate("New filter");
204 std::wstring newName = name;
205 while (filterList_->FindString(newName) != wxNOT_FOUND) {
206 newName = fz::sprintf(L"%s (%d)", name, ++index);
207 }
208
209 wxTextEntryDialog dlg(this, _("Please enter a name for the new filter."), _("Enter filter name"), newName);
210 if (dlg.ShowModal() != wxID_OK) {
211 return;
212 }
213 newName = dlg.GetValue().ToStdWstring();
214
215 if (newName.empty()) {
216 wxMessageBoxEx(_("No filter name given"), _("Cannot create new filter"), wxICON_INFORMATION);
217 return;
218 }
219
220 if (filterList_->FindString(newName) != wxNOT_FOUND) {
221 wxMessageBoxEx(_("The entered filter name already exists, please choose a different name."), _("Filter name already exists"), wxICON_ERROR, this);
222 return;
223 }
224
225 CFilter filter;
226 filter.name = newName;
227
228 m_filters.push_back(filter);
229
230 for (auto iter = m_filterSets.begin(); iter != m_filterSets.end(); ++iter) {
231 CFilterSet& set = *iter;
232 set.local.push_back(false);
233 set.remote.push_back(false);
234 }
235
236 int item = filterList_->Append(newName);
237 filterList_->Select(item);
238 wxCommandEvent evt;
239 OnFilterSelect(evt);
240 }
241
OnDelete(wxCommandEvent &)242 void CFilterEditDialog::OnDelete(wxCommandEvent&)
243 {
244 int item = filterList_->GetSelection();
245 if (item == -1) {
246 return;
247 }
248
249 m_currentSelection = -1;
250 filterList_->Delete(item);
251 m_filters.erase(m_filters.begin() + item);
252
253 // Remote filter from all filter sets
254 for (auto & set : m_filterSets) {
255 set.local.erase(set.local.begin() + item);
256 set.remote.erase(set.remote.begin() + item);
257 }
258
259 XRCCTRL(*this, "ID_NAME", wxTextCtrl)->ChangeValue(wxString());
260 ClearFilter();
261 SetCtrlState(false);
262 }
263
OnRename(wxCommandEvent &)264 void CFilterEditDialog::OnRename(wxCommandEvent&)
265 {
266 if (m_currentSelection == -1) {
267 wxBell();
268 return;
269 }
270
271 const wxString& oldName = XRCCTRL(*this, "ID_NAME", wxTextCtrl)->GetValue();
272 wxTextEntryDialog *pDlg = new wxTextEntryDialog(this, _("Please enter a new name for the filter."), _("Enter filter name"), oldName);
273 pDlg->SetMaxLength(255);
274 if (pDlg->ShowModal() != wxID_OK) {
275 delete pDlg;
276 return;
277 }
278
279 const wxString& newName = pDlg->GetValue();
280 delete pDlg;
281
282 if (newName.empty()) {
283 wxMessageBoxEx(_("Empty filter names are not allowed."), _("Empty name"), wxICON_ERROR, this);
284 return;
285 }
286
287 if (newName == oldName) {
288 return;
289 }
290
291 if (filterList_->FindString(newName) != wxNOT_FOUND) {
292 wxMessageBoxEx(_("The entered filter name already exists, please choose a different name."), _("Filter name already exists"), wxICON_ERROR, this);
293 return;
294 }
295
296 filterList_->Delete(m_currentSelection);
297 filterList_->Insert(newName, m_currentSelection);
298 filterList_->Select(m_currentSelection);
299 }
300
OnCopy(wxCommandEvent &)301 void CFilterEditDialog::OnCopy(wxCommandEvent&)
302 {
303 if (m_currentSelection == -1) {
304 return;
305 }
306
307 if (!Validate()) {
308 return;
309 }
310 SaveFilter(m_filters[m_currentSelection]);
311
312 CFilter filter = m_filters[m_currentSelection];
313
314 int index = 1;
315 std::wstring const& name = filter.name;
316 std::wstring newName = name;
317 while (filterList_->FindString(newName) != wxNOT_FOUND) {
318 newName = fz::sprintf(L"%s (%d)", name, ++index);
319 }
320
321 wxTextEntryDialog dlg(this, _("Please enter a new name for the copied filter."), _("Enter filter name"), newName);
322 if (dlg.ShowModal() != wxID_OK) {
323 return;
324 }
325
326 newName = dlg.GetValue().ToStdWstring();
327 if (newName.empty()) {
328 wxMessageBoxEx(_("Empty filter names are not allowed."), _("Empty name"), wxICON_ERROR, this);
329 return;
330 }
331
332 if (filterList_->FindString(newName) != wxNOT_FOUND) {
333 wxMessageBoxEx(_("The entered filter name already exists, please choose a different name."), _("Filter name already exists"), wxICON_ERROR, this);
334 return;
335 }
336
337 filter.name = newName;
338
339 m_filters.push_back(filter);
340
341 for (auto iter = m_filterSets.begin(); iter != m_filterSets.end(); ++iter) {
342 CFilterSet& set = *iter;
343 set.local.push_back(false);
344 set.remote.push_back(false);
345 }
346
347 int item = filterList_->Append(newName);
348 filterList_->Select(item);
349 wxCommandEvent evt;
350 OnFilterSelect(evt);
351 }
352
OnFilterSelect(wxCommandEvent &)353 void CFilterEditDialog::OnFilterSelect(wxCommandEvent&)
354 {
355 int item = filterList_->GetSelection();
356 if (item == -1) {
357 m_currentSelection = -1;
358 SetCtrlState(false);
359 return;
360 }
361 else {
362 SetCtrlState(true);
363 }
364
365 if (item == m_currentSelection) {
366 return;
367 }
368
369 if (m_currentSelection != -1) {
370 wxASSERT((unsigned int)m_currentSelection < m_filters.size());
371
372 if (!Validate()) {
373 return;
374 }
375
376 SaveFilter(m_filters[m_currentSelection]);
377 }
378
379 m_currentSelection = item;
380 filterList_->SetSelection(item); // In case SaveFilter has renamed an item
381 CFilter filter = m_filters[item];
382 EditFilter(filter);
383
384 XRCCTRL(*this, "ID_CASE", wxCheckBox)->SetValue(filter.matchCase);
385
386 XRCCTRL(*this, "ID_FILES", wxCheckBox)->SetValue(filter.filterFiles);
387 XRCCTRL(*this, "ID_DIRS", wxCheckBox)->SetValue(filter.filterDirs);
388
389 XRCCTRL(*this, "ID_NAME", wxTextCtrl)->SetValue(filter.name);
390 }
391
SetCtrlState(bool enabled)392 void CFilterEditDialog::SetCtrlState(bool enabled)
393 {
394 XRCCTRL(*this, "ID_CASE", wxCheckBox)->Enable(enabled);
395 XRCCTRL(*this, "ID_FILES", wxCheckBox)->Enable(enabled);
396 XRCCTRL(*this, "ID_DIRS", wxCheckBox)->Enable(enabled);
397 }
398
GetFilters() const399 const std::vector<CFilter>& CFilterEditDialog::GetFilters() const
400 {
401 return m_filters;
402 }
403
GetFilterSets() const404 const std::vector<CFilterSet>& CFilterEditDialog::GetFilterSets() const
405 {
406 return m_filterSets;
407 }
408
Validate()409 bool CFilterEditDialog::Validate()
410 {
411 if (m_currentSelection == -1) {
412 return true;
413 }
414
415 wxString error;
416 if (!ValidateFilter(error)) {
417 filterList_->SetSelection(m_currentSelection);
418 wxMessageBoxEx(error, _("Filter validation failed"), wxICON_ERROR, this);
419 return false;
420 }
421
422 wxString name = XRCCTRL(*this, "ID_NAME", wxTextCtrl)->GetValue();
423 if (name.empty()) {
424 filterList_->SetSelection(m_currentSelection);
425 XRCCTRL(*this, "ID_NAME", wxTextCtrl)->SetFocus();
426 wxMessageBoxEx(_("Need to enter filter name"), _("Filter validation failed"), wxICON_ERROR, this);
427 return false;
428 }
429
430 int pos = filterList_->FindString(name);
431 if (pos != wxNOT_FOUND && pos != m_currentSelection) {
432 filterList_->SetSelection(m_currentSelection);
433 XRCCTRL(*this, "ID_NAME", wxTextCtrl)->SetFocus();
434 wxMessageBoxEx(_("Filter name already exists"), _("Filter validation failed"), wxICON_ERROR, this);
435 return false;
436 }
437
438 return true;
439 }
440