diff options
author | Yetizone <andreii.lisita@gmail.com> | 2020-07-04 18:48:24 +0300 |
---|---|---|
committer | Michael Catanzaro <mcatanzaro@gnome.org> | 2020-08-01 13:50:25 +0000 |
commit | c4aa0fb63aafb8ec93921cee84dae64ef6f5bfde (patch) | |
tree | f6293e4b3388bc17b0bedbe26ea5fb4946c49699 | |
parent | d1c0317c687955507fa842e489cebe486ba83918 (diff) | |
download | epiphany-c4aa0fb63aafb8ec93921cee84dae64ef6f5bfde.tar.gz |
history-dialog: Add selection mode
- Add copy url buttons to History rows
- Remove the right click popup menu
- Move Clear All button to an action bar at the bottom
Closes https://gitlab.gnome.org/GNOME/epiphany/-/issues/903
-rw-r--r-- | src/ephy-history-dialog.c | 414 | ||||
-rw-r--r-- | src/preferences/ephy-data-dialog.c | 160 | ||||
-rw-r--r-- | src/preferences/ephy-data-dialog.h | 10 | ||||
-rw-r--r-- | src/resources/gtk/data-dialog.ui | 182 | ||||
-rw-r--r-- | src/resources/gtk/history-dialog.ui | 44 |
5 files changed, 559 insertions, 251 deletions
diff --git a/src/ephy-history-dialog.c b/src/ephy-history-dialog.c index e1356348a..2065ce4eb 100644 --- a/src/ephy-history-dialog.c +++ b/src/ephy-history-dialog.c @@ -54,7 +54,6 @@ struct _EphyHistoryDialog { GtkWidget *listbox; GtkWidget *forget_all_button; - GtkWidget *popup_menu; GActionGroup *action_group; @@ -62,6 +61,7 @@ struct _EphyHistoryDialog { guint sorter_source; gint num_fetch; + gboolean shift_modifier_active; GtkWidget *confirmation_dialog; }; @@ -178,23 +178,21 @@ filter_now (EphyHistoryDialog *self) } static GList * -get_selection (EphyHistoryDialog *self) +get_checked_rows (EphyHistoryDialog *self) { - GList *selected_rows = gtk_list_box_get_selected_rows (GTK_LIST_BOX (self->listbox)); - GList *list = NULL; - GList *tmp; + g_autoptr (GList) rows_list = gtk_container_get_children (GTK_CONTAINER (self->listbox)); + GList *checked_rows = NULL; + GList *iter = NULL; - for (tmp = selected_rows; tmp; tmp = tmp->next) { - EphyHistoryURL *url = get_url_from_row (tmp->data); + for (iter = rows_list; iter != NULL; iter = g_list_next (iter)) { + GObject *row = iter->data; + GtkCheckButton *check_button = GTK_CHECK_BUTTON (g_object_get_data (row, "check-button")); - if (!url) { - continue; - } - - list = g_list_append (list, url); + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (check_button))) + checked_rows = g_list_prepend (checked_rows, row); } - return g_list_reverse (list); + return checked_rows; } static void @@ -212,28 +210,71 @@ on_browse_history_deleted_cb (gpointer service, } static void -delete_selected (EphyHistoryDialog *self) +delete_checked_rows (EphyHistoryDialog *self) { - GList *selected; + g_autoptr (GList) checked_rows = get_checked_rows (self); + GList *deleted_urls = NULL; + GList *iter = NULL; + + for (iter = checked_rows; iter != NULL; iter = g_list_next (iter)) { + EphyHistoryURL *url = get_url_from_row (iter->data); + + deleted_urls = g_list_prepend (deleted_urls, url); + } - selected = get_selection (self); - ephy_history_service_delete_urls (self->history_service, selected, self->cancellable, + ephy_history_service_delete_urls (self->history_service, deleted_urls, self->cancellable, (EphyHistoryJobCallback)on_browse_history_deleted_cb, self); - for (GList *l = selected; l; l = l->next) - ephy_snapshot_service_delete_snapshot_for_url (self->snapshot_service, ((EphyHistoryURL *)l->data)->url); + for (iter = deleted_urls; iter != NULL; iter = g_list_next (iter)) + ephy_snapshot_service_delete_snapshot_for_url (self->snapshot_service, ((EphyHistoryURL *)iter->data)->url); + + ephy_data_dialog_set_selection_actions_sensitive (EPHY_DATA_DIALOG (self), FALSE); + g_list_free_full (deleted_urls, (GDestroyNotify)ephy_history_url_free); +} + +static GtkWidget * +get_target_window (EphyHistoryDialog *self) +{ + return GTK_WIDGET (gtk_application_get_active_window (GTK_APPLICATION (ephy_shell_get_default ()))); } static void -forget_clicked (GtkButton *button, - gpointer user_data) +open_checked_rows (GtkWidget *open_button, + EphyHistoryDialog *self) { - EphyHistoryDialog *self = EPHY_HISTORY_DIALOG (user_data); - GtkListBoxRow *row = g_object_get_data (G_OBJECT (button), "row"); + EphyWindow *window = EPHY_WINDOW (get_target_window (self)); + g_autoptr (GList) checked_rows = get_checked_rows (self); + GList *iter = NULL; + + for (iter = checked_rows; iter != NULL; iter = g_list_next (iter)) { + g_autoptr (EphyHistoryURL) url = get_url_from_row (iter->data); + EphyEmbed *embed; + + embed = ephy_shell_new_tab (ephy_shell_get_default (), + window, NULL, EPHY_NEW_TAB_JUMP); + ephy_web_view_load_url (ephy_embed_get_web_view (embed), url->url); + } +} - gtk_list_box_select_row (GTK_LIST_BOX (self->listbox), row); +static void +row_copy_url_button_clicked (GtkWidget *button, + gpointer user_data) +{ + GtkListBoxRow *row = user_data; + g_autoptr (EphyHistoryURL) url = get_url_from_row (row); - delete_selected (self); + if (url) + gtk_clipboard_set_text (gtk_widget_get_clipboard (GTK_WIDGET (button), GDK_SELECTION_CLIPBOARD), url->url, -1); +} + +static void +row_check_button_toggled (GtkCheckButton *check_button, + EphyHistoryDialog *self) +{ + g_autoptr (GList) checked_rows = get_checked_rows (self); + guint n_rows = g_list_length (checked_rows); + + ephy_data_dialog_set_selection_actions_sensitive (EPHY_DATA_DIALOG (self), n_rows > 0); } static GtkWidget * @@ -244,7 +285,8 @@ create_row (EphyHistoryDialog *self, GtkWidget *date; GtkWidget *row; GtkWidget *separator; - GtkWidget *button; + GtkWidget *check_button; + GtkWidget *copy_url_button; /* Row */ row = hdy_action_row_new (); @@ -263,23 +305,44 @@ create_row (EphyHistoryDialog *self, gtk_widget_set_margin_top (separator, 8); gtk_widget_set_margin_bottom (separator, 8); - /* Button */ - button = gtk_button_new_from_icon_name ("user-trash-symbolic", GTK_ICON_SIZE_BUTTON); - gtk_widget_set_valign (button, GTK_ALIGN_CENTER); - g_object_set_data (G_OBJECT (button), "row", row); - gtk_widget_set_tooltip_text (button, _("Remove the selected pages from history")); - g_signal_connect (button, "clicked", G_CALLBACK (forget_clicked), self); - gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE); - - /* Added in reverse order because actions are packed from the end. */ + /* CheckButton */ + check_button = gtk_check_button_new (); + g_object_set_data (G_OBJECT (row), "check-button", check_button); + gtk_widget_set_valign (check_button, GTK_ALIGN_CENTER); + gtk_widget_set_tooltip_text (check_button, _("Remove the selected pages from history")); + gtk_button_set_relief (GTK_BUTTON (check_button), GTK_RELIEF_NONE); + g_signal_connect (check_button, "toggled", G_CALLBACK (row_check_button_toggled), self); + + /* Copy URL button */ + copy_url_button = gtk_button_new_from_icon_name ("edit-copy-symbolic", GTK_ICON_SIZE_BUTTON); + gtk_widget_set_valign (copy_url_button, GTK_ALIGN_CENTER); + gtk_widget_set_tooltip_text (copy_url_button, _("Copy URL")); + g_signal_connect (copy_url_button, "clicked", G_CALLBACK (row_copy_url_button_clicked), row); + + /* Separator and CheckButton should be visible only during selection mode */ + g_object_bind_property (self, "selection-active", + separator, "visible", + G_BINDING_DEFAULT); + + g_object_bind_property (self, "selection-active", + check_button, "visible", + G_BINDING_DEFAULT); + + hdy_action_row_add_prefix (HDY_ACTION_ROW (row), check_button); + hdy_action_row_add_prefix (HDY_ACTION_ROW (row), separator); gtk_container_add (GTK_CONTAINER (row), date); - gtk_container_add (GTK_CONTAINER (row), separator); - gtk_container_add (GTK_CONTAINER (row), button); + gtk_container_add (GTK_CONTAINER (row), copy_url_button); - gtk_widget_set_sensitive (button, ephy_embed_shell_get_mode (shell) != EPHY_EMBED_SHELL_MODE_INCOGNITO); + gtk_widget_set_sensitive (check_button, ephy_embed_shell_get_mode (shell) != EPHY_EMBED_SHELL_MODE_INCOGNITO); gtk_widget_show_all (row); + /* Hide the Separator and CheckButton if selection isn't active */ + if (!ephy_data_dialog_get_selection_active (EPHY_DATA_DIALOG (self))) { + gtk_widget_set_visible (separator, FALSE); + gtk_widget_set_visible (check_button, FALSE); + } + return row; } @@ -394,73 +457,13 @@ forget_all (GSimpleAction *action, gtk_widget_show (self->confirmation_dialog); } -static GtkWidget * -get_target_window (EphyHistoryDialog *self) -{ - return GTK_WIDGET (gtk_application_get_active_window (GTK_APPLICATION (ephy_shell_get_default ()))); -} - -static void -open_selection (GSimpleAction *action, - GVariant *parameter, - gpointer user_data) -{ - EphyHistoryDialog *self = EPHY_HISTORY_DIALOG (user_data); - EphyWindow *window; - GList *selection; - GList *l; - - selection = get_selection (self); - - window = EPHY_WINDOW (get_target_window (self)); - for (l = selection; l; l = l->next) { - EphyHistoryURL *url = l->data; - EphyEmbed *embed; - - embed = ephy_shell_new_tab (ephy_shell_get_default (), - window, NULL, EPHY_NEW_TAB_JUMP); - ephy_web_view_load_url (ephy_embed_get_web_view (embed), url->url); - } - - g_list_free_full (selection, (GDestroyNotify)ephy_history_url_free); -} - -static void -copy_url (GSimpleAction *action, - GVariant *parameter, - gpointer user_data) -{ - EphyHistoryDialog *self = EPHY_HISTORY_DIALOG (user_data); - GList *selection; - - selection = get_selection (self); - - if (g_list_length (selection) == 1) { - EphyHistoryURL *url = selection->data; - gtk_clipboard_set_text (gtk_clipboard_get_default (gdk_display_get_default ()), - url->url, -1); - } - - g_list_free_full (selection, (GDestroyNotify)ephy_history_url_free); -} - -static void -forget (GSimpleAction *action, - GVariant *parameter, - gpointer user_data) -{ - EphyHistoryDialog *self = EPHY_HISTORY_DIALOG (user_data); - - delete_selected (self); -} - static gboolean on_listbox_key_press_event (GtkWidget *widget, GdkEventKey *event, EphyHistoryDialog *self) { if (event->keyval == GDK_KEY_Delete || event->keyval == GDK_KEY_KP_Delete) { - delete_selected (self); + delete_checked_rows (self); return TRUE; } @@ -469,17 +472,6 @@ on_listbox_key_press_event (GtkWidget *widget, } static void -update_popup_menu_actions (GActionGroup *action_group, - gboolean only_one_selected_item) -{ - GAction *copy_url_action; - - copy_url_action = g_action_map_lookup_action (G_ACTION_MAP (action_group), "copy-url"); - - g_simple_action_set_enabled (G_SIMPLE_ACTION (copy_url_action), only_one_selected_item); -} - -static void on_search_text_changed (EphyHistoryDialog *self) { filter_now (self); @@ -501,6 +493,11 @@ on_key_press_event (EphyHistoryDialog *self, { GdkEventKey *key = (GdkEventKey *)event; + /* Keep track internally of the Shift modifier needed for the + * interval selection logic */ + if (key->keyval == GDK_KEY_Shift_L || key->keyval == GDK_KEY_Shift_R) + self->shift_modifier_active = TRUE; + if (key->keyval == GDK_KEY_Down || key->keyval == GDK_KEY_Page_Down) { GList *childrens = gtk_container_get_children (GTK_CONTAINER (self->listbox)); GtkWidget *last = g_list_last (childrens)->data; @@ -513,82 +510,126 @@ on_key_press_event (EphyHistoryDialog *self, } } - return GDK_EVENT_PROPAGATE; -} + /* Edge case: Shift + Enter in selection mode + * Pressing simply Enter without any modifiers activates the focused row, + * but pressing Enter with modifiers doesn't do anything. + * We want Shift + Enter to activate the row and trigger the + * row interval selecton logic */ + if (key->keyval == GDK_KEY_Return && self->shift_modifier_active + && ephy_data_dialog_get_selection_active (EPHY_DATA_DIALOG (self))) { + GtkWindow *dialog_window = GTK_WINDOW (self); + GtkWidget *focused_widget = gtk_window_get_focus (dialog_window); -static void -update_selection_actions (GActionGroup *action_group, - gboolean has_selection) -{ - EphyEmbedShell *shell = ephy_embed_shell_get_default (); - GAction *forget_action; - GAction *open_selection_action; + if (GTK_IS_LIST_BOX_ROW (focused_widget)) { + g_signal_emit_by_name (self->listbox, "row-activated", focused_widget, self); - if (ephy_embed_shell_get_mode (shell) != EPHY_EMBED_SHELL_MODE_INCOGNITO) { - forget_action = g_action_map_lookup_action (G_ACTION_MAP (action_group), "forget"); - g_simple_action_set_enabled (G_SIMPLE_ACTION (forget_action), has_selection); + return GDK_EVENT_STOP; + } } - open_selection_action = g_action_map_lookup_action (G_ACTION_MAP (action_group), "open-selection"); - g_simple_action_set_enabled (G_SIMPLE_ACTION (open_selection_action), has_selection); + return GDK_EVENT_PROPAGATE; } -static void -on_listbox_row_selected (GtkListBox *box, - GtkListBoxRow *row, - EphyHistoryDialog *self) +static gboolean +on_key_release_event (EphyHistoryDialog *self, + GdkEvent *event, + gpointer user_data) { - update_selection_actions (self->action_group, !!row); + GdkEventKey *key = (GdkEventKey *)event; + + /* Keep track internally of the Shift modifier needed for the + * interval selection logic */ + if (key->keyval == GDK_KEY_Shift_L || key->keyval == GDK_KEY_Shift_R) + self->shift_modifier_active = FALSE; + + /* Don't handle the event */ + return GDK_EVENT_PROPAGATE; } static void -on_listbox_row_activated (GtkListBox *box, - GtkListBoxRow *row, - EphyHistoryDialog *self) +check_rows_interval (GtkListBox *listbox, + gint index_a, + gint index_b) { - EphyWindow *window; - EphyHistoryURL *url; - EphyEmbed *embed; + gint start = 0; + gint end = 0; + gint index = 0; - window = EPHY_WINDOW (get_target_window (self)); - url = get_url_from_row (row); - g_assert (url); + if (index_a < index_b) { + start = index_a; + end = index_b; + } else { + start = index_b; + end = index_a; + } - embed = ephy_shell_new_tab (ephy_shell_get_default (), - window, NULL, EPHY_NEW_TAB_JUMP); - ephy_web_view_load_url (ephy_embed_get_web_view (embed), url->url); - ephy_history_url_free (url); + for (index = start; index <= end; index++) { + GtkListBoxRow *row = gtk_list_box_get_row_at_index (listbox, index); + GtkCheckButton *check_button = GTK_CHECK_BUTTON (g_object_get_data (G_OBJECT (row), "check-button")); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button), TRUE); + } } -static gboolean -on_listbox_button_press_event (GtkWidget *widget, - GdkEventButton *event, - EphyHistoryDialog *self) +static void +handle_selection_row_activated_event (EphyHistoryDialog *self, + GtkListBoxRow *activated_row) { - if (event->button == GDK_BUTTON_SECONDARY) { - GtkListBoxRow *row = gtk_list_box_get_row_at_y (GTK_LIST_BOX (self->listbox), event->y); - GList *rows = NULL; - int n; + g_autoptr (GList) checked_rows = get_checked_rows (self); + GtkCheckButton *check_button = GTK_CHECK_BUTTON (g_object_get_data (G_OBJECT (activated_row), "check-button")); + gboolean button_checked = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (check_button)); - if (!row) - return GDK_EVENT_PROPAGATE; + /* If Shift modifier isn't active, event simply toggles the row's checkbox button */ + if (!self->shift_modifier_active) { + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button), !button_checked); + return; + } - if (!gtk_list_box_row_is_selected (row)) - gtk_list_box_unselect_all (GTK_LIST_BOX (self->listbox)); + /* If Shift modifier is active, do the row interval logic */ + if (g_list_length (checked_rows) == 1) { + /* If there's exactly one other row checked we check the interval between + * that one and the currently clicked row */ + gint index_a = gtk_list_box_row_get_index (activated_row); + gint index_b = gtk_list_box_row_get_index (checked_rows->data); - gtk_list_box_select_row (GTK_LIST_BOX (self->listbox), row); - rows = gtk_list_box_get_selected_rows (GTK_LIST_BOX (self->listbox)); - n = g_list_length (rows); - g_list_free (rows); + check_rows_interval (GTK_LIST_BOX (self->listbox), index_a, index_b); + } else { + /* If there are zero or more than one other rows checked, + * then we check the clicked row and uncheck all the others */ + g_autoptr (GList) rows = gtk_container_get_children (GTK_CONTAINER (self->listbox)); + GList *iter = NULL; - update_popup_menu_actions (self->action_group, n == 1); + for (iter = rows; iter != NULL; iter = g_list_next (iter)) { + GObject *row = iter->data; + GtkCheckButton *row_check_btn = GTK_CHECK_BUTTON (g_object_get_data (row, "check-button")); - gtk_menu_popup_at_pointer (GTK_MENU (self->popup_menu), (GdkEvent *)event); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (row_check_btn), FALSE); + } - return GDK_EVENT_STOP; + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button), TRUE); } +} - return GDK_EVENT_PROPAGATE; +static void +on_listbox_row_activated (GtkListBox *box, + GtkListBoxRow *row, + EphyHistoryDialog *self) +{ + gboolean selection_active = ephy_data_dialog_get_selection_active (EPHY_DATA_DIALOG (self)); + + /* If a History row is activated outside of selection mode, we open the + * row's web page in a new tab*/ + if (!selection_active) { + EphyWindow *window = EPHY_WINDOW (get_target_window (self)); + g_autoptr (EphyHistoryURL) url = get_url_from_row (row); + EphyEmbed *embed = ephy_shell_new_tab (ephy_shell_get_default (), + window, NULL, EPHY_NEW_TAB_JUMP); + + ephy_web_view_load_url (ephy_embed_get_web_view (embed), url->url); + } else { + /* Selection mode is active, run selection logic */ + handle_selection_row_activated_event (self, row); + } } static void @@ -672,6 +713,27 @@ on_edge_reached (GtkScrolledWindow *scrolled, } static void +on_dialog_selection_active_toggled (EphyHistoryDialog *self) +{ + /* Uncheck all rows */ + g_autoptr (GList) rows = gtk_container_get_children (GTK_CONTAINER (self->listbox)); + GList *iter = NULL; + + for (iter = rows; iter != NULL; iter = g_list_next (iter)) { + GObject *row = iter->data; + GtkCheckButton *check_button = GTK_CHECK_BUTTON (g_object_get_data (row, "check-button")); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button), FALSE); + } +} + +static void +on_dialog_selection_delete_clicked (EphyHistoryDialog *self) +{ + delete_checked_rows (self); +} + +static void ephy_history_dialog_class_init (EphyHistoryDialogClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); @@ -693,14 +755,14 @@ ephy_history_dialog_class_init (EphyHistoryDialogClass *klass) gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/epiphany/gtk/history-dialog.ui"); gtk_widget_class_bind_template_child (widget_class, EphyHistoryDialog, listbox); - gtk_widget_class_bind_template_child (widget_class, EphyHistoryDialog, popup_menu); - gtk_widget_class_bind_template_callback (widget_class, on_listbox_row_activated); - gtk_widget_class_bind_template_callback (widget_class, on_listbox_row_selected); - gtk_widget_class_bind_template_callback (widget_class, on_listbox_button_press_event); gtk_widget_class_bind_template_callback (widget_class, on_listbox_key_press_event); + gtk_widget_class_bind_template_callback (widget_class, on_listbox_row_activated); gtk_widget_class_bind_template_callback (widget_class, on_key_press_event); + gtk_widget_class_bind_template_callback (widget_class, on_key_release_event); gtk_widget_class_bind_template_callback (widget_class, on_search_text_changed); + gtk_widget_class_bind_template_callback (widget_class, on_dialog_selection_active_toggled); + gtk_widget_class_bind_template_callback (widget_class, on_dialog_selection_delete_clicked); gtk_widget_class_bind_template_callback (widget_class, on_edge_reached); } @@ -718,13 +780,23 @@ ephy_history_dialog_new (EphyHistoryService *history_service) return GTK_WIDGET (self); } +static void +add_open_selection_button (EphyHistoryDialog *self) +{ + GtkWidget *button = gtk_button_new (); + + gtk_widget_set_visible (button, TRUE); + gtk_widget_set_sensitive (button, FALSE); + gtk_button_set_use_underline (GTK_BUTTON (button), TRUE); + gtk_button_set_label (GTK_BUTTON (button), "_Open"); + g_signal_connect (button, "clicked", (GCallback)open_checked_rows, self); + ephy_data_dialog_add_selection_action (EPHY_DATA_DIALOG (self), button); +} + static GActionGroup * create_action_group (EphyHistoryDialog *self) { const GActionEntry entries[] = { - { "open-selection", open_selection }, - { "copy-url", copy_url }, - { "forget", forget }, { "forget-all", forget_all } }; GSimpleActionGroup *group; @@ -752,7 +824,7 @@ ephy_history_dialog_init (EphyHistoryDialog *self) ephy_gui_ensure_window_group (GTK_WINDOW (self)); - gtk_menu_attach_to_widget (GTK_MENU (self->popup_menu), GTK_WIDGET (self), NULL); + add_open_selection_button (self); self->action_group = create_action_group (self); gtk_widget_insert_action_group (GTK_WIDGET (self), "history", self->action_group); @@ -763,8 +835,6 @@ ephy_history_dialog_init (EphyHistoryDialog *self) action = g_action_map_lookup_action (G_ACTION_MAP (self->action_group), "forget-all"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE); - - update_selection_actions (self->action_group, FALSE); } else { ephy_data_dialog_set_can_clear (EPHY_DATA_DIALOG (self), TRUE); } diff --git a/src/preferences/ephy-data-dialog.c b/src/preferences/ephy-data-dialog.c index bd22a44d5..4ebe801e5 100644 --- a/src/preferences/ephy-data-dialog.c +++ b/src/preferences/ephy-data-dialog.c @@ -26,20 +26,29 @@ typedef struct { GtkWidget *box; + GtkWidget *header_bars_stack; + GtkWidget *window_header_bar; + GtkWidget *selection_header_bar; GtkWidget *child; GtkWidget *clear_all_button; GtkWidget *search_bar; GtkWidget *search_entry; GtkWidget *search_button; - GtkWidget *stack; + GtkWidget *data_presentation_stack; GtkWidget *empty_title_label; GtkWidget *empty_description_label; GtkWidget *spinner; + GtkWidget *action_bars_stack; + GtkWidget *regular_action_bar; + GtkWidget *selection_action_bar; + GtkWidget *selection_actions_box; + GtkWidget *selection_delete_button; gboolean is_loading : 1; gboolean has_data : 1; gboolean has_search_results : 1; gboolean can_clear : 1; + gboolean selection_active : 1; char *search_text; } EphyDataDialogPrivate; @@ -58,6 +67,7 @@ enum { PROP_HAS_DATA, PROP_HAS_SEARCH_RESULTS, PROP_CAN_CLEAR, + PROP_SELECTION_ACTIVE, LAST_PROP, }; @@ -65,6 +75,7 @@ static GParamSpec *obj_properties[LAST_PROP]; enum { CLEAR_ALL_CLICKED, + SELECTION_DELETE_CLICKED, LAST_SIGNAL, }; @@ -74,26 +85,33 @@ static void update (EphyDataDialog *self) { EphyDataDialogPrivate *priv = ephy_data_dialog_get_instance_private (self); + GtkStack *data_stack = GTK_STACK (priv->data_presentation_stack); + GtkStack *action_bars_stack = GTK_STACK (priv->action_bars_stack); gboolean has_data = priv->has_data && priv->child && gtk_widget_get_visible (priv->child); if (priv->is_loading) { - gtk_stack_set_visible_child_name (GTK_STACK (priv->stack), "loading"); + gtk_stack_set_visible_child_name (data_stack, "loading"); gtk_spinner_start (GTK_SPINNER (priv->spinner)); } else { if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->search_button))) { if (has_data && priv->has_search_results) - gtk_stack_set_visible_child (GTK_STACK (priv->stack), priv->child); + gtk_stack_set_visible_child (data_stack, priv->child); else - gtk_stack_set_visible_child_name (GTK_STACK (priv->stack), "no-results"); + gtk_stack_set_visible_child_name (data_stack, "no-results"); } else { if (has_data) - gtk_stack_set_visible_child (GTK_STACK (priv->stack), priv->child); + gtk_stack_set_visible_child (data_stack, priv->child); else - gtk_stack_set_visible_child_name (GTK_STACK (priv->stack), "empty"); + gtk_stack_set_visible_child_name (data_stack, "empty"); } gtk_spinner_stop (GTK_SPINNER (priv->spinner)); } + if (priv->selection_active) + gtk_stack_set_visible_child (action_bars_stack, priv->selection_action_bar); + else + gtk_stack_set_visible_child (action_bars_stack, priv->regular_action_bar); + gtk_widget_set_sensitive (priv->clear_all_button, has_data && priv->can_clear); gtk_widget_set_sensitive (priv->search_button, has_data); } @@ -105,6 +123,35 @@ on_clear_all_button_clicked (EphyDataDialog *self) } static void +on_selection_button_clicked (GtkButton *button, + EphyDataDialog *self) +{ + EphyDataDialogPrivate *priv = ephy_data_dialog_get_instance_private (self); + GtkStack *header_bars_stack = GTK_STACK (priv->header_bars_stack); + + ephy_data_dialog_set_selection_active (self, TRUE); + gtk_stack_set_visible_child (header_bars_stack, priv->selection_header_bar); +} + +static void +on_selection_cancel_button_clicked (GtkButton *button, + EphyDataDialog *self) +{ + EphyDataDialogPrivate *priv = ephy_data_dialog_get_instance_private (self); + GtkStack *header_bars_stack = GTK_STACK (priv->header_bars_stack); + + ephy_data_dialog_set_selection_active (self, FALSE); + gtk_stack_set_visible_child (header_bars_stack, priv->window_header_bar); +} + +static void +on_selection_delete_button_clicked (GtkButton *button, + EphyDataDialog *self) +{ + g_signal_emit (self, signals[SELECTION_DELETE_CLICKED], 0); +} + +static void on_search_entry_changed (GtkSearchEntry *entry, EphyDataDialog *self) { @@ -184,6 +231,9 @@ ephy_data_dialog_set_property (GObject *object, case PROP_CAN_CLEAR: ephy_data_dialog_set_can_clear (self, g_value_get_boolean (value)); break; + case PROP_SELECTION_ACTIVE: + ephy_data_dialog_set_selection_active (self, g_value_get_boolean (value)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -233,6 +283,9 @@ ephy_data_dialog_get_property (GObject *object, case PROP_CAN_CLEAR: g_value_set_boolean (value, ephy_data_dialog_get_can_clear (self)); break; + case PROP_SELECTION_ACTIVE: + g_value_set_boolean (value, ephy_data_dialog_get_selection_active (self)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -256,6 +309,7 @@ ephy_data_dialog_add (GtkContainer *container, { EphyDataDialog *self = EPHY_DATA_DIALOG (container); EphyDataDialogPrivate *priv = ephy_data_dialog_get_instance_private (self); + GtkStack *data_stack = GTK_STACK (priv->data_presentation_stack); if (!priv->box) { GTK_CONTAINER_CLASS (ephy_data_dialog_parent_class)->add (container, child); @@ -265,7 +319,7 @@ ephy_data_dialog_add (GtkContainer *container, g_assert (!priv->child); priv->child = child; - gtk_container_add (GTK_CONTAINER (priv->stack), child); + gtk_container_add (GTK_CONTAINER (data_stack), child); update (self); } @@ -360,8 +414,24 @@ ephy_data_dialog_class_init (EphyDataDialogClass *klass) FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_SELECTION_ACTIVE] = + g_param_spec_boolean ("selection-active", + "Selection active", + "Is selection active?", + FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (object_class, LAST_PROP, obj_properties); + signals[SELECTION_DELETE_CLICKED] = + g_signal_new ("selection-delete-clicked", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, + 0, + G_TYPE_NONE); + /** * EphyLocationEntry::user-changed: * @entry: the object on which the signal is emitted @@ -381,6 +451,9 @@ ephy_data_dialog_class_init (EphyDataDialogClass *klass) gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/epiphany/gtk/data-dialog.ui"); gtk_widget_class_bind_template_child_private (widget_class, EphyDataDialog, box); + gtk_widget_class_bind_template_child_private (widget_class, EphyDataDialog, header_bars_stack); + gtk_widget_class_bind_template_child_private (widget_class, EphyDataDialog, window_header_bar); + gtk_widget_class_bind_template_child_private (widget_class, EphyDataDialog, selection_header_bar); gtk_widget_class_bind_template_child_private (widget_class, EphyDataDialog, clear_all_button); gtk_widget_class_bind_template_child_private (widget_class, EphyDataDialog, empty_title_label); gtk_widget_class_bind_template_child_private (widget_class, EphyDataDialog, empty_description_label); @@ -388,10 +461,18 @@ ephy_data_dialog_class_init (EphyDataDialogClass *klass) gtk_widget_class_bind_template_child_private (widget_class, EphyDataDialog, search_button); gtk_widget_class_bind_template_child_private (widget_class, EphyDataDialog, search_entry); gtk_widget_class_bind_template_child_private (widget_class, EphyDataDialog, spinner); - gtk_widget_class_bind_template_child_private (widget_class, EphyDataDialog, stack); + gtk_widget_class_bind_template_child_private (widget_class, EphyDataDialog, data_presentation_stack); + gtk_widget_class_bind_template_child_private (widget_class, EphyDataDialog, action_bars_stack); + gtk_widget_class_bind_template_child_private (widget_class, EphyDataDialog, regular_action_bar); + gtk_widget_class_bind_template_child_private (widget_class, EphyDataDialog, selection_action_bar); + gtk_widget_class_bind_template_child_private (widget_class, EphyDataDialog, selection_actions_box); + gtk_widget_class_bind_template_child_private (widget_class, EphyDataDialog, selection_delete_button); gtk_widget_class_bind_template_callback (widget_class, on_key_press_event); gtk_widget_class_bind_template_callback (widget_class, on_clear_all_button_clicked); + gtk_widget_class_bind_template_callback (widget_class, on_selection_button_clicked); + gtk_widget_class_bind_template_callback (widget_class, on_selection_cancel_button_clicked); + gtk_widget_class_bind_template_callback (widget_class, on_selection_delete_button_clicked); gtk_widget_class_bind_template_callback (widget_class, on_search_entry_changed); } @@ -569,6 +650,69 @@ ephy_data_dialog_set_can_clear (EphyDataDialog *self, g_object_notify_by_pspec (G_OBJECT (self), obj_properties[PROP_CAN_CLEAR]); } +gboolean +ephy_data_dialog_get_selection_active (EphyDataDialog *self) +{ + EphyDataDialogPrivate *priv; + + g_assert (EPHY_IS_DATA_DIALOG (self)); + + priv = ephy_data_dialog_get_instance_private (self); + + return priv->selection_active; +} + +void +ephy_data_dialog_set_selection_active (EphyDataDialog *self, + gboolean selection_active) +{ + EphyDataDialogPrivate *priv; + + g_assert (EPHY_IS_DATA_DIALOG (self)); + + priv = ephy_data_dialog_get_instance_private (self); + + if (priv->selection_active == selection_active) + return; + + priv->selection_active = selection_active; + + update (self); + + g_object_notify_by_pspec (G_OBJECT (self), obj_properties[PROP_SELECTION_ACTIVE]); +} + +void +ephy_data_dialog_set_selection_actions_sensitive (EphyDataDialog *self, + gboolean actions_sensitive) +{ + EphyDataDialogPrivate *priv; + GtkContainer *selection_actions_box; + g_autoptr (GList) selection_actions_widgets = NULL; + GList *iter; + + g_assert (EPHY_IS_DATA_DIALOG (self)); + + priv = ephy_data_dialog_get_instance_private (self); + selection_actions_box = GTK_CONTAINER (priv->selection_actions_box); + selection_actions_widgets = gtk_container_get_children (selection_actions_box); + + for (iter = selection_actions_widgets; iter != NULL; iter = g_list_next (iter)) { + GtkWidget *action_widget = iter->data; + + gtk_widget_set_sensitive (action_widget, actions_sensitive); + } +} + +void +ephy_data_dialog_add_selection_action (EphyDataDialog *self, + GtkWidget *action_widget) +{ + EphyDataDialogPrivate *priv = ephy_data_dialog_get_instance_private (self); + + gtk_container_add (GTK_CONTAINER (priv->selection_actions_box), action_widget); +} + const gchar * ephy_data_dialog_get_search_text (EphyDataDialog *self) { diff --git a/src/preferences/ephy-data-dialog.h b/src/preferences/ephy-data-dialog.h index ed89d6851..e0d0a3bf2 100644 --- a/src/preferences/ephy-data-dialog.h +++ b/src/preferences/ephy-data-dialog.h @@ -54,6 +54,16 @@ gboolean ephy_data_dialog_get_can_clear (EphyDataDialog *self); void ephy_data_dialog_set_can_clear (EphyDataDialog *self, gboolean can_clear); +gboolean ephy_data_dialog_get_selection_active (EphyDataDialog *self); +void ephy_data_dialog_set_selection_active (EphyDataDialog *self, + gboolean selection_active); + +void ephy_data_dialog_set_selection_actions_sensitive (EphyDataDialog *self, + gboolean actions_sensitive); + +void ephy_data_dialog_add_selection_action (EphyDataDialog *self, + GtkWidget *action_bar_widget); + const gchar *ephy_data_dialog_get_search_text (EphyDataDialog *self); G_END_DECLS diff --git a/src/resources/gtk/data-dialog.ui b/src/resources/gtk/data-dialog.ui index c8cf048ea..7503054df 100644 --- a/src/resources/gtk/data-dialog.ui +++ b/src/resources/gtk/data-dialog.ui @@ -3,11 +3,6 @@ <interface> <requires lib="gtk+" version="3.20"/> <requires lib="libhandy" version="0.0"/> - <object class="GtkImage" id="search_image"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="icon_name">edit-find-symbolic</property> - </object> <object class="GtkImage" id="clear_all_image"> <property name="visible">True</property> <property name="can_focus">False</property> @@ -28,47 +23,113 @@ <property name="can_focus">False</property> <property name="orientation">vertical</property> <child> - <object class="HdyHeaderBar"> + <object class="GtkStack" id="header_bars_stack"> <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="show_close_button">True</property> - <property name="title" bind-source="EphyDataDialog" bind-property="title"/> + <property name="transition-type">GTK_STACK_TRANSITION_TYPE_CROSSFADE</property> <child> - <object class="GtkButton" id="clear_all_button"> + <object class="HdyHeaderBar" id="window_header_bar"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="image">clear_all_image</property> - <property name="always_show_image">True</property> - <signal name="clicked" handler="on_clear_all_button_clicked" swapped="yes"/> - <accelerator key="Delete" modifiers="GDK_SHIFT_MASK" signal="clicked"/> - <style> - <class name="destructive-action"/> - <class name="image-button"/> - </style> + <property name="can_focus">False</property> + <property name="show_close_button">True</property> + <property name="title" bind-source="EphyDataDialog" bind-property="title"/> + <child> + <object class="GtkButton" id="selection_button"> + <property name="visible">True</property> + <property name="valign">center</property> + <property name="use-underline">True</property> + <signal name="clicked" handler="on_selection_button_clicked"/> + <style> + <class name="image-button"/> + </style> + <child internal-child="accessible"> + <object class="AtkObject" id="a11y-button3"> + <property name="accessible-name" translatable="yes">Select Items</property> + </object> + </child> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="icon-name">object-select-symbolic</property> + <property name="icon-size">1</property> + </object> + </child> + </object> + <packing> + <property name="pack-type">end</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="search_button"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="always_show_image">True</property> + <property name="active" bind-source="search_bar" bind-property="search-mode-enabled" bind-flags="sync-create|bidirectional"/> + <accelerator key="F" modifiers="GDK_CONTROL_MASK" signal="clicked"/> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="icon_name">edit-find-symbolic</property> + </object> + </child> + <child internal-child="accessible"> + <object class="AtkObject"> + <property name="AtkObject::accessible-name" translatable="yes">Search</property> + </object> + </child> + </object> + <packing> + <property name="pack_type">end</property> + </packing> + </child> </object> </child> <child> - <object class="GtkToggleButton" id="search_button"> + <object class="HdyHeaderBar" id="selection_header_bar"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="image">search_image</property> - <property name="always_show_image">True</property> - <property name="active" bind-source="search_bar" bind-property="search-mode-enabled" bind-flags="sync-create|bidirectional"/> - <accelerator key="F" modifiers="GDK_CONTROL_MASK" signal="clicked"/> + <property name="title" bind-source="EphyDataDialog" bind-property="title"/> <style> - <class name="image-button"/> + <class name="selection-mode"/> </style> - <child internal-child="accessible"> - <object class="AtkObject"> - <property name="AtkObject::accessible-name" translatable="yes">Search</property> + <child> + <object class="GtkButton" id="selection_cancel_button"> + <property name="visible">True</property> + <property name="valign">center</property> + <property name="use-underline">True</property> + <property name="label" translatable="yes">_Cancel</property> + <signal name="clicked" handler="on_selection_cancel_button_clicked"/> + <style> + <class name="text-button"/> + </style> + </object> + <packing> + <property name="pack-type">end</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="selection_search_button"> + <property name="visible">True</property> + <property name="active" bind-source="search_bar" bind-property="search-mode-enabled" bind-flags="sync-create|bidirectional"/> + <accelerator key="F" modifiers="GDK_CONTROL_MASK" signal="clicked"/> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="icon_name">edit-find-symbolic</property> + </object> + </child> + <child internal-child="accessible"> + <object class="AtkObject"> + <property name="AtkObject::accessible-name" translatable="yes">Search</property> + </object> + </child> </object> + <packing> + <property name="pack_type">end</property> + </packing> </child> </object> - <packing> - <property name="pack_type">end</property> - </packing> </child> </object> </child> @@ -104,7 +165,7 @@ </packing> </child> <child> - <object class="GtkStack" id="stack"> + <object class="GtkStack" id="data_presentation_stack"> <property name="can_focus">False</property> <property name="expand">True</property> <property name="visible">True</property> @@ -264,6 +325,57 @@ <property name="expand">True</property> </packing> </child> + <child> + <object class="GtkStack" id="action_bars_stack"> + <property name="visible">True</property> + <property name="transition-type">GTK_STACK_TRANSITION_TYPE_CROSSFADE</property> + <child> + <object class="GtkActionBar" id="regular_action_bar"> + <property name="visible">True</property> + <child> + <object class="GtkButton" id="clear_all_button"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use-underline">True</property> + <property name="label" translatable="yes">_Clear All</property> + <signal name="clicked" handler="on_clear_all_button_clicked" swapped="yes"/> + <accelerator key="Delete" modifiers="GDK_SHIFT_MASK" signal="clicked"/> + <style> + <class name="destructive-action"/> + <class name="image-button"/> + </style> + </object> + </child> + </object> + </child> + <child> + <object class="GtkActionBar" id="selection_action_bar"> + <property name="visible">True</property> + <child> + <object class="GtkBox" id="selection_actions_box"> + <property name="visible">True</property> + <property name="spacing">5</property> + <child> + <object class="GtkButton" id="selection_delete_button"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="sensitive">False</property> + <property name="use_underline">True</property> + <property name="label" translatable="yes">_Delete</property> + <signal name="clicked" handler="on_selection_delete_button_clicked"/> + <style> + <class name="destructive-action"/> + </style> + </object> + </child> + </object> + </child> + </object> + </child> + </object> + </child> </object> </child> </template> diff --git a/src/resources/gtk/history-dialog.ui b/src/resources/gtk/history-dialog.ui index 63375ddb5..03d90dfce 100644 --- a/src/resources/gtk/history-dialog.ui +++ b/src/resources/gtk/history-dialog.ui @@ -10,8 +10,13 @@ <property name="search_description" translatable="yes">Search history</property> <property name="empty_title" translatable="yes">The History is Empty</property> <property name="empty_description" translatable="yes">Visited pages will be listed here</property> + <property name="default-width">640</property> + <property name="default-height">800</property> <signal name="key-press-event" handler="on_key_press_event" swapped="no"/> + <signal name="key-release-event" handler="on_key_release_event"/> + <signal name="selection-delete-clicked" handler="on_dialog_selection_delete_clicked"/> <signal name="notify::search-text" handler="on_search_text_changed" swapped="yes"/> + <signal name="notify::selection-active" handler="on_dialog_selection_active_toggled"/> <child> <object class="GtkScrolledWindow" id="scrolled_window"> <property name="visible">True</property> @@ -30,12 +35,10 @@ <property name="margin_top">6</property> <property name="margin_bottom">6</property> <property name="valign">start</property> - <property name="selection_mode">multiple</property> - <property name="activate_on_single_click">False</property> - <signal name="button-press-event" handler="on_listbox_button_press_event" swapped="no"/> + <property name="selection_mode">none</property> + <property name="activate_on_single_click">True</property> <signal name="key-press-event" handler="on_listbox_key_press_event" swapped="no"/> - <signal name="row-activated" handler="on_listbox_row_activated" swapped="no"/> - <signal name="row-selected" handler="on_listbox_row_selected"/> + <signal name="row-activated" handler="on_listbox_row_activated"/> <style> <class name="content"/> </style> @@ -46,35 +49,4 @@ </object> </child> </template> - <object class="GtkMenu" id="popup_menu"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <child> - <object class="GtkMenuItem"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="action_name">history.open-selection</property> - <property name="label" translatable="yes">_Open</property> - <property name="use_underline">True</property> - </object> - </child> - <child> - <object class="GtkMenuItem"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="action_name">history.copy-url</property> - <property name="label" translatable="yes">_Copy Location</property> - <property name="use_underline">True</property> - </object> - </child> - <child> - <object class="GtkMenuItem"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="action_name">history.forget</property> - <property name="label" translatable="yes">_Delete</property> - <property name="use_underline">True</property> - </object> - </child> - </object> </interface> |