1 #include "FilterDialog.h"
2 
3 #include "radiant_i18n.h"
4 #include "ifilter.h"
5 #include "iradiant.h"
6 #include "gtkutil/TextColumn.h"
7 #include "gtkutil/TreeModel.h"
8 #include "gtkutil/ScrolledFrame.h"
9 #include "gtkutil/RightAlignment.h"
10 #include "gtkutil/LeftAlignment.h"
11 #include "gtkutil/LeftAlignedLabel.h"
12 #include <gtk/gtk.h>
13 #include "../menu/FiltersMenu.h"
14 
15 #include "FilterEditor.h"
16 
17 namespace ui {
18 
19 namespace {
20 const int DEFAULT_SIZE_X = 600;
21 const int DEFAULT_SIZE_Y = 550;
22 const char* const WINDOW_TITLE = N_("Filter Settings");
23 
24 enum
25 {
26 	WIDGET_ADD_FILTER_BUTTON, WIDGET_EDIT_FILTER_BUTTON, WIDGET_VIEW_FILTER_BUTTON, WIDGET_DELETE_FILTER_BUTTON
27 };
28 
29 enum
30 {
31 	COL_NAME, COL_STATE, COL_COLOUR, COL_READONLY, NUM_COLUMNS
32 };
33 }
34 
FilterDialog()35 FilterDialog::FilterDialog () :
36 	BlockingTransientWindow(_(WINDOW_TITLE), GlobalRadiant().getMainWindow()), _filterStore(gtk_list_store_new(
37 			NUM_COLUMNS, G_TYPE_STRING, // name
38 			G_TYPE_STRING, // state
39 			G_TYPE_STRING, // colour
40 			G_TYPE_BOOLEAN)) // read-only
41 {
42 	gtk_window_set_default_size(GTK_WINDOW(getWindow()), DEFAULT_SIZE_X, DEFAULT_SIZE_Y);
43 	gtk_container_set_border_width(GTK_CONTAINER(getWindow()), 12);
44 	gtk_window_set_type_hint(GTK_WINDOW(getWindow()), GDK_WINDOW_TYPE_HINT_DIALOG);
45 
46 	// Create the child widgets
47 	populateWindow();
48 
49 	// Load the filters from the filtersystem
50 	loadFilters();
51 
52 	// Refresh dialog contents
53 	update();
54 
55 	// Show the window and its children, enter the main loop
56 	show();
57 }
58 
save()59 void FilterDialog::save ()
60 {
61 	// Delete filters marked for removal
62 	for (FilterMap::const_iterator i = _deletedFilters.begin(); i != _deletedFilters.end(); ++i) {
63 		GlobalFilterSystem().removeFilter(i->first);
64 	}
65 
66 	// Save all remaining filters
67 	for (FilterMap::const_iterator i = _filters.begin(); i != _filters.end(); ++i) {
68 		// Check if the name has changed (or a new filters has been defined)
69 		if (i->second->nameHasChanged()) {
70 			// New filters have their original name set to the empty string
71 			if (i->second->getOriginalName().empty()) {
72 				// Insert a new filter
73 				GlobalFilterSystem().addFilter(i->second->name, i->second->rules);
74 			} else {
75 				// Existing filer, issue the rename command
76 				GlobalFilterSystem().renameFilter(i->second->getOriginalName(), i->second->name);
77 			}
78 		}
79 
80 		// Save the ruleset (to the new name, in case the filter has been renamed)
81 		GlobalFilterSystem().setFilterRules(i->first, i->second->rules);
82 	}
83 
84 	// Re-build the filters menu
85 	ui::FiltersMenu::addItemsToMainMenu();
86 }
87 
loadFilters()88 void FilterDialog::loadFilters ()
89 {
90 	// Clear first, before population
91 	_filters.clear();
92 
93 	// Local helper class to populate the map
94 	class FilterMapPopulator: public IFilterVisitor
95 	{
96 			FilterMap& _target;
97 		public:
98 			FilterMapPopulator (FilterMap& target) :
99 				_target(target)
100 			{
101 			}
102 
103 			void visit (const std::string& filterName)
104 			{
105 				// Get the properties
106 				bool state = GlobalFilterSystem().getFilterState(filterName);
107 				bool readOnly = GlobalFilterSystem().filterIsReadOnly(filterName);
108 
109 				std::pair<FilterMap::iterator, bool> result = _target.insert(FilterMap::value_type(filterName,
110 						new Filter(filterName, state, readOnly)));
111 
112 				// Copy the ruleset from the given filter
113 				result.first->second->rules = GlobalFilterSystem().getRuleSet(filterName);
114 			}
115 
116 	} populator(_filters);
117 
118 	GlobalFilterSystem().forEachFilter(populator);
119 }
120 
update()121 void FilterDialog::update ()
122 {
123 	// Clear the store first
124 	gtk_list_store_clear(_filterStore);
125 
126 	for (FilterMap::const_iterator i = _filters.begin(); i != _filters.end(); ++i) {
127 		GtkTreeIter iter;
128 
129 		// Allocate a new list store element and store its pointer into <iter>
130 		gtk_list_store_append(_filterStore, &iter);
131 
132 		const Filter& filter = *(i->second);
133 
134 		gtk_list_store_set(_filterStore, &iter, COL_NAME, i->first.c_str(), COL_STATE, filter.state ? _("enabled")
135 				: _("disabled"), COL_COLOUR, filter.readOnly ? "#707070" : "black", COL_READONLY,
136 				filter.readOnly ? TRUE : FALSE, -1);
137 	}
138 
139 	// Update the button sensitivity
140 	updateWidgetSensitivity();
141 }
142 
populateWindow()143 void FilterDialog::populateWindow ()
144 {
145 	// Create the dialog vbox
146 	GtkWidget* vbox = gtk_vbox_new(FALSE, 6);
147 
148 	// Create the "Filters" label
149 	gtk_box_pack_start(GTK_BOX(vbox), gtkutil::LeftAlignedLabel(std::string("<b>") + _("Filters") + "</b>"), FALSE,
150 			FALSE, 0);
151 
152 	// Pack the treeview into the main window's vbox
153 	gtk_box_pack_start(GTK_BOX(vbox), createFiltersPanel(), TRUE, TRUE, 0);
154 
155 	// Buttons
156 	gtk_box_pack_start(GTK_BOX(vbox), createButtonPanel(), FALSE, FALSE, 0);
157 
158 	gtk_container_add(GTK_CONTAINER(getWindow()), GTK_WIDGET(vbox));
159 }
160 
createFiltersPanel()161 GtkWidget* FilterDialog::createFiltersPanel ()
162 {
163 	// Create an hbox for the treeview and the action buttons
164 	GtkWidget* hbox = gtk_hbox_new(FALSE, 6);
165 
166 	// Create a new treeview
167 	_filterView = GTK_TREE_VIEW(gtk_tree_view_new_with_model(GTK_TREE_MODEL(_filterStore)));
168 
169 	gtkutil::TextColumn filterCol(_("Name"), COL_NAME);
170 	gtkutil::TextColumn stateCol(_("State"), COL_STATE);
171 
172 	gtk_tree_view_column_set_attributes(filterCol, GTK_CELL_RENDERER(filterCol.getCellRenderer()), "markup", COL_NAME,
173 			"foreground", COL_COLOUR, NULL);
174 
175 	gtk_tree_view_column_set_attributes(stateCol, GTK_CELL_RENDERER(stateCol.getCellRenderer()), "markup", COL_STATE,
176 			"foreground", COL_COLOUR, NULL);
177 
178 	gtk_tree_view_append_column(GTK_TREE_VIEW(_filterView), filterCol);
179 	gtk_tree_view_append_column(GTK_TREE_VIEW(_filterView), stateCol);
180 
181 	GtkTreeSelection* sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(_filterView));
182 	g_signal_connect(G_OBJECT(sel), "changed", G_CALLBACK(onFilterSelectionChanged), this);
183 
184 	// Action buttons
185 	_widgets[WIDGET_ADD_FILTER_BUTTON] = gtk_button_new_from_stock(GTK_STOCK_ADD);
186 	_widgets[WIDGET_EDIT_FILTER_BUTTON] = gtk_button_new_from_stock(GTK_STOCK_EDIT);
187 	_widgets[WIDGET_VIEW_FILTER_BUTTON] = gtk_button_new_with_label(_("View"));
188 
189 	_widgets[WIDGET_DELETE_FILTER_BUTTON] = gtk_button_new_from_stock(GTK_STOCK_DELETE);
190 
191 	g_signal_connect(G_OBJECT(_widgets[WIDGET_ADD_FILTER_BUTTON]), "clicked", G_CALLBACK(onAddFilter), this);
192 	g_signal_connect(G_OBJECT(_widgets[WIDGET_EDIT_FILTER_BUTTON]), "clicked", G_CALLBACK(onEditFilter), this);
193 	g_signal_connect(G_OBJECT(_widgets[WIDGET_VIEW_FILTER_BUTTON]), "clicked", G_CALLBACK(onViewFilter), this);
194 	g_signal_connect(G_OBJECT(_widgets[WIDGET_DELETE_FILTER_BUTTON]), "clicked", G_CALLBACK(onDeleteFilter), this);
195 
196 	GtkWidget* actionVBox = gtk_vbox_new(FALSE, 6);
197 
198 	gtk_box_pack_start(GTK_BOX(actionVBox), _widgets[WIDGET_ADD_FILTER_BUTTON], FALSE, FALSE, 0);
199 	gtk_box_pack_start(GTK_BOX(actionVBox), _widgets[WIDGET_EDIT_FILTER_BUTTON], FALSE, FALSE, 0);
200 	gtk_box_pack_start(GTK_BOX(actionVBox), _widgets[WIDGET_VIEW_FILTER_BUTTON], FALSE, FALSE, 0);
201 	gtk_box_pack_start(GTK_BOX(actionVBox), _widgets[WIDGET_DELETE_FILTER_BUTTON], FALSE, FALSE, 0);
202 
203 	gtk_box_pack_start(GTK_BOX(hbox), gtkutil::ScrolledFrame(GTK_WIDGET(_filterView)), TRUE, TRUE, 0);
204 	gtk_box_pack_start(GTK_BOX(hbox), actionVBox, FALSE, FALSE, 0);
205 
206 	return gtkutil::LeftAlignment(hbox, 18, 1);
207 }
208 
createButtonPanel()209 GtkWidget* FilterDialog::createButtonPanel ()
210 {
211 	GtkWidget* buttonHBox = gtk_hbox_new(TRUE, 12);
212 
213 	// Save button
214 	GtkWidget* okButton = gtk_button_new_from_stock(GTK_STOCK_OK);
215 	g_signal_connect(G_OBJECT(okButton), "clicked", G_CALLBACK(onSave), this);
216 	gtk_box_pack_end(GTK_BOX(buttonHBox), okButton, TRUE, TRUE, 0);
217 
218 	// Cancel Button
219 	GtkWidget* cancelButton = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
220 	g_signal_connect(G_OBJECT(cancelButton), "clicked", G_CALLBACK(onCancel), this);
221 	gtk_box_pack_end(GTK_BOX(buttonHBox), cancelButton, TRUE, TRUE, 0);
222 
223 	return gtkutil::RightAlignment(buttonHBox);
224 }
225 
updateWidgetSensitivity()226 void FilterDialog::updateWidgetSensitivity ()
227 {
228 	if (!_selectedFilter.empty()) {
229 		// We have a filter, is it read-only?
230 		FilterMap::const_iterator i = _filters.find(_selectedFilter);
231 
232 		if (i != _filters.end()) {
233 
234 			if (i->second->readOnly) {
235 				gtk_widget_hide(_widgets[WIDGET_EDIT_FILTER_BUTTON]);
236 				gtk_widget_show(_widgets[WIDGET_VIEW_FILTER_BUTTON]);
237 			} else {
238 				gtk_widget_show(_widgets[WIDGET_EDIT_FILTER_BUTTON]);
239 				gtk_widget_hide(_widgets[WIDGET_VIEW_FILTER_BUTTON]);
240 			}
241 
242 			gtk_widget_set_sensitive(_widgets[WIDGET_DELETE_FILTER_BUTTON], i->second->readOnly ? FALSE : TRUE);
243 			gtk_widget_set_sensitive(_widgets[WIDGET_EDIT_FILTER_BUTTON], i->second->readOnly ? FALSE : TRUE);
244 			gtk_widget_set_sensitive(_widgets[WIDGET_VIEW_FILTER_BUTTON], i->second->readOnly ? TRUE : FALSE);
245 
246 			return;
247 		}
248 	}
249 
250 	// no valid filter selected
251 	gtk_widget_set_sensitive(_widgets[WIDGET_DELETE_FILTER_BUTTON], FALSE);
252 	gtk_widget_set_sensitive(_widgets[WIDGET_EDIT_FILTER_BUTTON], FALSE);
253 	gtk_widget_set_sensitive(_widgets[WIDGET_VIEW_FILTER_BUTTON], FALSE);
254 	gtk_widget_hide(_widgets[WIDGET_EDIT_FILTER_BUTTON]);
255 	gtk_widget_show(_widgets[WIDGET_VIEW_FILTER_BUTTON]);
256 }
257 
showDialog()258 void FilterDialog::showDialog ()
259 {
260 	// Instantiate a new instance, blocks GTK
261 	FilterDialog instance;
262 }
263 
onCancel(GtkWidget * widget,FilterDialog * self)264 void FilterDialog::onCancel (GtkWidget* widget, FilterDialog* self)
265 {
266 	// destroy dialog without saving
267 	self->destroy();
268 }
269 
onSave(GtkWidget * widget,FilterDialog * self)270 void FilterDialog::onSave (GtkWidget* widget, FilterDialog* self)
271 {
272 	// Save changes
273 	self->save();
274 
275 	// Close the dialog
276 	self->destroy();
277 }
278 
onAddFilter(GtkWidget * w,FilterDialog * self)279 void FilterDialog::onAddFilter (GtkWidget* w, FilterDialog* self)
280 {
281 	// Construct a new filter with an empty name (this indicates it has not been there before when saving)
282 	Filter* workingCopy = new Filter("", false, false);
283 	workingCopy->name = _("NewFilter");
284 
285 	// Instantiate a new editor, will block
286 	FilterEditor editor(*workingCopy, GTK_WINDOW(self->getWindow()), false);
287 
288 	if (editor.getResult() != FilterEditor::RESULT_OK) {
289 		// User hit cancel, we're done
290 		return;
291 	}
292 
293 	if (workingCopy->rules.empty()) {
294 		// Empty ruleset, notify user
295 		// TODO mattn
296 #if 0
297 		IDialogPtr dialog = GlobalDialogManager().createMessageBox(_("Empty Filter"),
298 				_("No rules defined for this filter, cannot insert."), ui::IDialog::MESSAGE_ERROR);
299 
300 		dialog->run();
301 #endif
302 		return;
303 	}
304 
305 	std::pair<FilterMap::iterator, bool> result = self->_filters.insert(FilterMap::value_type(workingCopy->name,
306 			workingCopy));
307 
308 	if (!result.second) {
309 		// Empty ruleset, notify user
310 		// TODO mattn
311 #if 0
312 		IDialogPtr dialog = GlobalDialogManager().createMessageBox(_("Name Conflict"),
313 				_("Cannot add, filter with same name already exists."), ui::IDialog::MESSAGE_ERROR);
314 
315 		dialog->run();
316 #endif
317 		return;
318 	}
319 
320 	self->update();
321 }
322 
onViewFilter(GtkWidget * w,FilterDialog * self)323 void FilterDialog::onViewFilter (GtkWidget* w, FilterDialog* self)
324 {
325 	// Lookup the Filter object
326 	FilterMap::iterator f = self->_filters.find(self->_selectedFilter);
327 
328 	if (f == self->_filters.end()) {
329 		return; // not found
330 	}
331 
332 	// Construct a new filter
333 	Filter workingCopy(*(f->second));
334 
335 	// Instantiate a new editor, will block
336 	FilterEditor editor(workingCopy, GTK_WINDOW(self->getWindow()), true);
337 }
338 
onEditFilter(GtkWidget * w,FilterDialog * self)339 void FilterDialog::onEditFilter (GtkWidget* w, FilterDialog* self)
340 {
341 	// Lookup the Filter object
342 	FilterMap::iterator f = self->_filters.find(self->_selectedFilter);
343 
344 	if (f == self->_filters.end() || f->second->readOnly) {
345 		return; // not found or read-only
346 	}
347 
348 	// Copy-construct a new filter
349 	Filter workingCopy(*(f->second));
350 
351 	// Instantiate a new editor, will block
352 	FilterEditor editor(workingCopy, GTK_WINDOW(self->getWindow()), false);
353 
354 	if (editor.getResult() != FilterEditor::RESULT_OK) {
355 		// User hit cancel, we're done
356 		return;
357 	}
358 
359 	if (workingCopy.rules.empty()) {
360 		// Empty ruleset, ask user for deletion
361 		// TODO mattn
362 #if 0
363 		IDialogPtr dialog = GlobalDialogManager().createMessageBox(_("Empty Filter"),
364 				_("No rules defined for this filter. Delete it?"), ui::IDialog::MESSAGE_ASK);
365 
366 		if (dialog->run() == IDialog::RESULT_YES) {
367 			// Move the object from _filters to _deletedfilters
368 			self->_deletedFilters.insert(*f);
369 			self->_filters.erase(f);
370 		} else {
371 			// Don't delete the empty filter, leave the old one alone
372 		}
373 #endif
374 	} else {
375 		// Ruleset is ok, has the name changed?
376 
377 		if (workingCopy.name != f->first) {
378 			// Name has changed, relocate the filter object
379 			self->_filters.erase(f->first);
380 			self->_filters[workingCopy.name] = new Filter(workingCopy);
381 		} else {
382 			// No name change, just overwrite the filter object
383 			*(f->second) = workingCopy;
384 		}
385 	}
386 
387 	// Update all widgets
388 	self->update();
389 }
390 
onDeleteFilter(GtkWidget * w,FilterDialog * self)391 void FilterDialog::onDeleteFilter (GtkWidget* w, FilterDialog* self)
392 {
393 	// Lookup the Filter object
394 	FilterMap::iterator f = self->_filters.find(self->_selectedFilter);
395 
396 	if (f == self->_filters.end() || f->second->readOnly) {
397 		return; // not found or read-only
398 	}
399 
400 	// Move the object from _filters to _deletedfilters
401 	self->_deletedFilters.insert(*f);
402 	self->_filters.erase(f);
403 
404 	// Update all widgets
405 	self->update();
406 }
407 
onFilterSelectionChanged(GtkTreeSelection * sel,FilterDialog * self)408 void FilterDialog::onFilterSelectionChanged (GtkTreeSelection* sel, FilterDialog* self)
409 {
410 	// Get the selection
411 	GtkTreeIter selected;
412 	bool hasSelection = gtk_tree_selection_get_selected(sel, NULL, &selected) ? true : false;
413 
414 	if (hasSelection) {
415 		self->_selectedFilter = gtkutil::TreeModel::getString(GTK_TREE_MODEL(self->_filterStore), &selected, COL_NAME);
416 	} else {
417 		self->_selectedFilter = "";
418 	}
419 
420 	self->updateWidgetSensitivity();
421 }
422 
423 } // namespace ui
424