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