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