/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Copyright © 2003, 2004 Marco Pesenti Gritti * Copyright © 2003, 2004 Christian Persch * Copyright © 2012 Igalia S.L * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "config.h" #include "ephy-history-window.h" #include "ephy-bookmarks-ui.h" #include "ephy-debug.h" #include "ephy-dnd.h" #include "ephy-file-helpers.h" #include "ephy-gui.h" #include "ephy-hosts-store.h" #include "ephy-hosts-view.h" #include "ephy-prefs.h" #include "ephy-session.h" #include "ephy-settings.h" #include "ephy-shell.h" #include "ephy-initial-state.h" #include "ephy-time-helpers.h" #include "ephy-urls-store.h" #include "ephy-urls-view.h" #include "ephy-window.h" #include "window-commands.h" #include #include #include #include static const GtkTargetEntry page_drag_types [] = { { EPHY_DND_URL_TYPE, 0, 0 }, { EPHY_DND_URI_LIST_TYPE, 0, 1 }, { EPHY_DND_TEXT_TYPE, 0, 2 } }; static void ephy_history_window_constructed (GObject *object); static void ephy_history_window_class_init (EphyHistoryWindowClass *klass); static void ephy_history_window_init (EphyHistoryWindow *editor); static void ephy_history_window_finalize (GObject *object); static void ephy_history_window_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void ephy_history_window_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void ephy_history_window_dispose (GObject *object); static void cmd_open_bookmarks_in_tabs (GtkAction *action, EphyHistoryWindow *editor); static void cmd_open_bookmarks_in_browser (GtkAction *action, EphyHistoryWindow *editor); static void cmd_delete (GtkAction *action, EphyHistoryWindow *editor); static void cmd_bookmark_link (GtkAction *action, EphyHistoryWindow *editor); static void cmd_clear (GtkAction *action, EphyHistoryWindow *editor); static void cmd_close (GtkAction *action, EphyHistoryWindow *editor); static void cmd_cut (GtkAction *action, EphyHistoryWindow *editor); static void cmd_copy (GtkAction *action, EphyHistoryWindow *editor); static void cmd_paste (GtkAction *action, EphyHistoryWindow *editor); static void cmd_select_all (GtkAction *action, EphyHistoryWindow *editor); static void cmd_help_contents (GtkAction *action, EphyHistoryWindow *editor); static void search_entry_changed_cb (GtkWidget *entry, EphyHistoryWindow *editor); static void filter_now (EphyHistoryWindow *editor, gboolean hosts, gboolean pages); #define EPHY_HISTORY_WINDOW_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), EPHY_TYPE_HISTORY_WINDOW, EphyHistoryWindowPrivate)) struct _EphyHistoryWindowPrivate { EphyHistoryService *history_service; GCancellable *cancellable; GtkWidget *hosts_view; GtkWidget *pages_view; EphyURLsStore *urls_store; EphyHostsStore *hosts_store; GtkWidget *time_combo; GtkWidget *search_entry; GtkWidget *main_vbox; GtkWidget *window; GtkUIManager *ui_merge; GtkActionGroup *action_group; GtkWidget *confirmation_dialog; GtkTreeViewColumn *title_col; GtkTreeViewColumn *address_col; GtkTreeViewColumn *datetime_col; }; enum { PROP_0, PROP_HISTORY_SERVICE, }; static const GtkActionEntry ephy_history_ui_entries [] = { /* Toplevel */ { "File", NULL, N_("_File") }, { "Edit", NULL, N_("_Edit") }, { "View", NULL, N_("_View") }, { "Help", NULL, N_("_Help") }, { "PopupAction", NULL, "" }, /* File Menu */ { "OpenInWindow", NULL, N_("Open in New _Window"), "O", N_("Open the selected history link in a new window"), G_CALLBACK (cmd_open_bookmarks_in_browser) }, { "OpenInTab", NULL, N_("Open in New _Tab"), "O", N_("Open the selected history link in a new tab"), G_CALLBACK (cmd_open_bookmarks_in_tabs) }, { "BookmarkLink", NULL, N_("Add _Bookmark…"), "D", N_("Bookmark the selected history link"), G_CALLBACK (cmd_bookmark_link) }, { "Close", NULL, N_("_Close"), "W", N_("Close the history window"), G_CALLBACK (cmd_close) }, /* Edit Menu */ { "Cut", NULL, N_("Cu_t"), "X", N_("Cut the selection"), G_CALLBACK (cmd_cut) }, { "Copy", NULL, N_("_Copy"), "C", N_("Copy the selection"), G_CALLBACK (cmd_copy) }, { "Paste", NULL, N_("_Paste"), "V", N_("Paste the clipboard"), G_CALLBACK (cmd_paste) }, { "Delete", NULL, N_("_Delete"), "T", N_("Delete the selected history link"), G_CALLBACK (cmd_delete) }, { "SelectAll", NULL, N_("Select _All"), "A", N_("Select all history links or text"), G_CALLBACK (cmd_select_all) }, { "Clear", NULL, N_("Clear _History"), NULL, N_("Clear your browsing history"), G_CALLBACK (cmd_clear) }, /* Help Menu */ { "HelpContents", NULL, N_("_Contents"), "F1", N_("Display history help"), G_CALLBACK (cmd_help_contents) }, { "HelpAbout", NULL, N_("_About"), NULL, N_("Display credits for the web browser creators"), G_CALLBACK (window_cmd_help_about) }, }; typedef enum { VIEW_TITLE = 1 << 0, VIEW_ADDRESS = 1 << 1, VIEW_DATETIME = 1 << 2 } EphyHistoryWindowColumns; static const GtkToggleActionEntry ephy_history_toggle_entries [] = { /* View Menu */ { "ViewTitle", NULL, N_("_Title"), NULL, N_("Show the title column"), NULL, TRUE }, { "ViewAddress", NULL, N_("_Address"), NULL, N_("Show the address column"), NULL, TRUE }, { "ViewDateTime", NULL, N_("_Date and Time"), NULL, N_("Show the date and time column"), NULL, TRUE } }; static void confirmation_dialog_response_cb (GtkWidget *dialog, int response, EphyHistoryWindow *editor) { gtk_widget_destroy (dialog); if (response == GTK_RESPONSE_ACCEPT) { ephy_history_service_clear (editor->priv->history_service, NULL, NULL, NULL); filter_now (editor, TRUE, TRUE); } } static GtkWidget * confirmation_dialog_construct (EphyHistoryWindow *editor) { GtkWidget *dialog, *button, *image; dialog = gtk_message_dialog_new (GTK_WINDOW (editor), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_WARNING, GTK_BUTTONS_CANCEL, _("Clear browsing history?")); gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), _("Clearing the browsing history will cause all" " history links to be permanently deleted.")); gtk_window_group_add_window (ephy_gui_ensure_window_group (GTK_WINDOW (editor)), GTK_WINDOW (dialog)); button = gtk_button_new_with_mnemonic (_("Cl_ear")); image = gtk_image_new_from_stock (GTK_STOCK_CLEAR, GTK_ICON_SIZE_BUTTON); gtk_button_set_image (GTK_BUTTON (button), image); /* don't show the image! see bug #307818 */ gtk_widget_show (button); gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button, GTK_RESPONSE_ACCEPT); gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_CANCEL); gtk_window_set_title (GTK_WINDOW (dialog), _("Clear History")); g_signal_connect (dialog, "response", G_CALLBACK (confirmation_dialog_response_cb), editor); return dialog; } static EphyHistoryHost * get_selected_host (EphyHistoryWindow *editor) { GtkTreeSelection *selection; GtkTreeIter iter; GtkTreePath *path; GtkTreeModel *model; EphyHistoryHost *host = NULL; EphyHistoryWindowPrivate *priv = editor->priv; selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->hosts_view)); if (gtk_tree_selection_get_selected (selection, &model, &iter)) { path = gtk_tree_model_get_path (model, &iter); host = ephy_hosts_store_get_host_from_path (priv->hosts_store, path); gtk_tree_path_free (path); } return host; } static void cmd_clear (GtkAction *action, EphyHistoryWindow *editor) { if (editor->priv->confirmation_dialog == NULL) { GtkWidget **confirmation_dialog; editor->priv->confirmation_dialog = confirmation_dialog_construct (editor); confirmation_dialog = &editor->priv->confirmation_dialog; g_object_add_weak_pointer (G_OBJECT (editor->priv->confirmation_dialog), (gpointer *) confirmation_dialog); } gtk_widget_show (editor->priv->confirmation_dialog); } static void cmd_close (GtkAction *action, EphyHistoryWindow *editor) { if (editor->priv->confirmation_dialog != NULL) { gtk_widget_destroy (editor->priv->confirmation_dialog); } gtk_widget_hide (GTK_WIDGET (editor)); } static GtkWidget * get_target_window (EphyHistoryWindow *editor) { if (editor->priv->window) { return editor->priv->window; } else { return GTK_WIDGET (gtk_application_get_active_window (GTK_APPLICATION (ephy_shell_get_default ()))); } } static void cmd_open_bookmarks_in_tabs (GtkAction *action, EphyHistoryWindow *editor) { EphyWindow *window; GList *selection; GList *l; window = EPHY_WINDOW (get_target_window (editor)); selection = ephy_urls_view_get_selection (EPHY_URLS_VIEW (editor->priv->pages_view)); for (l = selection; l; l = l->next) { EphyHistoryURL *url = l->data; ephy_shell_new_tab (ephy_shell_get_default (), window, NULL, url->url, EPHY_NEW_TAB_OPEN_PAGE | EPHY_NEW_TAB_IN_EXISTING_WINDOW); } g_list_free_full (selection, (GDestroyNotify) ephy_history_url_free); } static void cmd_open_bookmarks_in_browser (GtkAction *action, EphyHistoryWindow *editor) { EphyWindow *window; GList *selection; GList *l; window = EPHY_WINDOW (get_target_window (editor)); selection = ephy_urls_view_get_selection (EPHY_URLS_VIEW (editor->priv->pages_view)); for (l = selection; l; l = l->next) { EphyHistoryURL *url = l->data; ephy_shell_new_tab (ephy_shell_get_default (), window, NULL, url->url, EPHY_NEW_TAB_OPEN_PAGE | EPHY_NEW_TAB_IN_NEW_WINDOW); } g_list_free_full (selection, (GDestroyNotify) ephy_history_url_free); } static void cmd_cut (GtkAction *action, EphyHistoryWindow *editor) { GtkWidget *widget = gtk_window_get_focus (GTK_WINDOW (editor)); if (GTK_IS_EDITABLE (widget)) { gtk_editable_cut_clipboard (GTK_EDITABLE (widget)); } } static void cmd_copy (GtkAction *action, EphyHistoryWindow *editor) { GtkWidget *widget = gtk_window_get_focus (GTK_WINDOW (editor)); if (GTK_IS_EDITABLE (widget)) { gtk_editable_copy_clipboard (GTK_EDITABLE (widget)); } else if (gtk_widget_is_focus (editor->priv->pages_view)) { GList *selection; selection = ephy_urls_view_get_selection (EPHY_URLS_VIEW (editor->priv->pages_view)); if (g_list_length (selection) == 1) { EphyHistoryURL *url = selection->data; gtk_clipboard_set_text (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD), url->url, -1); } g_list_free_full (selection, (GDestroyNotify) ephy_history_url_free); } } static void cmd_paste (GtkAction *action, EphyHistoryWindow *editor) { GtkWidget *widget = gtk_window_get_focus (GTK_WINDOW (editor)); if (GTK_IS_EDITABLE (widget)) { gtk_editable_paste_clipboard (GTK_EDITABLE (widget)); } } static void cmd_select_all (GtkAction *action, EphyHistoryWindow *editor) { GtkWidget *widget = gtk_window_get_focus (GTK_WINDOW (editor)); GtkWidget *pages_view = editor->priv->pages_view; if (GTK_IS_EDITABLE (widget)) { gtk_editable_select_region (GTK_EDITABLE (widget), 0, -1); } else if (gtk_widget_is_focus (pages_view)) { GtkTreeSelection *sel; sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (pages_view)); gtk_tree_selection_select_all (sel); } } static void on_browse_history_deleted_cb (gpointer service, gboolean success, gpointer result_data, gpointer user_data) { EphyHistoryWindow *editor = EPHY_HISTORY_WINDOW (user_data); if (success != TRUE) return; filter_now (editor, TRUE, TRUE); } static void on_host_deleted_cb (gpointer service, gboolean success, gpointer result_data, gpointer user_data) { EphyHistoryWindow *editor = EPHY_HISTORY_WINDOW (user_data); if (success != TRUE) return; filter_now (editor, TRUE, TRUE); } static void cmd_delete (GtkAction *action, EphyHistoryWindow *editor) { if (gtk_widget_is_focus (editor->priv->pages_view)) { GList *selected; selected = ephy_urls_view_get_selection (EPHY_URLS_VIEW (editor->priv->pages_view)); ephy_history_service_delete_urls (editor->priv->history_service, selected, editor->priv->cancellable, (EphyHistoryJobCallback)on_browse_history_deleted_cb, editor); } else if (gtk_widget_is_focus (editor->priv->hosts_view)) { EphyHistoryHost *host = get_selected_host (editor); if (host) { ephy_history_service_delete_host (editor->priv->history_service, host, editor->priv->cancellable, (EphyHistoryJobCallback)on_host_deleted_cb, editor); ephy_history_host_free (host); } } } static void cmd_bookmark_link (GtkAction *action, EphyHistoryWindow *editor) { GList *selection; selection = ephy_urls_view_get_selection (EPHY_URLS_VIEW (editor->priv->pages_view)); if (g_list_length (selection) == 1) { EphyHistoryURL *url; url = selection->data; ephy_bookmarks_ui_add_bookmark (GTK_WINDOW (editor), url->url, url->title); } g_list_free_full (selection, (GDestroyNotify) ephy_history_url_free); } static void cmd_help_contents (GtkAction *action, EphyHistoryWindow *editor) { ephy_gui_help (GTK_WIDGET (editor), "ephy-managing-history"); } G_DEFINE_TYPE (EphyHistoryWindow, ephy_history_window, GTK_TYPE_WINDOW) static void ephy_history_window_show (GtkWidget *widget) { EphyHistoryWindow *window = EPHY_HISTORY_WINDOW (widget); gtk_widget_grab_focus (window->priv->search_entry); GTK_WIDGET_CLASS (ephy_history_window_parent_class)->show (widget); } static void ephy_history_window_class_init (EphyHistoryWindowClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->finalize = ephy_history_window_finalize; object_class->set_property = ephy_history_window_set_property; object_class->get_property = ephy_history_window_get_property; object_class->dispose = ephy_history_window_dispose; object_class->constructed = ephy_history_window_constructed; widget_class->show = ephy_history_window_show; g_object_class_install_property (object_class, PROP_HISTORY_SERVICE, g_param_spec_object ("history-service", "History service", "History Service", EPHY_TYPE_HISTORY_SERVICE, G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | G_PARAM_CONSTRUCT_ONLY)); g_type_class_add_private (object_class, sizeof(EphyHistoryWindowPrivate)); } static void ephy_history_window_finalize (GObject *object) { EphyHistoryWindow *editor = EPHY_HISTORY_WINDOW (object); g_object_unref (editor->priv->action_group); g_object_unref (editor->priv->ui_merge); if (editor->priv->window) { GtkWidget **window = &editor->priv->window; g_object_remove_weak_pointer (G_OBJECT(editor->priv->window), (gpointer *)window); } G_OBJECT_CLASS (ephy_history_window_parent_class)->finalize (object); } static void ephy_history_window_row_activated_cb (GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *col, EphyHistoryWindow *editor) { EphyHistoryURL *url; url = ephy_urls_store_get_url_from_path (EPHY_URLS_STORE (gtk_tree_view_get_model (view)), path); g_return_if_fail (url != NULL); ephy_shell_new_tab (ephy_shell_get_default (), NULL, NULL, url->url, EPHY_NEW_TAB_OPEN_PAGE); ephy_history_url_free (url); } static void ephy_history_window_row_middle_clicked_cb (EphyHistoryView *view, GtkTreePath *path, EphyHistoryWindow *editor) { EphyWindow *window; EphyHistoryURL *url; window = EPHY_WINDOW (get_target_window (editor)); url = ephy_urls_store_get_url_from_path (editor->priv->urls_store, path); g_return_if_fail (url != NULL); ephy_shell_new_tab (ephy_shell_get_default (), window, NULL, url->url, EPHY_NEW_TAB_OPEN_PAGE | EPHY_NEW_TAB_IN_EXISTING_WINDOW); ephy_history_url_free (url); } static void ephy_history_window_update_menu (EphyHistoryWindow *editor) { gboolean open_in_window, open_in_tab; gboolean cut, copy, paste, select_all; gboolean pages_focus, pages_selection, single_page_selected; gboolean delete, bookmark_page; gboolean bookmarks_locked; int num_pages_selected; GtkActionGroup *action_group; GtkAction *action; char *open_in_window_label, *open_in_tab_label, *copy_label; GtkWidget *focus_widget; pages_focus = gtk_widget_is_focus (editor->priv->pages_view); num_pages_selected = gtk_tree_selection_count_selected_rows (gtk_tree_view_get_selection (GTK_TREE_VIEW (editor->priv->pages_view))); pages_selection = num_pages_selected > 0; single_page_selected = num_pages_selected == 1; focus_widget = gtk_window_get_focus (GTK_WINDOW (editor)); if (GTK_IS_EDITABLE (focus_widget)) { gboolean has_selection; has_selection = gtk_editable_get_selection_bounds (GTK_EDITABLE (focus_widget), NULL, NULL); cut = has_selection; copy = has_selection; paste = TRUE; select_all = TRUE; } else { cut = FALSE; copy = (pages_focus && single_page_selected); paste = FALSE; select_all = pages_focus; } open_in_window_label = ngettext ("Open in New _Window", "Open in New _Windows", num_pages_selected); open_in_tab_label = ngettext ("Open in New _Tab", "Open in New _Tabs", num_pages_selected); if (pages_focus) { copy_label = _("_Copy Address"); } else { copy_label = _("_Copy"); } open_in_window = (pages_focus && pages_selection); open_in_tab = (pages_focus && pages_selection); delete = (pages_focus && pages_selection); bookmarks_locked = g_settings_get_boolean (EPHY_SETTINGS_LOCKDOWN, EPHY_PREFS_LOCKDOWN_BOOKMARK_EDITING); bookmark_page = (pages_focus && single_page_selected) && !bookmarks_locked; action_group = editor->priv->action_group; action = gtk_action_group_get_action (action_group, "OpenInWindow"); gtk_action_set_sensitive (action, open_in_window); g_object_set (action, "label", open_in_window_label, NULL); action = gtk_action_group_get_action (action_group, "OpenInTab"); gtk_action_set_sensitive (action, open_in_tab); g_object_set (action, "label", open_in_tab_label, NULL); action = gtk_action_group_get_action (action_group, "Cut"); gtk_action_set_sensitive (action, cut); action = gtk_action_group_get_action (action_group, "Copy"); gtk_action_set_sensitive (action, copy); g_object_set (action, "label", copy_label, NULL); action = gtk_action_group_get_action (action_group, "Paste"); gtk_action_set_sensitive (action, paste); action = gtk_action_group_get_action (action_group, "SelectAll"); gtk_action_set_sensitive (action, select_all); action = gtk_action_group_get_action (action_group, "Delete"); gtk_action_set_sensitive (action, delete); action = gtk_action_group_get_action (action_group, "BookmarkLink"); gtk_action_set_sensitive (action, bookmark_page); } static void entry_selection_changed_cb (GtkWidget *widget, GParamSpec *pspec, EphyHistoryWindow *editor) { ephy_history_window_update_menu (editor); filter_now (editor, FALSE, TRUE); } static void add_entry_monitor (EphyHistoryWindow *editor, GtkWidget *entry) { g_signal_connect (G_OBJECT (entry), "notify::selection-bound", G_CALLBACK (entry_selection_changed_cb), editor); g_signal_connect (G_OBJECT (entry), "notify::cursor-position", G_CALLBACK (entry_selection_changed_cb), editor); } static gboolean view_focus_cb (GtkWidget *view, GdkEventFocus *event, EphyHistoryWindow *editor) { ephy_history_window_update_menu (editor); return FALSE; } static void add_focus_monitor (EphyHistoryWindow *editor, GtkWidget *widget) { g_signal_connect (G_OBJECT (widget), "focus_in_event", G_CALLBACK (view_focus_cb), editor); g_signal_connect (G_OBJECT (widget), "focus_out_event", G_CALLBACK (view_focus_cb), editor); } static void remove_focus_monitor (EphyHistoryWindow *editor, GtkWidget *widget) { g_signal_handlers_disconnect_by_func (G_OBJECT (widget), G_CALLBACK (view_focus_cb), editor); } static gboolean ephy_history_window_show_popup_cb (GtkWidget *view, EphyHistoryWindow *editor) { GtkWidget *widget; widget = gtk_ui_manager_get_widget (editor->priv->ui_merge, "/EphyHistoryWindowPopup"); ephy_history_view_popup (EPHY_HISTORY_VIEW (view), widget); return TRUE; } static gboolean key_pressed_cb (GtkWidget *view, GdkEventKey *event, EphyHistoryWindow *editor) { switch (event->keyval) { case GDK_KEY_Delete: case GDK_KEY_KP_Delete: cmd_delete (NULL, editor); return TRUE; default: break; } return FALSE; } static void search_entry_changed_cb (GtkWidget *entry, EphyHistoryWindow *editor) { filter_now (editor, FALSE, TRUE); } static void time_combo_changed_cb (GtkWidget *combo, EphyHistoryWindow *editor) { filter_now (editor, TRUE, TRUE); } static GtkWidget * build_search_box (EphyHistoryWindow *editor) { GtkWidget *box, *entry; GtkWidget *combo; char *str; box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); gtk_container_set_border_width (GTK_CONTAINER (box), 6); gtk_widget_show (box); entry = gtk_search_entry_new (); add_focus_monitor (editor, entry); add_entry_monitor (editor, entry); editor->priv->search_entry = entry; gtk_widget_show_all (entry); combo = gtk_combo_box_text_new (); gtk_widget_show (combo); gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo), _("Last 30 minutes")); gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo), _("Today")); str = g_strdup_printf (ngettext ("Last %d day", "Last %d days", 2), 2); gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo), str); g_free (str); str = g_strdup_printf (ngettext ("Last %d day", "Last %d days", 3), 3); gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo), str); g_free (str); /* keep this in sync with embed/ephy-history.c's * HISTORY_PAGE_OBSOLETE_DAYS */ str = g_strdup_printf (ngettext ("Last %d day", "Last %d days", 10), 10); gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo), str); g_free (str); gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo), _("All history")); g_settings_bind (EPHY_SETTINGS_STATE, EPHY_PREFS_STATE_HISTORY_DATE_FILTER, combo, "active", G_SETTINGS_BIND_DEFAULT); editor->priv->time_combo = combo; gtk_box_pack_start (GTK_BOX (box), entry, TRUE, TRUE, 0); gtk_box_pack_start (GTK_BOX (box), combo, FALSE, TRUE, 0); g_signal_connect (combo, "changed", G_CALLBACK (time_combo_changed_cb), editor); g_signal_connect (G_OBJECT (entry), "changed", G_CALLBACK (search_entry_changed_cb), editor); return box; } static void add_widget (GtkUIManager *merge, GtkWidget *widget, EphyHistoryWindow *editor) { gtk_box_pack_start (GTK_BOX (editor->priv->main_vbox), widget, FALSE, FALSE, 0); gtk_widget_show (widget); } static gboolean delete_event_cb (EphyHistoryWindow *editor) { gtk_widget_hide (GTK_WIDGET (editor)); return TRUE; } #if 0 static void provide_favicon (EphyNode *node, GValue *value, gpointer user_data) { const char *page_location; GdkPixbuf *pixbuf = NULL; page_location = ephy_node_get_property_string (node, EPHY_NODE_PAGE_PROP_LOCATION); LOG ("Get favicon for %s", page_location ? page_location : "None"); if (page_location) { /* No need to use the async version as this function will be called many times by the treeview. */ #ifdef HAVE_WEBKIT2 /* TODO: Favicons */ #else WebKitFaviconDatabase *database = webkit_get_favicon_database (); pixbuf = webkit_favicon_database_get_favicon_pixbuf (database, page_location, FAVICON_SIZE, FAVICON_SIZE); #endif } g_value_init (value, GDK_TYPE_PIXBUF); g_value_take_object (value, pixbuf); } #endif static void convert_cell_data_func (GtkTreeViewColumn *column, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data) { int col_id = GPOINTER_TO_INT (user_data); int value; time_t time; char *friendly; gtk_tree_model_get (model, iter, col_id, &value, -1); time = (time_t) value; friendly = ephy_time_helpers_utf_friendly_time (time); g_object_set (renderer, "text", friendly, NULL); g_free (friendly); } static void parse_time_into_date (GtkTreeViewColumn *column, int column_id) { GList *renderers_list; GtkCellRenderer *renderer; renderers_list = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column)); renderer = GTK_CELL_RENDERER (renderers_list->data); gtk_tree_view_column_set_cell_data_func (column, renderer, (GtkTreeCellDataFunc) convert_cell_data_func, GINT_TO_POINTER (column_id), NULL); g_list_free (renderers_list); } static void view_selection_changed_cb (GtkWidget *view, EphyHistoryWindow *editor) { ephy_history_window_update_menu (editor); filter_now (editor, FALSE, TRUE); } static void setup_time_filters (EphyHistoryWindow *editor, gint64 *from, gint64 *to) { time_t now, midnight, cmp_time = 0; struct tm btime; int time_range, days = 0; time_range = gtk_combo_box_get_active (GTK_COMBO_BOX (editor->priv->time_combo)); *from = *to = -1; /* no need to setup a new filter */ if (time_range == EPHY_PREFS_STATE_HISTORY_DATE_FILTER_EVER) return; now = time (NULL); if (localtime_r (&now, &btime) == NULL) return; /* get start of day */ btime.tm_sec = 0; btime.tm_min = 0; btime.tm_hour = 0; midnight = mktime (&btime); switch (time_range) { case EPHY_PREFS_STATE_HISTORY_DATE_FILTER_LAST_HALF_HOUR: cmp_time = now - 30 * 60; break; case EPHY_PREFS_STATE_HISTORY_DATE_FILTER_TODAY: cmp_time = midnight; break; case EPHY_PREFS_STATE_HISTORY_DATE_FILTER_LAST_TWO_DAYS: days = 1; cmp_time = midnight; break; case EPHY_PREFS_STATE_HISTORY_DATE_FILTER_LAST_THREE_DAYS: days = 2; cmp_time = midnight; break; case EPHY_PREFS_STATE_HISTORY_DATE_FILTER_LAST_TEN_DAYS: days = 9; cmp_time = midnight; break; default: g_return_if_reached (); break; } while (--days >= 0) { /* subtract 1 day */ cmp_time -= 43200; localtime_r (&cmp_time, &btime); btime.tm_sec = 0; btime.tm_min = 0; btime.tm_hour = 0; cmp_time = mktime (&btime); } *from = cmp_time; } static GList * substrings_filter (EphyHistoryWindow *editor) { const char *search_text; char **tokens, **p; GList *substrings = NULL; search_text = gtk_entry_get_text (GTK_ENTRY (editor->priv->search_entry)); tokens = p = g_strsplit (search_text, " ", -1); while (*p) { substrings = g_list_prepend (substrings, *p++); }; substrings = g_list_reverse (substrings); g_free (tokens); return substrings; } static void on_get_hosts_cb (gpointer service, gboolean success, gpointer result_data, gpointer user_data) { EphyHistoryWindow *window = EPHY_HISTORY_WINDOW (user_data); EphyHistoryHost *selected_host; GList *hosts = NULL; if (success != TRUE) goto out; hosts = (GList *) result_data; selected_host = get_selected_host (window); ephy_hosts_store_clear (EPHY_HOSTS_STORE (window->priv->hosts_store)); ephy_hosts_store_add_hosts (window->priv->hosts_store, hosts); ephy_hosts_view_select_host (EPHY_HOSTS_VIEW (window->priv->hosts_view), selected_host); ephy_history_host_free (selected_host); out: g_list_free_full (hosts, (GDestroyNotify)ephy_history_host_free); } static void on_find_urls_cb (gpointer service, gboolean success, gpointer result_data, gpointer user_data) { EphyHistoryWindow *window = EPHY_HISTORY_WINDOW (user_data); GList *urls; if (success != TRUE) return; urls = (GList *)result_data; gtk_list_store_clear (GTK_LIST_STORE (window->priv->urls_store)); ephy_urls_store_add_urls (window->priv->urls_store, urls); g_list_free_full (urls, (GDestroyNotify)ephy_history_url_free); } static void filter_now (EphyHistoryWindow *editor, gboolean hosts, gboolean pages) { gint64 from, to; EphyHistoryHost *host; GList *substrings; setup_time_filters (editor, &from, &to); substrings = substrings_filter (editor); if (hosts) { ephy_history_service_find_hosts (editor->priv->history_service, from, to, editor->priv->cancellable, (EphyHistoryJobCallback) on_get_hosts_cb, editor); } if (pages) { host = get_selected_host (editor); ephy_history_service_find_urls (editor->priv->history_service, from, to, 0, host ? host->id : 0, substrings, editor->priv->cancellable, (EphyHistoryJobCallback)on_find_urls_cb, editor); ephy_history_host_free (host); } } static gboolean on_urls_visited_cb (EphyHistoryService *service, EphyHistoryWindow *editor) { filter_now (editor, TRUE, TRUE); return FALSE; } static void ephy_history_window_constructed (GObject *object) { GtkTreeViewColumn *col; GtkTreeSelection *selection; GtkWidget *vbox, *hpaned; GtkWidget *pages_view, *hosts_view; GtkWidget *scrolled_window; EphyURLsStore *urls_store; EphyHostsStore *hosts_store; GtkUIManager *ui_merge; GtkActionGroup *action_group; GtkAction *action; EphyHistoryWindow *editor = EPHY_HISTORY_WINDOW (object); ephy_gui_ensure_window_group (GTK_WINDOW (editor)); gtk_window_set_title (GTK_WINDOW (editor), _("History")); g_signal_connect (editor, "delete_event", G_CALLBACK (delete_event_cb), NULL); editor->priv->main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); gtk_widget_show (editor->priv->main_vbox); gtk_container_add (GTK_CONTAINER (editor), editor->priv->main_vbox); ui_merge = gtk_ui_manager_new (); g_signal_connect (ui_merge, "add_widget", G_CALLBACK (add_widget), editor); gtk_window_add_accel_group (GTK_WINDOW (editor), gtk_ui_manager_get_accel_group (ui_merge)); action_group = gtk_action_group_new ("PopupActions"); gtk_action_group_set_translation_domain (action_group, NULL); gtk_action_group_add_actions (action_group, ephy_history_ui_entries, G_N_ELEMENTS (ephy_history_ui_entries), editor); gtk_action_group_add_toggle_actions (action_group, ephy_history_toggle_entries, G_N_ELEMENTS (ephy_history_toggle_entries), editor); gtk_ui_manager_insert_action_group (ui_merge, action_group, 0); gtk_ui_manager_add_ui_from_resource (ui_merge, "/org/gnome/epiphany/epiphany-history-window-ui.xml", NULL); gtk_ui_manager_ensure_update (ui_merge); editor->priv->ui_merge = ui_merge; editor->priv->action_group = action_group; hpaned = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL); gtk_container_set_border_width (GTK_CONTAINER (hpaned), 0); gtk_box_pack_end (GTK_BOX (editor->priv->main_vbox), hpaned, TRUE, TRUE, 0); gtk_widget_show (hpaned); /* Sites View */ scrolled_window = g_object_new (GTK_TYPE_SCROLLED_WINDOW, "hadjustment", NULL, "vadjustment", NULL, "hscrollbar_policy", GTK_POLICY_AUTOMATIC, "vscrollbar_policy", GTK_POLICY_AUTOMATIC, "shadow_type", GTK_SHADOW_IN, NULL); gtk_paned_pack1 (GTK_PANED (hpaned), scrolled_window, TRUE, FALSE); gtk_widget_show (scrolled_window); hosts_store = ephy_hosts_store_new (); hosts_view = ephy_hosts_view_new (); gtk_tree_view_set_model (GTK_TREE_VIEW (hosts_view), GTK_TREE_MODEL (hosts_store)); add_focus_monitor (editor, hosts_view); selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (hosts_view)); gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE); gtk_container_add (GTK_CONTAINER (scrolled_window), hosts_view); gtk_widget_show (hosts_view); editor->priv->hosts_view = hosts_view; g_signal_connect (G_OBJECT (hosts_view), "key_press_event", G_CALLBACK (key_pressed_cb), editor); g_signal_connect (G_OBJECT (selection), "changed", G_CALLBACK (view_selection_changed_cb), editor); vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); gtk_paned_pack2 (GTK_PANED (hpaned), vbox, TRUE, FALSE); gtk_widget_show (vbox); gtk_box_pack_start (GTK_BOX (vbox), build_search_box (editor), FALSE, FALSE, 0); /* Pages View */ scrolled_window = g_object_new (GTK_TYPE_SCROLLED_WINDOW, "hadjustment", NULL, "vadjustment", NULL, "hscrollbar_policy", GTK_POLICY_AUTOMATIC, "vscrollbar_policy", GTK_POLICY_AUTOMATIC, "shadow_type", GTK_SHADOW_IN, NULL); gtk_box_pack_start (GTK_BOX (vbox), scrolled_window, TRUE, TRUE, 0); gtk_widget_show (scrolled_window); editor->priv->pages_view = pages_view = ephy_urls_view_new (); urls_store = ephy_urls_store_new (); gtk_tree_view_set_model (GTK_TREE_VIEW (pages_view), GTK_TREE_MODEL (urls_store)); add_focus_monitor (editor, pages_view); selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (pages_view)); gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (pages_view), TRUE); /* These three blocks should most likely go into EphyHistoryView. */ col = gtk_tree_view_get_column (GTK_TREE_VIEW (pages_view), 0); gtk_tree_view_column_set_min_width (col, 300); gtk_tree_view_column_set_resizable (col, TRUE); editor->priv->title_col = col; col = gtk_tree_view_get_column (GTK_TREE_VIEW (pages_view), 1); gtk_tree_view_column_set_min_width (col, 300); gtk_tree_view_column_set_resizable (col, TRUE); editor->priv->address_col = col; col = gtk_tree_view_get_column (GTK_TREE_VIEW (pages_view), 2); editor->priv->datetime_col = col; parse_time_into_date (editor->priv->datetime_col, 2); gtk_container_add (GTK_CONTAINER (scrolled_window), pages_view); gtk_widget_show (pages_view); editor->priv->pages_view = pages_view; editor->priv->urls_store = urls_store; editor->priv->hosts_store = hosts_store; action = gtk_action_group_get_action (action_group, "ViewTitle"); g_settings_bind (EPHY_SETTINGS_STATE, EPHY_PREFS_STATE_HISTORY_VIEW_TITLE, action, "active", G_SETTINGS_BIND_DEFAULT); g_settings_bind (EPHY_SETTINGS_STATE, EPHY_PREFS_STATE_HISTORY_VIEW_TITLE, editor->priv->title_col, "visible", G_SETTINGS_BIND_DEFAULT); action = gtk_action_group_get_action (action_group, "ViewAddress"); g_settings_bind (EPHY_SETTINGS_STATE, EPHY_PREFS_STATE_HISTORY_VIEW_ADDRESS, action, "active", G_SETTINGS_BIND_DEFAULT); g_settings_bind (EPHY_SETTINGS_STATE, EPHY_PREFS_STATE_HISTORY_VIEW_ADDRESS, editor->priv->address_col, "visible", G_SETTINGS_BIND_DEFAULT); action = gtk_action_group_get_action (action_group, "ViewDateTime"); g_settings_bind (EPHY_SETTINGS_STATE, EPHY_PREFS_STATE_HISTORY_VIEW_DATE, action, "active", G_SETTINGS_BIND_DEFAULT); g_settings_bind (EPHY_SETTINGS_STATE, EPHY_PREFS_STATE_HISTORY_VIEW_DATE, editor->priv->datetime_col, "visible", G_SETTINGS_BIND_DEFAULT); g_signal_connect (G_OBJECT (pages_view), "row-activated", G_CALLBACK (ephy_history_window_row_activated_cb), editor); g_signal_connect (pages_view, "row-middle-clicked", G_CALLBACK (ephy_history_window_row_middle_clicked_cb), editor); g_signal_connect (G_OBJECT (pages_view), "popup_menu", G_CALLBACK (ephy_history_window_show_popup_cb), editor); g_signal_connect (G_OBJECT (pages_view), "key_press_event", G_CALLBACK (key_pressed_cb), editor); ephy_initial_state_add_window (GTK_WIDGET (editor), "history_window", 450, 400, FALSE, EPHY_INITIAL_STATE_WINDOW_SAVE_SIZE | EPHY_INITIAL_STATE_WINDOW_SAVE_POSITION); ephy_initial_state_add_paned (GTK_WIDGET (hpaned), "history_paned", 130); editor->priv->cancellable = g_cancellable_new (); filter_now (editor, TRUE, TRUE); g_signal_connect_after (editor->priv->history_service, "urls-visited", G_CALLBACK (on_urls_visited_cb), editor); if (G_OBJECT_CLASS (ephy_history_window_parent_class)->constructed) G_OBJECT_CLASS (ephy_history_window_parent_class)->constructed (object); } void ephy_history_window_set_parent (EphyHistoryWindow *ebe, GtkWidget *window) { GtkWidget **widget; if (ebe->priv->window) { widget = &ebe->priv->window; g_object_remove_weak_pointer (G_OBJECT(ebe->priv->window), (gpointer *)widget); } ebe->priv->window = window; widget = &ebe->priv->window; g_object_add_weak_pointer (G_OBJECT(ebe->priv->window), (gpointer *)widget); } GtkWidget * ephy_history_window_new (EphyHistoryService *history_service) { EphyHistoryWindow *editor; g_return_val_if_fail (history_service != NULL, NULL); editor = g_object_new (EPHY_TYPE_HISTORY_WINDOW, "history-service", history_service, NULL); return GTK_WIDGET (editor); } static void ephy_history_window_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { EphyHistoryWindow *editor = EPHY_HISTORY_WINDOW (object); switch (prop_id) { case PROP_HISTORY_SERVICE: editor->priv->history_service = g_value_get_object (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void ephy_history_window_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { EphyHistoryWindow *editor = EPHY_HISTORY_WINDOW (object); switch (prop_id) { case PROP_HISTORY_SERVICE: g_value_set_object (value, editor->priv->history_service); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void ephy_history_window_init (EphyHistoryWindow *editor) { editor->priv = EPHY_HISTORY_WINDOW_GET_PRIVATE (editor); } static void ephy_history_window_dispose (GObject *object) { EphyHistoryWindow *editor = EPHY_HISTORY_WINDOW (object); if (editor->priv->hosts_view != NULL) { remove_focus_monitor (editor, editor->priv->pages_view); remove_focus_monitor (editor, editor->priv->hosts_view); remove_focus_monitor (editor, editor->priv->search_entry); editor->priv->hosts_view = NULL; } if (editor->priv->cancellable) { g_cancellable_cancel (editor->priv->cancellable); g_clear_object (&editor->priv->cancellable); } G_OBJECT_CLASS (ephy_history_window_parent_class)->dispose (object); }