1 #include "filezilla.h"
2 #include "filter_conditions_dialog.h"
3 #include "customheightlistctrl.h"
4 #include "textctrlex.h"
5
6 static wxArrayString stringConditionTypes;
7 static wxArrayString sizeConditionTypes;
8 static wxArrayString attributeConditionTypes;
9 static wxArrayString permissionConditionTypes;
10 static wxArrayString attributeSetTypes;
11 static wxArrayString dateConditionTypes;
12
CreateChoice(wxWindow * parent,const wxArrayString & items,wxSize const & size=wxDefaultSize)13 static std::unique_ptr<wxChoice> CreateChoice(wxWindow* parent, const wxArrayString& items, wxSize const& size = wxDefaultSize)
14 {
15 #ifdef __WXGTK__
16 // Really obscure bug in wxGTK: If creating in a single step,
17 // first item in the choice sometimes looks disabled
18 // even though it can still be selected and returns to looking
19 // normal after hovering mouse over it.
20 // This works around it nicely.
21 auto ret = std::make_unique<wxChoice>();
22 ret->Create(parent, wxID_ANY, wxDefaultPosition, size);
23 ret->Append(items);
24 ret->InvalidateBestSize();
25 ret->SetInitialSize();
26 return ret;
27 #else
28 return std::make_unique<wxChoice>(parent, wxID_ANY, wxDefaultPosition, size, items);
29 #endif
30 }
31
CFilterControls()32 CFilterControls::CFilterControls()
33 {
34 sizer = std::make_unique<wxBoxSizer>(wxHORIZONTAL);
35 }
36
BEGIN_EVENT_TABLE(CFilterConditionsDialog,wxDialogEx)37 BEGIN_EVENT_TABLE(CFilterConditionsDialog, wxDialogEx)
38 EVT_CHOICE(wxID_ANY, CFilterConditionsDialog::OnFilterTypeChange)
39 EVT_LISTBOX(wxID_ANY, CFilterConditionsDialog::OnConditionSelectionChange)
40 END_EVENT_TABLE()
41
42 CFilterConditionsDialog::CFilterConditionsDialog()
43 {
44 m_choiceBoxHeight = 0;
45 m_pListCtrl = 0;
46 m_has_foreign_type = false;
47 m_button_size = wxSize(-1, -1);
48 }
49
CreateListControl(int conditions)50 bool CFilterConditionsDialog::CreateListControl(int conditions)
51 {
52 m_pListCtrl = XRCCTRL(*this, "ID_CONDITIONS", wxCustomHeightListCtrl);
53 if (!m_pListCtrl) {
54 return false;
55 }
56 m_pListCtrl->AllowSelection(false);
57
58 CalcMinListWidth();
59
60 if (stringConditionTypes.empty()) {
61 stringConditionTypes.Add(_("contains"));
62 stringConditionTypes.Add(_("is equal to"));
63 stringConditionTypes.Add(_("begins with"));
64 stringConditionTypes.Add(_("ends with"));
65 stringConditionTypes.Add(_("matches regex"));
66 stringConditionTypes.Add(_("does not contain"));
67
68 sizeConditionTypes.Add(_("greater than"));
69 sizeConditionTypes.Add(_("equals"));
70 sizeConditionTypes.Add(_("does not equal"));
71 sizeConditionTypes.Add(_("less than"));
72
73 attributeSetTypes.Add(_("is set"));
74 attributeSetTypes.Add(_("is unset"));
75
76 attributeConditionTypes.Add(_("Archive"));
77 attributeConditionTypes.Add(_("Compressed"));
78 attributeConditionTypes.Add(_("Encrypted"));
79 attributeConditionTypes.Add(_("Hidden"));
80 attributeConditionTypes.Add(_("Read-only"));
81 attributeConditionTypes.Add(_("System"));
82
83 permissionConditionTypes.Add(_("owner readable"));
84 permissionConditionTypes.Add(_("owner writeable"));
85 permissionConditionTypes.Add(_("owner executable"));
86 permissionConditionTypes.Add(_("group readable"));
87 permissionConditionTypes.Add(_("group writeable"));
88 permissionConditionTypes.Add(_("group executable"));
89 permissionConditionTypes.Add(_("world readable"));
90 permissionConditionTypes.Add(_("world writeable"));
91 permissionConditionTypes.Add(_("world executable"));
92
93 dateConditionTypes.Add(_("before"));
94 dateConditionTypes.Add(_("equals"));
95 dateConditionTypes.Add(_("does not equal"));
96 dateConditionTypes.Add(_("after"));
97 }
98
99 if (conditions & filter_name) {
100 filterTypes.Add(_("Filename"));
101 filter_type_map.push_back(filter_name);
102 }
103 if (conditions & filter_size) {
104 filterTypes.Add(_("Filesize"));
105 filter_type_map.push_back(filter_size);
106 }
107 if (conditions & filter_attributes) {
108 filterTypes.Add(_("Attribute"));
109 filter_type_map.push_back(filter_attributes);
110 }
111 if (conditions & filter_permissions) {
112 filterTypes.Add(_("Permission"));
113 filter_type_map.push_back(filter_permissions);
114 }
115 if (conditions & filter_path) {
116 filterTypes.Add(_("Path"));
117 filter_type_map.push_back(filter_path);
118 }
119 if (conditions & filter_date) {
120 filterTypes.Add(_("Date"));
121 filter_type_map.push_back(filter_date);
122 }
123
124 SetFilterCtrlState(true);
125
126 return true;
127 }
128
CalcMinListWidth()129 void CFilterConditionsDialog::CalcMinListWidth()
130 {
131 wxChoice *pType = new wxChoice(m_pListCtrl, wxID_ANY, wxDefaultPosition, wxDefaultSize, filterTypes);
132 int requiredWidth = pType->GetBestSize().GetWidth();
133 pType->Destroy();
134
135 wxChoice *pStringCondition = new wxChoice(m_pListCtrl, wxID_ANY, wxDefaultPosition, wxDefaultSize, stringConditionTypes);
136 wxChoice *pSizeCondition = new wxChoice(m_pListCtrl, wxID_ANY, wxDefaultPosition, wxDefaultSize, sizeConditionTypes);
137 wxStaticText *pSizeLabel = new wxStaticText(m_pListCtrl, wxID_ANY, _("bytes"));
138 wxChoice *pDateCondition = new wxChoice(m_pListCtrl, wxID_ANY, wxDefaultPosition, wxDefaultSize, dateConditionTypes);
139
140 int w = wxMax(pStringCondition->GetBestSize().GetWidth(), pSizeCondition->GetBestSize().GetWidth() + 5 + pSizeLabel->GetBestSize().GetWidth());
141 w = wxMax(w, pDateCondition->GetBestSize().GetWidth());
142 requiredWidth += w;
143
144 m_size_label_size = pSizeLabel->GetBestSize();
145
146 pStringCondition->Destroy();
147 pSizeCondition->Destroy();
148 pSizeLabel->Destroy();
149 pDateCondition->Destroy();
150
151 requiredWidth += m_pListCtrl->GetWindowBorderSize().x;
152 requiredWidth += 40;
153 requiredWidth += 120;
154 wxSize minSize = m_pListCtrl->GetMinSize();
155 minSize.IncTo(wxSize(requiredWidth, -1));
156 m_pListCtrl->SetMinSize(minSize);
157
158 m_lastListSize = m_pListCtrl->GetClientSize();
159 }
160
GetTypeFromTypeSelection(int selection)161 t_filterType CFilterConditionsDialog::GetTypeFromTypeSelection(int selection)
162 {
163 if (selection < 0 || selection >(int)filter_type_map.size()) {
164 selection = 0;
165 }
166
167 return filter_type_map[selection];
168 }
169
SetSelectionFromType(wxChoice * pChoice,t_filterType type)170 void CFilterConditionsDialog::SetSelectionFromType(wxChoice* pChoice, t_filterType type)
171 {
172 for (unsigned int i = 0; i < filter_type_map.size(); ++i) {
173 if (filter_type_map[i] == type) {
174 pChoice->SetSelection(i);
175 return;
176 }
177 }
178
179 pChoice->SetSelection(0);
180 }
181
OnMore()182 void CFilterConditionsDialog::OnMore()
183 {
184 if (m_filterControls.size() > 1000) {
185 return;
186 }
187
188 CFilterCondition cond;
189 m_currentFilter.filters.push_back(cond);
190
191 size_t newRowIndex = m_filterControls.size() - 1;
192
193 m_filterControls.insert(m_filterControls.begin() + newRowIndex, CFilterControls());
194 MakeControls(cond, newRowIndex);
195
196 CFilterControls& controls = m_filterControls[newRowIndex];
197 if (m_filterControls.back().pRemove) {
198 m_filterControls.back().pRemove->MoveAfterInTabOrder(controls.pRemove.get());
199 }
200
201 m_pListCtrl->InsertRow(m_filterControls[newRowIndex].sizer.get(), newRowIndex);
202 }
203
OnRemove(size_t item)204 void CFilterConditionsDialog::OnRemove(size_t item)
205 {
206 if (item + 1 >= m_filterControls.size()) {
207 return;
208 }
209
210 m_pListCtrl->DeleteRow(item);
211 m_filterControls.erase(m_filterControls.begin() + item);
212 m_currentFilter.filters.erase(m_currentFilter.filters.begin() + item);
213
214 if (m_currentFilter.filters.empty()) {
215 OnMore();
216 }
217 }
218
OnFilterTypeChange(wxCommandEvent & event)219 void CFilterConditionsDialog::OnFilterTypeChange(wxCommandEvent& event)
220 {
221 size_t item;
222 for (item = 0; item < m_filterControls.size(); ++item) {
223 if (m_filterControls[item].pType && m_filterControls[item].pType->GetId() == event.GetId()) {
224 break;
225 }
226 }
227 if (item == m_filterControls.size()) {
228 return;
229 }
230
231 CFilterCondition& filter = m_currentFilter.filters[item];
232
233 t_filterType type = GetTypeFromTypeSelection(event.GetSelection());
234 if (type == filter.type) {
235 return;
236 }
237 filter.type = type;
238
239 if (filter.type == filter_size && filter.condition > 3) {
240 filter.condition = 0;
241 }
242 else if (filter.type == filter_date && filter.condition > 3) {
243 filter.condition = 0;
244 }
245
246 UpdateControls(filter, item);
247 }
248
MakeControls(CFilterCondition const & condition,size_t i)249 void CFilterConditionsDialog::MakeControls(CFilterCondition const& condition, size_t i)
250 {
251 CFilterControls& controls = m_filterControls[i];
252
253 if (!controls.pType) {
254 controls.pType = CreateChoice(m_pListCtrl, wxArrayString());
255 controls.pType->Set(filterTypes);
256 controls.sizer->Add(controls.pType.get(), 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 5);
257 }
258
259 if (!m_choiceBoxHeight) {
260 wxSize size = controls.pType->GetSize();
261 m_choiceBoxHeight = size.GetHeight();
262 m_pListCtrl->SetLineHeight(m_choiceBoxHeight + 6);
263 }
264
265 if (!controls.pCondition) {
266 controls.pCondition = CreateChoice(m_pListCtrl, wxArrayString());
267 controls.sizer->Add(controls.pCondition.get(), 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 5);
268 }
269
270 if (!controls.pValue) {
271 controls.pValue = std::make_unique<wxTextCtrlEx>();
272 controls.pValue->Create(m_pListCtrl, wxID_ANY, wxString());
273 controls.pValue->Hide();
274 controls.sizer->Add(controls.pValue.get(), 1, wxALIGN_CENTER_VERTICAL | wxLEFT, 5);
275 }
276
277 if (!controls.pSet) {
278 controls.pSet = CreateChoice(m_pListCtrl, wxArrayString());
279 controls.pSet->Set(attributeSetTypes);
280 controls.pSet->Hide();
281 controls.sizer->Add(controls.pSet.get(), 1, wxALIGN_CENTER_VERTICAL | wxLEFT, 5);
282 }
283
284 if (!controls.pLabel) {
285 controls.pLabel = std::make_unique<wxStaticText>();
286 controls.pLabel->Hide();
287 controls.pLabel->Create(m_pListCtrl, wxID_ANY, _("bytes"), wxDefaultPosition, m_size_label_size);
288 controls.sizer->Add(controls.pLabel.get(), 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 5);
289 }
290
291 if (!controls.pRemove) {
292 controls.pRemove = std::make_unique<wxButton>(m_pListCtrl, wxID_ANY, _T("-"), wxDefaultPosition, m_button_size, wxBU_EXACTFIT);
293 controls.pRemove->Bind(wxEVT_BUTTON, [this](wxEvent const& ev) { OnButton(ev.GetId()); });
294 if (m_button_size.x <= 0) {
295 m_button_size.x = wxMax(m_choiceBoxHeight, controls.pRemove->GetSize().x);
296 m_button_size.y = m_choiceBoxHeight;
297 controls.pRemove->SetSize(m_button_size);
298 }
299 controls.sizer->Add(controls.pRemove.get(), 0, wxALIGN_CENTER_VERTICAL | wxFIXED_MINSIZE | wxLEFT | wxRIGHT, 5);
300 }
301
302 UpdateControls(condition, i);
303 }
304
UpdateControls(CFilterCondition const & condition,size_t i)305 void CFilterConditionsDialog::UpdateControls(CFilterCondition const& condition, size_t i)
306 {
307 CFilterControls& controls = m_filterControls[i];
308
309 SetSelectionFromType(controls.pType.get(), condition.type);
310
311 switch (condition.type)
312 {
313 case filter_name:
314 case filter_path:
315 controls.pCondition->Set(stringConditionTypes);
316 break;
317 case filter_size:
318 controls.pCondition->Set(sizeConditionTypes);
319 break;
320 case filter_attributes:
321 controls.pCondition->Set(attributeConditionTypes);
322 break;
323 case filter_permissions:
324 controls.pCondition->Set(permissionConditionTypes);
325 break;
326 case filter_date:
327 controls.pCondition->Set(dateConditionTypes);
328 break;
329 default:
330 wxFAIL_MSG(_T("Unhandled condition"));
331 return;
332 }
333 controls.pCondition->Select(condition.condition);
334
335 controls.pValue->SetValue(condition.strValue);
336 controls.pSet->Select(condition.strValue != _T("0") ? 0 : 1);
337
338 controls.pValue->Show(condition.type == filter_name || condition.type == filter_size || condition.type == filter_path || condition.type == filter_date);
339 controls.pSet->Show(!controls.pValue->IsShown());
340 controls.pLabel->Show(condition.type == filter_size);
341
342 controls.sizer->Layout();
343 }
344
DestroyControls()345 void CFilterConditionsDialog::DestroyControls()
346 {
347 m_pListCtrl->ClearRows();
348 m_filterControls.clear();
349 }
350
EditFilter(CFilter const & filter)351 void CFilterConditionsDialog::EditFilter(CFilter const& filter)
352 {
353 Freeze();
354 DestroyControls();
355
356 // Create new controls
357 m_currentFilter = filter;
358
359 if (m_currentFilter.filters.empty()) {
360 m_currentFilter.filters.push_back(CFilterCondition());
361 }
362 m_filterControls.resize(m_currentFilter.filters.size() + 1);
363
364 for (unsigned int i = 0; i < m_currentFilter.filters.size(); ++i) {
365 const CFilterCondition& cond = m_currentFilter.filters[i];
366
367 MakeControls(cond, i);
368 m_pListCtrl->InsertRow(m_filterControls[i].sizer.get(), i);
369 }
370
371 CFilterControls & controls = m_filterControls.back();
372 controls.pRemove = std::make_unique<wxButton>(m_pListCtrl, wxID_ANY, _T("+"), wxDefaultPosition, m_button_size);
373 controls.pRemove->Bind(wxEVT_BUTTON, [this](wxEvent const& ev) { OnButton(ev.GetId()); });
374 controls.sizer->AddStretchSpacer();
375 controls.sizer->Add(controls.pRemove.get(), 0, wxALIGN_CENTER_VERTICAL|wxFIXED_MINSIZE|wxRIGHT, 5);
376
377 m_pListCtrl->InsertRow(controls.sizer.get(), m_filterControls.size() - 1);
378
379 XRCCTRL(*this, "ID_MATCHTYPE", wxChoice)->SetSelection(filter.matchType);
380
381 SetFilterCtrlState(false);
382 Thaw();
383 }
384
GetFilter(bool matchCase)385 CFilter CFilterConditionsDialog::GetFilter(bool matchCase)
386 {
387 wxASSERT(m_filterControls.size() >= m_currentFilter.filters.size());
388
389 CFilter filter;
390 for (size_t i = 0; i < m_currentFilter.filters.size(); ++i) {
391 CFilterControls const& controls = m_filterControls[i];
392 if (!controls.pType || !controls.pCondition) {
393 continue;
394 }
395 CFilterCondition condition = m_currentFilter.filters[i];
396
397 std::wstring value;
398 switch (condition.type) {
399 case filter_attributes:
400 case filter_permissions:
401 value = controls.pSet ? fz::to_wstring(controls.pSet->GetSelection()) : std::wstring();
402 break;
403 default:
404 value = controls.pValue ? controls.pValue->GetValue().ToStdWstring() : std::wstring();
405 break;
406 }
407
408 t_filterType const type = GetTypeFromTypeSelection(controls.pType->GetSelection());
409 int const cond = controls.pCondition->GetSelection();
410
411 if (!condition.set(type, value, cond, matchCase)) {
412 continue;
413 }
414
415 filter.filters.push_back(condition);
416 }
417
418 switch (XRCCTRL(*this, "ID_MATCHTYPE", wxChoice)->GetSelection())
419 {
420 case 1:
421 filter.matchType = CFilter::any;
422 break;
423 case 2:
424 filter.matchType = CFilter::none;
425 break;
426 case 3:
427 filter.matchType = CFilter::not_all;
428 break;
429 default:
430 filter.matchType = CFilter::all;
431 break;
432 }
433
434 return filter;
435 }
436
ClearFilter()437 void CFilterConditionsDialog::ClearFilter()
438 {
439 DestroyControls();
440 SetFilterCtrlState(true);
441 }
442
SetFilterCtrlState(bool disable)443 void CFilterConditionsDialog::SetFilterCtrlState(bool disable)
444 {
445 m_pListCtrl->Enable(!disable);
446
447 XRCCTRL(*this, "ID_MATCHTYPE", wxChoice)->Enable(!disable);
448 }
449
ValidateFilter(wxString & error,bool allow_empty)450 bool CFilterConditionsDialog::ValidateFilter(wxString& error, bool allow_empty)
451 {
452 size_t const size = m_currentFilter.filters.size();
453 if (!size) {
454 if (allow_empty) {
455 return true;
456 }
457
458 error = _("Each filter needs at least one condition.");
459 return false;
460 }
461
462 wxASSERT(m_filterControls.size() >= m_currentFilter.filters.size());
463
464 for (unsigned int i = 0; i < size; ++i) {
465 const CFilterControls& controls = m_filterControls[i];
466 t_filterType type = GetTypeFromTypeSelection(controls.pType->GetSelection());
467 int condition = controls.pCondition ? controls.pCondition->GetSelection() : 0;
468 if (!controls.pValue) {
469 continue;
470 }
471
472 if (type == filter_name || type == filter_path) {
473 if (!controls.pValue || controls.pValue->GetValue().empty()) {
474 if (allow_empty) {
475 continue;
476 }
477
478 m_pListCtrl->SelectLine(i);
479 controls.pValue->SetFocus();
480 error = _("At least one filter condition is incomplete");
481 return false;
482 }
483
484 if (condition == 4) {
485 bool valid = false;
486 auto const v = controls.pValue->GetValue().ToStdWstring();
487 if (v.size() <= 2000) {
488 try {
489 std::wregex r(v);
490 (void)r;
491 valid = true;
492 }
493 catch (std::regex_error const&) {
494 }
495 }
496 if (!valid) {
497 m_pListCtrl->SelectLine(i);
498 controls.pValue->SetFocus();
499 error = _("Invalid regular expression");
500 return false;
501 }
502 }
503 }
504 else if (type == filter_size) {
505 const wxString v = controls.pValue->GetValue();
506 if (v.empty() && allow_empty) {
507 continue;
508 }
509
510 long long number;
511 if (!v.ToLongLong(&number) || number < 0) {
512 m_pListCtrl->SelectLine(i);
513 controls.pValue->SetFocus();
514 error = _("Invalid size in condition");
515 return false;
516 }
517 }
518 else if (type == filter_date) {
519 wxString const d = controls.pValue->GetValue();
520 if (d.empty() && allow_empty) {
521 continue;
522 }
523
524 fz::datetime date(d.ToStdWstring(), fz::datetime::local);
525 if (date.empty()) {
526 m_pListCtrl->SelectLine(i);
527 controls.pValue->SetFocus();
528 error = _("Please enter a date of the form YYYY-MM-DD such as for example 2010-07-18.");
529 return false;
530 }
531 }
532 }
533
534 return true;
535 }
536
OnConditionSelectionChange(wxCommandEvent & event)537 void CFilterConditionsDialog::OnConditionSelectionChange(wxCommandEvent& event)
538 {
539 if (event.GetId() != m_pListCtrl->GetId()) {
540 return;
541 }
542 }
543
OnButton(int id)544 void CFilterConditionsDialog::OnButton(int id)
545 {
546 for (size_t i = 0; i < m_filterControls.size(); ++i) {
547 if (m_filterControls[i].pRemove->GetId() == id) {
548 Freeze();
549 if (i + 1 == m_filterControls.size()) {
550 OnMore();
551 }
552 else {
553 OnRemove(i);
554 }
555 Thaw();
556 return;
557 }
558 }
559 }
560