summaryrefslogtreecommitdiff
path: root/src/searches/SavedSearchDialog.vala
diff options
context:
space:
mode:
Diffstat (limited to 'src/searches/SavedSearchDialog.vala')
-rw-r--r--src/searches/SavedSearchDialog.vala829
1 files changed, 829 insertions, 0 deletions
diff --git a/src/searches/SavedSearchDialog.vala b/src/searches/SavedSearchDialog.vala
new file mode 100644
index 0000000..da7f7db
--- /dev/null
+++ b/src/searches/SavedSearchDialog.vala
@@ -0,0 +1,829 @@
+/* Copyright 2011-2014 Yorba Foundation
+ *
+ * This software is licensed under the GNU LGPL (version 2.1 or later).
+ * See the COPYING file in this distribution.
+ */
+
+// This dialog displays a boolean search configuration.
+public class SavedSearchDialog {
+
+ // Conatins a search row, with a type selector and remove button.
+ private class SearchRowContainer {
+ public signal void remove(SearchRowContainer this_row);
+ public signal void changed(SearchRowContainer this_row);
+
+ private Gtk.ComboBoxText type_combo;
+ private Gtk.Box box;
+ private Gtk.Alignment align;
+ private Gtk.Button remove_button;
+ private SearchCondition.SearchType[] search_types;
+ private Gee.HashMap<SearchCondition.SearchType, int> search_types_index;
+
+ private SearchRow? my_row = null;
+
+ public SearchRowContainer() {
+ setup_gui();
+ set_type(SearchCondition.SearchType.ANY_TEXT);
+ }
+
+ public SearchRowContainer.edit_existing(SearchCondition sc) {
+ setup_gui();
+ set_type(sc.search_type);
+ set_type_combo_box(sc.search_type);
+ my_row.populate(sc);
+ }
+
+ // Creates the GUI for this row.
+ private void setup_gui() {
+ search_types = SearchCondition.SearchType.as_array();
+ search_types_index = new Gee.HashMap<SearchCondition.SearchType, int>();
+ SearchCondition.SearchType.sort_array(ref search_types);
+
+ type_combo = new Gtk.ComboBoxText();
+ for (int i = 0; i < search_types.length; i++) {
+ SearchCondition.SearchType st = search_types[i];
+ search_types_index.set(st, i);
+ type_combo.append_text(st.display_text());
+ }
+ set_type_combo_box(SearchCondition.SearchType.ANY_TEXT); // Sets default.
+ type_combo.changed.connect(on_type_changed);
+
+ remove_button = new Gtk.Button();
+ remove_button.set_label(" – ");
+ remove_button.button_press_event.connect(on_removed);
+
+ align = new Gtk.Alignment(0,0,0,0);
+
+ box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 8);
+ box.pack_start(type_combo, false, false, 0);
+ box.pack_start(align, false, false, 0);
+ box.pack_start(new Gtk.Alignment(0,0,0,0), true, true, 0); // Fill space.
+ box.pack_start(remove_button, false, false, 0);
+ box.show_all();
+ }
+
+ private void on_type_changed() {
+ set_type(get_search_type());
+ changed(this);
+ }
+
+ private void set_type_combo_box(SearchCondition.SearchType st) {
+ type_combo.set_active(search_types_index.get(st));
+ }
+
+ private void set_type(SearchCondition.SearchType type) {
+ if (my_row != null)
+ align.remove(my_row.get_widget());
+
+ switch (type) {
+ case SearchCondition.SearchType.ANY_TEXT:
+ case SearchCondition.SearchType.EVENT_NAME:
+ case SearchCondition.SearchType.FILE_NAME:
+ case SearchCondition.SearchType.TAG:
+ case SearchCondition.SearchType.COMMENT:
+ case SearchCondition.SearchType.TITLE:
+ my_row = new SearchRowText(this);
+ break;
+
+ case SearchCondition.SearchType.MEDIA_TYPE:
+ my_row = new SearchRowMediaType(this);
+ break;
+
+ case SearchCondition.SearchType.FLAG_STATE:
+ my_row = new SearchRowFlagged(this);
+ break;
+
+ case SearchCondition.SearchType.MODIFIED_STATE:
+ my_row = new SearchRowModified(this);
+ break;
+
+ case SearchCondition.SearchType.RATING:
+ my_row = new SearchRowRating(this);
+ break;
+
+ case SearchCondition.SearchType.DATE:
+ my_row = new SearchRowDate(this);
+ break;
+
+ default:
+ assert(false);
+ break;
+ }
+
+ align.add(my_row.get_widget());
+ }
+
+ public SearchCondition.SearchType get_search_type() {
+ return search_types[type_combo.get_active()];
+ }
+
+ private bool on_removed(Gdk.EventButton event) {
+ remove(this);
+ return false;
+ }
+
+ public void allow_removal(bool allow) {
+ remove_button.sensitive = allow;
+ }
+
+ public Gtk.Widget get_widget() {
+ return box;
+ }
+
+ public SearchCondition get_search_condition() {
+ return my_row.get_search_condition();
+ }
+
+ public bool is_complete() {
+ return my_row.is_complete();
+ }
+ }
+
+ // Represents a row-type.
+ private abstract class SearchRow {
+ // Returns the GUI widget for this row.
+ public abstract Gtk.Widget get_widget();
+
+ // Returns the search condition for this row.
+ public abstract SearchCondition get_search_condition();
+
+ // Fills out the fields in this row based on an existing search condition (for edit mode.)
+ public abstract void populate(SearchCondition sc);
+
+ // Returns true if the row is valid and complete.
+ public abstract bool is_complete();
+ }
+
+ private class SearchRowText : SearchRow {
+ private Gtk.Box box;
+ private Gtk.ComboBoxText text_context;
+ private Gtk.Entry entry;
+
+ private SearchRowContainer parent;
+
+ public SearchRowText(SearchRowContainer parent) {
+ this.parent = parent;
+
+ // Ordering must correspond with SearchConditionText.Context
+ text_context = new Gtk.ComboBoxText();
+ text_context.append_text(_("contains"));
+ text_context.append_text(_("is exactly"));
+ text_context.append_text(_("starts with"));
+ text_context.append_text(_("ends with"));
+ text_context.append_text(_("does not contain"));
+ text_context.append_text(_("is not set"));
+ text_context.set_active(0);
+ text_context.changed.connect(on_changed);
+
+ entry = new Gtk.Entry();
+ entry.set_width_chars(25);
+ entry.set_activates_default(true);
+ entry.changed.connect(on_changed);
+
+ box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 8);
+ box.pack_start(text_context, false, false, 0);
+ box.pack_start(entry, false, false, 0);
+ box.show_all();
+ }
+
+ ~SearchRowText() {
+ text_context.changed.disconnect(on_changed);
+ entry.changed.disconnect(on_changed);
+ }
+
+ public override Gtk.Widget get_widget() {
+ return box;
+ }
+
+ public override SearchCondition get_search_condition() {
+ SearchCondition.SearchType type = parent.get_search_type();
+ string text = entry.get_text();
+ SearchConditionText.Context context = get_text_context();
+ SearchConditionText c = new SearchConditionText(type, text, context);
+ return c;
+ }
+
+ public override void populate(SearchCondition sc) {
+ SearchConditionText? text = sc as SearchConditionText;
+ assert(text != null);
+ text_context.set_active(text.context);
+ entry.set_text(text.text);
+ on_changed();
+ }
+
+ public override bool is_complete() {
+ return entry.text.chomp() != "" || get_text_context() == SearchConditionText.Context.IS_NOT_SET;
+ }
+
+ private SearchConditionText.Context get_text_context() {
+ return (SearchConditionText.Context) text_context.get_active();
+ }
+
+ private void on_changed() {
+ if (get_text_context() == SearchConditionText.Context.IS_NOT_SET) {
+ entry.hide();
+ } else {
+ entry.show();
+ }
+
+ parent.changed(parent);
+ }
+ }
+
+ private class SearchRowMediaType : SearchRow {
+ private Gtk.Box box;
+ private Gtk.ComboBoxText media_context;
+ private Gtk.ComboBoxText media_type;
+
+ private SearchRowContainer parent;
+
+ public SearchRowMediaType(SearchRowContainer parent) {
+ this.parent = parent;
+
+ // Ordering must correspond with SearchConditionMediaType.Context
+ media_context = new Gtk.ComboBoxText();
+ media_context.append_text(_("is"));
+ media_context.append_text(_("is not"));
+ media_context.set_active(0);
+ media_context.changed.connect(on_changed);
+
+ // Ordering must correspond with SearchConditionMediaType.MediaType
+ media_type = new Gtk.ComboBoxText();
+ media_type.append_text(_("any photo"));
+ media_type.append_text(_("a raw photo"));
+ media_type.append_text(_("a video"));
+ media_type.set_active(0);
+ media_type.changed.connect(on_changed);
+
+ box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 8);
+ box.pack_start(media_context, false, false, 0);
+ box.pack_start(media_type, false, false, 0);
+ box.show_all();
+ }
+
+ ~SearchRowMediaType() {
+ media_context.changed.disconnect(on_changed);
+ media_type.changed.disconnect(on_changed);
+ }
+
+ public override Gtk.Widget get_widget() {
+ return box;
+ }
+
+ public override SearchCondition get_search_condition() {
+ SearchCondition.SearchType search_type = parent.get_search_type();
+ SearchConditionMediaType.Context context = (SearchConditionMediaType.Context) media_context.get_active();
+ SearchConditionMediaType.MediaType type = (SearchConditionMediaType.MediaType) media_type.get_active();
+ SearchConditionMediaType c = new SearchConditionMediaType(search_type, context, type);
+ return c;
+ }
+
+ public override void populate(SearchCondition sc) {
+ SearchConditionMediaType? media = sc as SearchConditionMediaType;
+ assert(media != null);
+ media_context.set_active(media.context);
+ media_type.set_active(media.media_type);
+ }
+
+ public override bool is_complete() {
+ return true;
+ }
+
+ private void on_changed() {
+ parent.changed(parent);
+ }
+ }
+
+ private class SearchRowModified : SearchRow {
+ private Gtk.Box box;
+ private Gtk.ComboBoxText modified_context;
+ private Gtk.ComboBoxText modified_state;
+
+ private SearchRowContainer parent;
+
+ public SearchRowModified(SearchRowContainer parent) {
+ this.parent = parent;
+
+ modified_context = new Gtk.ComboBoxText();
+ modified_context.append_text(_("has"));
+ modified_context.append_text(_("has no"));
+ modified_context.set_active(0);
+ modified_context.changed.connect(on_changed);
+
+ modified_state = new Gtk.ComboBoxText();
+ modified_state.append_text(_("modifications"));
+ modified_state.append_text(_("internal modifications"));
+ modified_state.append_text(_("external modifications"));
+ modified_state.set_active(0);
+ modified_state.changed.connect(on_changed);
+
+ box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 8);
+ box.pack_start(modified_context, false, false, 0);
+ box.pack_start(modified_state, false, false, 0);
+ box.show_all();
+ }
+
+ ~SearchRowModified() {
+ modified_state.changed.disconnect(on_changed);
+ modified_context.changed.disconnect(on_changed);
+ }
+
+ public override Gtk.Widget get_widget() {
+ return box;
+ }
+
+ public override SearchCondition get_search_condition() {
+ SearchCondition.SearchType search_type = parent.get_search_type();
+ SearchConditionModified.Context context = (SearchConditionModified.Context) modified_context.get_active();
+ SearchConditionModified.State state = (SearchConditionModified.State) modified_state.get_active();
+ SearchConditionModified c = new SearchConditionModified(search_type, context, state);
+ return c;
+ }
+
+ public override void populate(SearchCondition sc) {
+ SearchConditionModified? scm = sc as SearchConditionModified;
+ assert(scm != null);
+ modified_state.set_active(scm.state);
+ modified_context.set_active(scm.context);
+ }
+
+ public override bool is_complete() {
+ return true;
+ }
+
+ private void on_changed() {
+ parent.changed(parent);
+ }
+ }
+
+ private class SearchRowFlagged : SearchRow {
+ private Gtk.Box box;
+ private Gtk.ComboBoxText flagged_state;
+
+ private SearchRowContainer parent;
+
+ public SearchRowFlagged(SearchRowContainer parent) {
+ this.parent = parent;
+
+ // Ordering must correspond with SearchConditionFlagged.State
+ flagged_state = new Gtk.ComboBoxText();
+ flagged_state.append_text(_("flagged"));
+ flagged_state.append_text(_("not flagged"));
+ flagged_state.set_active(0);
+ flagged_state.changed.connect(on_changed);
+
+ box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 8);
+ box.pack_start(new Gtk.Label(_("is")), false, false, 0);
+ box.pack_start(flagged_state, false, false, 0);
+ box.show_all();
+ }
+
+ ~SearchRowFlagged() {
+ flagged_state.changed.disconnect(on_changed);
+ }
+
+ public override Gtk.Widget get_widget() {
+ return box;
+ }
+
+ public override SearchCondition get_search_condition() {
+ SearchCondition.SearchType search_type = parent.get_search_type();
+ SearchConditionFlagged.State state = (SearchConditionFlagged.State) flagged_state.get_active();
+ SearchConditionFlagged c = new SearchConditionFlagged(search_type, state);
+ return c;
+ }
+
+ public override void populate(SearchCondition sc) {
+ SearchConditionFlagged? f = sc as SearchConditionFlagged;
+ assert(f != null);
+ flagged_state.set_active(f.state);
+ }
+
+ public override bool is_complete() {
+ return true;
+ }
+
+ private void on_changed() {
+ parent.changed(parent);
+ }
+ }
+
+ private class SearchRowRating : SearchRow {
+ private Gtk.Box box;
+ private Gtk.ComboBoxText rating;
+ private Gtk.ComboBoxText context;
+
+ private SearchRowContainer parent;
+
+ public SearchRowRating(SearchRowContainer parent) {
+ this.parent = parent;
+
+ // Ordering must correspond with Rating
+ rating = new Gtk.ComboBoxText();
+ rating.append_text(Resources.rating_combo_box(Rating.REJECTED));
+ rating.append_text(Resources.rating_combo_box(Rating.UNRATED));
+ rating.append_text(Resources.rating_combo_box(Rating.ONE));
+ rating.append_text(Resources.rating_combo_box(Rating.TWO));
+ rating.append_text(Resources.rating_combo_box(Rating.THREE));
+ rating.append_text(Resources.rating_combo_box(Rating.FOUR));
+ rating.append_text(Resources.rating_combo_box(Rating.FIVE));
+ rating.set_active(0);
+ rating.changed.connect(on_changed);
+
+ context = new Gtk.ComboBoxText();
+ context.append_text(_("and higher"));
+ context.append_text(_("only"));
+ context.append_text(_("and lower"));
+ context.set_active(0);
+ context.changed.connect(on_changed);
+
+ box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 8);
+ box.pack_start(new Gtk.Label(_("is")), false, false, 0);
+ box.pack_start(rating, false, false, 0);
+ box.pack_start(context, false, false, 0);
+ box.show_all();
+ }
+
+ ~SearchRowRating() {
+ rating.changed.disconnect(on_changed);
+ context.changed.disconnect(on_changed);
+ }
+
+ public override Gtk.Widget get_widget() {
+ return box;
+ }
+
+ public override SearchCondition get_search_condition() {
+ SearchCondition.SearchType search_type = parent.get_search_type();
+ Rating search_rating = (Rating) rating.get_active() + Rating.REJECTED;
+ SearchConditionRating.Context search_context = (SearchConditionRating.Context) context.get_active();
+ SearchConditionRating c = new SearchConditionRating(search_type, search_rating, search_context);
+ return c;
+ }
+
+ public override void populate(SearchCondition sc) {
+ SearchConditionRating? r = sc as SearchConditionRating;
+ assert(r != null);
+ context.set_active(r.context);
+ rating.set_active(r.rating - Rating.REJECTED);
+ }
+
+ public override bool is_complete() {
+ return true;
+ }
+
+ private void on_changed() {
+ parent.changed(parent);
+ }
+ }
+
+ private class SearchRowDate : SearchRow {
+ private const string DATE_FORMAT = "%x";
+ private Gtk.Box box;
+ private Gtk.ComboBoxText context;
+ private Gtk.Button label_one;
+ private Gtk.Button label_two;
+ private Gtk.Calendar cal_one;
+ private Gtk.Calendar cal_two;
+ private Gtk.Label and;
+
+ private SearchRowContainer parent;
+
+ public SearchRowDate(SearchRowContainer parent) {
+ this.parent = parent;
+
+ // Ordering must correspond with Context
+ context = new Gtk.ComboBoxText();
+ context.append_text(_("is exactly"));
+ context.append_text(_("is after"));
+ context.append_text(_("is before"));
+ context.append_text(_("is between"));
+ context.append_text(_("is not set"));
+ context.set_active(0);
+ context.changed.connect(on_changed);
+
+ cal_one = new Gtk.Calendar();
+ cal_two = new Gtk.Calendar();
+
+ label_one = new Gtk.Button();
+ label_one.clicked.connect(on_one_clicked);
+ label_two = new Gtk.Button();
+ label_two.clicked.connect(on_two_clicked);
+
+ and = new Gtk.Label(_("and"));
+
+ box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 8);
+ box.pack_start(context, false, false, 0);
+ box.pack_start(label_one, false, false, 0);
+ box.pack_start(and, false, false, 0);
+ box.pack_start(label_two, false, false, 0);
+
+ box.show_all();
+ update_date_labels();
+ }
+
+ ~SearchRowDate() {
+ context.changed.disconnect(on_changed);
+ }
+
+ private void update_date_labels() {
+ SearchConditionDate.Context c = (SearchConditionDate.Context) context.get_active();
+
+ // Only show "and" and 2nd date label for between mode.
+ if (c == SearchConditionDate.Context.BETWEEN) {
+ label_one.show();
+ and.show();
+ label_two.show();
+ } else if (c == SearchConditionDate.Context.IS_NOT_SET) {
+ label_one.hide();
+ and.hide();
+ label_two.hide();
+ } else {
+ label_one.show();
+ and.hide();
+ label_two.hide();
+ }
+
+ // Set label text to date.
+ label_one.label = get_date_one().format(DATE_FORMAT);
+ label_two.label = get_date_two().format(DATE_FORMAT);;
+ }
+
+ public override Gtk.Widget get_widget() {
+ return box;
+ }
+
+ private DateTime get_date_one() {
+ return new DateTime.local(cal_one.year, cal_one.month + 1, cal_one.day, 0, 0, 0.0);
+ }
+
+ private DateTime get_date_two() {
+ return new DateTime.local(cal_two.year, cal_two.month + 1, cal_two.day, 0, 0, 0.0);
+ }
+
+ private void set_date_one(DateTime date) {
+ cal_one.day = date.get_day_of_month();
+ cal_one.month = date.get_month() - 1;
+ cal_one.year = date.get_year();
+ }
+
+ private void set_date_two(DateTime date) {
+ cal_two.day = date.get_day_of_month();
+ cal_two.month = date.get_month() - 1;
+ cal_two.year = date.get_year();
+ }
+
+ public override SearchCondition get_search_condition() {
+ SearchCondition.SearchType search_type = parent.get_search_type();
+ SearchConditionDate.Context search_context = (SearchConditionDate.Context) context.get_active();
+ SearchConditionDate c = new SearchConditionDate(search_type, search_context, get_date_one(),
+ get_date_two());
+ return c;
+ }
+
+ public override void populate(SearchCondition sc) {
+ SearchConditionDate? cond = sc as SearchConditionDate;
+ assert(cond != null);
+ context.set_active(cond.context);
+ set_date_one(cond.date_one);
+ set_date_two(cond.date_two);
+ update_date_labels();
+ }
+
+ public override bool is_complete() {
+ return true;
+ }
+
+ private void on_changed() {
+ parent.changed(parent);
+ update_date_labels();
+ }
+
+ private void popup_calendar(Gtk.Calendar cal) {
+ int orig_day = cal.day;
+ int orig_month = cal.month;
+ int orig_year = cal.year;
+ Gtk.Dialog d = new Gtk.Dialog.with_buttons(null, null,
+ Gtk.DialogFlags.MODAL, Gtk.Stock.CANCEL, Gtk.ResponseType.REJECT,
+ Gtk.Stock.OK, Gtk.ResponseType.ACCEPT);
+ d.set_modal(true);
+ d.set_resizable(false);
+ d.set_decorated(false);
+ ((Gtk.Box) d.get_content_area()).add(cal);
+ ulong id_1 = cal.day_selected.connect(()=>{update_date_labels();});
+ ulong id_2 = cal.day_selected_double_click.connect(()=>{d.close();});
+ d.show_all();
+ int res = d.run();
+ if (res != Gtk.ResponseType.ACCEPT) {
+ // User hit cancel, restore original date.
+ cal.day = orig_day;
+ cal.month = orig_month;
+ cal.year = orig_year;
+ }
+ cal.disconnect(id_1);
+ cal.disconnect(id_2);
+ d.destroy();
+ update_date_labels();
+ }
+
+ private void on_one_clicked() {
+ popup_calendar(cal_one);
+ }
+
+ private void on_two_clicked() {
+ popup_calendar(cal_two);
+ }
+ }
+
+ private Gtk.Builder builder;
+ private Gtk.Dialog dialog;
+ private Gtk.Button add_criteria;
+ private Gtk.ComboBoxText operator;
+ private Gtk.Box row_box;
+ private Gtk.Entry search_title;
+ private Gee.ArrayList<SearchRowContainer> row_list = new Gee.ArrayList<SearchRowContainer>();
+ private bool edit_mode = false;
+ private SavedSearch? previous_search = null;
+ private bool valid = false;
+
+ public SavedSearchDialog() {
+ setup_dialog();
+
+ // Default name.
+ search_title.set_text(SavedSearchTable.get_instance().generate_unique_name());
+ search_title.select_region(0, -1); // select all
+
+ // Default is text search.
+ add_text_search();
+ row_list.get(0).allow_removal(false);
+
+ // Add buttons for new search.
+ dialog.add_action_widget(new Gtk.Button.from_stock(Gtk.Stock.CANCEL), Gtk.ResponseType.CANCEL);
+ Gtk.Button ok_button = new Gtk.Button.from_stock(Gtk.Stock.OK);
+ ok_button.can_default = true;
+ dialog.add_action_widget(ok_button, Gtk.ResponseType.OK);
+ dialog.set_default_response(Gtk.ResponseType.OK);
+
+ dialog.show_all();
+ set_valid(false);
+ }
+
+ public SavedSearchDialog.edit_existing(SavedSearch saved_search) {
+ previous_search = saved_search;
+ edit_mode = true;
+ setup_dialog();
+
+ // Add close button.
+ Gtk.Button close_button = new Gtk.Button.from_stock(Gtk.Stock.CLOSE);
+ close_button.can_default = true;
+ dialog.add_action_widget(close_button, Gtk.ResponseType.OK);
+ dialog.set_default_response(Gtk.ResponseType.OK);
+
+ dialog.show_all();
+
+ // Load existing search into dialog.
+ operator.set_active((SearchOperator) saved_search.get_operator());
+ search_title.set_text(saved_search.get_name());
+ foreach (SearchCondition sc in saved_search.get_conditions()) {
+ add_row(new SearchRowContainer.edit_existing(sc));
+ }
+
+ if (row_list.size == 1)
+ row_list.get(0).allow_removal(false);
+
+ set_valid(true);
+ }
+
+ ~SavedSearchDialog() {
+ search_title.changed.disconnect(on_title_changed);
+ }
+
+ // Builds the dialog UI. Doesn't add buttons to the dialog or call dialog.show().
+ private void setup_dialog() {
+ builder = AppWindow.create_builder();
+
+ dialog = builder.get_object("Search criteria") as Gtk.Dialog;
+ dialog.set_parent_window(AppWindow.get_instance().get_parent_window());
+ dialog.set_transient_for(AppWindow.get_instance());
+ dialog.response.connect(on_response);
+
+ add_criteria = builder.get_object("Add search button") as Gtk.Button;
+ add_criteria.button_press_event.connect(on_add_criteria);
+
+ search_title = builder.get_object("Search title") as Gtk.Entry;
+ search_title.set_activates_default(true);
+ search_title.changed.connect(on_title_changed);
+
+ row_box = builder.get_object("row_box") as Gtk.Box;
+
+ operator = builder.get_object("Type of search criteria") as Gtk.ComboBoxText;
+ operator.append_text(_("any"));
+ operator.append_text(_("all"));
+ operator.append_text(_("none"));
+ operator.set_active(0);
+ }
+
+ // Displays the dialog.
+ public void show() {
+ dialog.run();
+ dialog.destroy();
+ }
+
+ // Adds a row of search criteria.
+ private bool on_add_criteria(Gdk.EventButton event) {
+ add_text_search();
+ return false;
+ }
+
+ private void add_text_search() {
+ SearchRowContainer text = new SearchRowContainer();
+ add_row(text);
+ }
+
+ // Appends a row of search criteria to the list and table.
+ private void add_row(SearchRowContainer row) {
+ if (row_list.size == 1)
+ row_list.get(0).allow_removal(true);
+ row_box.add(row.get_widget());
+ row_list.add(row);
+ row.remove.connect(on_remove_row);
+ row.changed.connect(on_row_changed);
+ set_valid(row.is_complete());
+ }
+
+ // Removes a row of search criteria.
+ private void on_remove_row(SearchRowContainer row) {
+ row.remove.disconnect(on_remove_row);
+ row.changed.disconnect(on_row_changed);
+ row_box.remove(row.get_widget());
+ row_list.remove(row);
+ if (row_list.size == 1)
+ row_list.get(0).allow_removal(false);
+ set_valid(true); // try setting to "true" since we removed a row
+ }
+
+ private void on_response(int response_id) {
+ if (response_id == Gtk.ResponseType.OK) {
+ if (SavedSearchTable.get_instance().exists(search_title.get_text()) &&
+ !(edit_mode && previous_search.get_name() == search_title.get_text())) {
+ AppWindow.error_message(Resources.rename_search_exists_message(search_title.get_text()));
+ return;
+ }
+
+ if (edit_mode) {
+ // Remove previous search.
+ SavedSearchTable.get_instance().remove(previous_search);
+ }
+
+ // Build the condition list from the search rows, and add our new saved search to the table.
+ Gee.ArrayList<SearchCondition> conditions = new Gee.ArrayList<SearchCondition>();
+ foreach (SearchRowContainer c in row_list) {
+ conditions.add(c.get_search_condition());
+ }
+
+ // Create the object. It will be added to the DB and SearchTable automatically.
+ SearchOperator search_operator = (SearchOperator)operator.get_active();
+ SavedSearchTable.get_instance().create(search_title.get_text(), search_operator, conditions);
+ }
+ }
+
+ private void on_row_changed(SearchRowContainer row) {
+ set_valid(row.is_complete());
+ }
+
+ private void on_title_changed() {
+ set_valid(is_title_valid());
+ }
+
+ private bool is_title_valid() {
+ if (edit_mode && previous_search != null &&
+ previous_search.get_name() == search_title.get_text())
+ return true; // Title hasn't changed.
+ if (search_title.get_text().chomp() == "")
+ return false;
+ if (SavedSearchTable.get_instance().exists(search_title.get_text()))
+ return false;
+ return true;
+ }
+
+ // Call this with your new value for validity whenever a row or the title changes.
+ private void set_valid(bool v) {
+ if (!v) {
+ valid = false;
+ } else if (v != valid) {
+ if (is_title_valid()) {
+ // Go through rows to check validity.
+ int valid_rows = 0;
+ foreach (SearchRowContainer c in row_list) {
+ if (c.is_complete())
+ valid_rows++;
+ }
+ valid = (valid_rows == row_list.size);
+ } else {
+ valid = false; // title was invalid
+ }
+ }
+
+ dialog.set_response_sensitive(Gtk.ResponseType.OK, valid);
+ }
+}