/* * GNOME Logs - View and search logs * Copyright (C) 2013, 2014, 2015 Red Hat, Inc. * * 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 3 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, see . */ #include "gl-eventviewlist.h" #include #include #include #include #include "gl-categorylist.h" #include "gl-enums.h" #include "gl-eventtoolbar.h" #include "gl-eventviewdetail.h" #include "gl-eventviewrow.h" #include "gl-journal-model.h" #include "gl-util.h" #include "gl-searchpopover.h" struct _GlEventViewList { /*< private >*/ GtkBox parent_instance; }; typedef struct { GlJournalModel *journal_model; GlRowEntry *entry; GlUtilClockFormat clock_format; GtkListBox *entries_box; GtkSizeGroup *category_sizegroup; GtkWidget *categories; GtkWidget *event_search; GtkWidget *event_scrolled; GtkWidget *search_entry; GtkWidget *search_dropdown_button; GtkWidget *search_popover; GlSearchPopoverJournalFieldFilter journal_search_field; GlQuerySearchType search_type; GlSearchPopoverJournalTimestampRange journal_timestamp_range; gchar *search_text; gchar *boot_match; } GlEventViewListPrivate; G_DEFINE_TYPE_WITH_PRIVATE (GlEventViewList, gl_event_view_list, GTK_TYPE_BOX) static const gchar DESKTOP_SCHEMA[] = "org.gnome.desktop.interface"; static const gchar SETTINGS_SCHEMA[] = "org.gnome.Logs"; static const gchar CLOCK_FORMAT[] = "clock-format"; static const gchar SORT_ORDER[] = "sort-order"; GlJournalModel * gl_event_view_list_get_journal_model (GlEventViewList *view) { GlEventViewListPrivate *priv; priv = gl_event_view_list_get_instance_private (view); return priv->journal_model; } GtkWidget * gl_event_view_list_get_category_list (GlEventViewList *view) { GlEventViewListPrivate *priv; priv = gl_event_view_list_get_instance_private (view); return priv->categories; } const gchar * gl_event_view_list_get_boot_match (GlEventViewList *view) { GlEventViewListPrivate *priv; priv = gl_event_view_list_get_instance_private (view); return priv->boot_match; } gchar * gl_event_view_list_get_output_logs (GlEventViewList *view) { gchar *output_buf = NULL; gint index = 0; GOutputStream *stream; GlEventViewListPrivate *priv; priv = gl_event_view_list_get_instance_private (view); stream = g_memory_output_stream_new_resizable (); while (gtk_list_box_get_row_at_index (GTK_LIST_BOX (priv->entries_box), index) != NULL) { const gchar *comm; const gchar *message; gchar *output_text; gchar *time; GDateTime *now; guint64 timestamp; GtkListBoxRow *row; row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (priv->entries_box), index); /* Only output search results. * Search results are entries that are visible and child visible */ if (gtk_widget_get_mapped (GTK_WIDGET (row)) == FALSE || gtk_widget_get_visible (GTK_WIDGET (row)) == FALSE) { index++; continue; } comm = gl_event_view_row_get_command_line (GL_EVENT_VIEW_ROW (row)); message = gl_event_view_row_get_message (GL_EVENT_VIEW_ROW (row)); timestamp = gl_event_view_row_get_timestamp (GL_EVENT_VIEW_ROW (row)); now = g_date_time_new_now_local (); time = gl_util_timestamp_to_display (timestamp, now, priv->clock_format, TRUE); output_text = g_strconcat (time, " ", comm ? comm : "kernel", ": ", message, "\n", NULL); index++; g_output_stream_write (stream, output_text, strlen (output_text), NULL, NULL); g_date_time_unref (now); g_free (time); g_free (output_text); } output_buf = g_memory_output_stream_get_data (G_MEMORY_OUTPUT_STREAM (stream)); g_output_stream_close (stream, NULL, NULL); return output_buf; } static void listbox_update_header_func (GtkListBoxRow *row, GtkListBoxRow *before, gpointer user_data) { GtkWidget *current; GlRowEntry *row_entry; if (before == NULL) { gtk_list_box_row_set_header (row, NULL); return; } current = gtk_list_box_row_get_header (row); row_entry = gl_event_view_row_get_entry (GL_EVENT_VIEW_ROW (row)); if (! (gl_row_entry_get_row_type (row_entry) == GL_ROW_ENTRY_TYPE_COMPRESSED)) { if (current == NULL) { current = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL); gtk_list_box_row_set_header (row, current); } } row_entry = gl_event_view_row_get_entry (GL_EVENT_VIEW_ROW (before)); if (gl_row_entry_get_row_type (row_entry) == GL_ROW_ENTRY_TYPE_HEADER) { if (current == NULL) { current = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL); gtk_list_box_row_set_header (row, current); } } } static void popover_closed (GtkPopover *popover) { gtk_widget_unparent (GTK_WIDGET (popover)); } static void on_listbox_row_activated (GtkListBox *listbox, GtkListBoxRow *row, GlEventViewList *view) { GlEventViewListPrivate *priv; priv = gl_event_view_list_get_instance_private (view); priv->entry = gl_event_view_row_get_entry (GL_EVENT_VIEW_ROW (row)); if (gl_row_entry_get_row_type (priv->entry) == GL_ROW_ENTRY_TYPE_HEADER) { guint compressed_entries; gint header_row_index; gint index; gboolean rows_expanded; GtkStyleContext *context; GtkListBoxRow *first_border_row; GtkListBoxRow *last_border_row; GtkWidget *row_separator; rows_expanded = FALSE; compressed_entries = gl_row_entry_get_compressed_entries (priv->entry); header_row_index = gtk_list_box_row_get_index (row); for (index = header_row_index + 1; compressed_entries != 0; index++) { GtkListBoxRow *compressed_row; compressed_row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (priv->entries_box), index); context = gtk_widget_get_style_context (GTK_WIDGET (compressed_row)); gtk_style_context_add_class (context, "compressed-row"); /* Toggle the visibility */ if (gtk_widget_get_visible (GTK_WIDGET (compressed_row))) { gtk_widget_hide (GTK_WIDGET (compressed_row)); rows_expanded = FALSE; } else { gtk_widget_show (GTK_WIDGET (compressed_row)); rows_expanded = TRUE; } compressed_entries--; } first_border_row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (priv->entries_box), header_row_index + 1); last_border_row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (priv->entries_box), index); context = gtk_widget_get_style_context (GTK_WIDGET (row)); /* Set background color of group separator */ if (rows_expanded) { gtk_style_context_add_class (context, "compressed-row-header"); row_separator = gtk_list_box_row_get_header (row); if (row_separator) { context = gtk_widget_get_style_context (row_separator); gtk_style_context_add_class (context, "compressed-rows-group-separator"); } row_separator = gtk_list_box_row_get_header (first_border_row); context = gtk_widget_get_style_context (row_separator); gtk_style_context_add_class (context, "compressed-rows-group-separator"); if (last_border_row) { row_separator = gtk_list_box_row_get_header (last_border_row); context = gtk_widget_get_style_context (row_separator); gtk_style_context_add_class (context, "compressed-rows-group-separator"); } } else { gtk_style_context_remove_class (context, "compressed-row-header"); row_separator = gtk_list_box_row_get_header (row); if (row_separator) { GlRowEntry *previous_row_entry; GtkListBoxRow *previous_row; previous_row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (priv->entries_box), header_row_index - 1); previous_row_entry = gl_event_view_row_get_entry (GL_EVENT_VIEW_ROW (previous_row)); /* Check if previous row is part of a compressed group and is visible */ if (gl_row_entry_get_row_type (previous_row_entry) == GL_ROW_ENTRY_TYPE_COMPRESSED) { /* If not visible, remove the style class from it's separator */ if (!gtk_widget_get_visible (GTK_WIDGET (previous_row))) { context = gtk_widget_get_style_context (row_separator); gtk_style_context_remove_class (context, "compressed-rows-group-separator"); } } else { context = gtk_widget_get_style_context (row_separator); gtk_style_context_remove_class (context, "compressed-rows-group-separator"); } } if (last_border_row) { GlRowEntry *border_row_entry; border_row_entry = gl_event_view_row_get_entry (GL_EVENT_VIEW_ROW (last_border_row)); /* Check if this border row is a compressed header row and is expanded */ if (gl_row_entry_get_row_type (border_row_entry) == GL_ROW_ENTRY_TYPE_HEADER) { GtkListBoxRow *next_row; next_row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (priv->entries_box), index + 1); /* If not expanded, remove the style class from it's separator */ if (!gtk_widget_get_visible (GTK_WIDGET (next_row))) { row_separator = gtk_list_box_row_get_header (last_border_row); context = gtk_widget_get_style_context (row_separator); gtk_style_context_remove_class (context, "compressed-rows-group-separator"); } } else { row_separator = gtk_list_box_row_get_header (last_border_row); context = gtk_widget_get_style_context (row_separator); gtk_style_context_remove_class (context, "compressed-rows-group-separator"); } } } } else { GtkWidget *event_detail_popover; GtkWidget *category_label; GtkWidget *time_label; event_detail_popover = gl_event_view_detail_new (priv->entry, priv->clock_format); gtk_widget_set_parent (event_detail_popover, GTK_WIDGET (row)); category_label = gl_event_view_row_get_category_label (GL_EVENT_VIEW_ROW (row)); if (category_label) gtk_widget_remove_css_class (category_label, "dim-label"); time_label = gl_event_view_row_get_time_label (GL_EVENT_VIEW_ROW (row)); gtk_widget_remove_css_class (time_label, "dim-label"); gtk_widget_add_css_class (GTK_WIDGET (row), "popover-activated-row"); g_signal_connect (event_detail_popover, "closed", G_CALLBACK (popover_closed), NULL); gtk_popover_popup (GTK_POPOVER (event_detail_popover)); } } GlRowEntry * gl_event_view_list_get_detail_entry (GlEventViewList *view) { GlEventViewListPrivate *priv; priv = gl_event_view_list_get_instance_private (view); return priv->entry; } gchar * gl_event_view_list_get_boot_time (GlEventViewList *view, const gchar *boot_match) { GlEventViewListPrivate *priv; priv = gl_event_view_list_get_instance_private (view); return gl_journal_model_get_boot_time (priv->journal_model, boot_match); } GArray * gl_event_view_list_get_boot_ids (GlEventViewList *view) { GlEventViewListPrivate *priv; priv = gl_event_view_list_get_instance_private (view); return gl_journal_model_get_boot_ids (priv->journal_model); } void gl_event_view_list_set_search_mode (GlEventViewList *view, gboolean state) { GlEventViewListPrivate *priv; g_return_if_fail (GL_EVENT_VIEW_LIST (view)); priv = gl_event_view_list_get_instance_private (view); gtk_search_bar_set_search_mode (GTK_SEARCH_BAR (priv->event_search), state); if (state) { gtk_widget_grab_focus (priv->search_entry); gtk_editable_set_position (GTK_EDITABLE (priv->search_entry), -1); } else { gtk_editable_set_text (GTK_EDITABLE (priv->search_entry), ""); } } static GtkWidget * gl_event_view_create_empty (G_GNUC_UNUSED GlEventViewList *view) { GtkWidget *status_page; status_page = adw_status_page_new (); gtk_widget_set_hexpand (status_page, TRUE); adw_status_page_set_icon_name (ADW_STATUS_PAGE (status_page), "action-unavailable-symbolic"); /* Translators: Shown when there are no (zero) results in the current * view. */ adw_status_page_set_title (ADW_STATUS_PAGE (status_page), _("No Results")); return status_page; } static GtkWidget * gl_event_list_view_create_row_widget (gpointer item, gpointer user_data) { GtkWidget *rtn; GlCategoryList *list; GlCategoryListFilter filter; GlEventViewList *view = user_data; GlEventViewListPrivate *priv = gl_event_view_list_get_instance_private (view); list = GL_CATEGORY_LIST (priv->categories); filter = gl_category_list_get_category (list); if (filter == GL_CATEGORY_LIST_FILTER_IMPORTANT) { GtkWidget *category_label; rtn = gl_event_view_row_new (item, priv->clock_format, GL_EVENT_VIEW_ROW_CATEGORY_IMPORTANT); category_label = gl_event_view_row_get_category_label (GL_EVENT_VIEW_ROW (rtn)); gtk_size_group_add_widget (GTK_SIZE_GROUP (priv->category_sizegroup), category_label); } else { rtn = gl_event_view_row_new (item, priv->clock_format, GL_EVENT_VIEW_ROW_CATEGORY_NONE); } return rtn; } static gchar * get_uid_match_field_value (void) { GCredentials *creds; uid_t uid; gchar *str = NULL; creds = g_credentials_new (); uid = g_credentials_get_unix_user (creds, NULL); if (uid != -1) str = g_strdup_printf ("%d", uid); g_object_unref (creds); return str; } /* Get Boot ID for current boot match */ static gchar * get_current_boot_id (const gchar *boot_match) { g_return_val_if_fail (boot_match != NULL, NULL); gchar *boot_value; boot_value = strchr (boot_match, '=') + 1; return g_strdup (boot_value); } static void query_add_category_matches (GlQuery *query, GlCategoryList *list) { GlCategoryListFilter filter; /* Add exact matches according to selected category */ filter = gl_category_list_get_category (list); switch (filter) { case GL_CATEGORY_LIST_FILTER_IMPORTANT: { /* Alert or emergency priority. */ gl_query_add_match (query, "PRIORITY", "0", GL_QUERY_SEARCH_TYPE_EXACT); gl_query_add_match (query, "PRIORITY", "1", GL_QUERY_SEARCH_TYPE_EXACT); gl_query_add_match (query, "PRIORITY", "2", GL_QUERY_SEARCH_TYPE_EXACT); gl_query_add_match (query, "PRIORITY", "3", GL_QUERY_SEARCH_TYPE_EXACT); } break; case GL_CATEGORY_LIST_FILTER_ALL: { } break; case GL_CATEGORY_LIST_FILTER_APPLICATIONS: /* Allow all _TRANSPORT != kernel. Attempt to filter by only processes * owned by the same UID. */ { gchar *uid_str; uid_str = get_uid_match_field_value (); gl_query_add_match (query, "_TRANSPORT", "journal", GL_QUERY_SEARCH_TYPE_EXACT); gl_query_add_match (query, "_TRANSPORT", "stdout", GL_QUERY_SEARCH_TYPE_EXACT); gl_query_add_match (query, "_TRANSPORT", "syslog", GL_QUERY_SEARCH_TYPE_EXACT); gl_query_add_match (query, "_UID", uid_str, GL_QUERY_SEARCH_TYPE_EXACT); g_free (uid_str); } break; case GL_CATEGORY_LIST_FILTER_SYSTEM: { gl_query_add_match (query, "_TRANSPORT", "kernel", GL_QUERY_SEARCH_TYPE_EXACT); } break; case GL_CATEGORY_LIST_FILTER_HARDWARE: { gl_query_add_match (query, "_TRANSPORT", "kernel", GL_QUERY_SEARCH_TYPE_EXACT); gl_query_add_match ( query, "_KERNEL_DEVICE", NULL, GL_QUERY_SEARCH_TYPE_EXACT); } break; case GL_CATEGORY_LIST_FILTER_SECURITY: { gl_query_add_match (query, "_AUDIT_SESSION", NULL, GL_QUERY_SEARCH_TYPE_EXACT); } break; default: g_assert_not_reached (); } } static void query_add_search_matches (GlQuery *query, const gchar *search_text, GlSearchPopoverJournalFieldFilter journal_search_field, GlQuerySearchType search_type) { switch (journal_search_field) { case GL_SEARCH_POPOVER_JOURNAL_FIELD_FILTER_ALL_AVAILABLE_FIELDS: gl_query_add_match (query, "_PID", search_text, GL_QUERY_SEARCH_TYPE_SUBSTRING); gl_query_add_match (query, "_UID", search_text, GL_QUERY_SEARCH_TYPE_SUBSTRING); gl_query_add_match (query, "_GID", search_text, GL_QUERY_SEARCH_TYPE_SUBSTRING); gl_query_add_match (query, "MESSAGE", search_text, GL_QUERY_SEARCH_TYPE_SUBSTRING); gl_query_add_match (query, "_COMM", search_text, GL_QUERY_SEARCH_TYPE_SUBSTRING); gl_query_add_match (query, "_SYSTEMD_UNIT", search_text, GL_QUERY_SEARCH_TYPE_SUBSTRING); gl_query_add_match (query, "_KERNEL_DEVICE", search_text, GL_QUERY_SEARCH_TYPE_SUBSTRING); gl_query_add_match (query, "_AUDIT_SESSION", search_text, GL_QUERY_SEARCH_TYPE_SUBSTRING); gl_query_add_match (query, "_EXE", search_text, GL_QUERY_SEARCH_TYPE_SUBSTRING); break; case GL_SEARCH_POPOVER_JOURNAL_FIELD_FILTER_PID: gl_query_add_match (query, "_PID", search_text, search_type); break; case GL_SEARCH_POPOVER_JOURNAL_FIELD_FILTER_UID: gl_query_add_match (query, "_UID", search_text, search_type); break; case GL_SEARCH_POPOVER_JOURNAL_FIELD_FILTER_GID: gl_query_add_match (query, "_GID", search_text, search_type); break; case GL_SEARCH_POPOVER_JOURNAL_FIELD_FILTER_MESSAGE: gl_query_add_match (query, "MESSAGE", search_text, search_type); break; case GL_SEARCH_POPOVER_JOURNAL_FIELD_FILTER_PROCESS_NAME: gl_query_add_match (query, "_COMM", search_text, search_type); break; case GL_SEARCH_POPOVER_JOURNAL_FIELD_FILTER_SYSTEMD_UNIT: gl_query_add_match (query, "_SYSTEMD_UNIT", search_text, search_type); break; case GL_SEARCH_POPOVER_JOURNAL_FIELD_FILTER_KERNEL_DEVICE: gl_query_add_match (query, "_KERNEL_DEVICE", search_text, search_type); break; case GL_SEARCH_POPOVER_JOURNAL_FIELD_FILTER_AUDIT_SESSION: gl_query_add_match (query, "_AUDIT_SESSION", search_text, search_type); break; case GL_SEARCH_POPOVER_JOURNAL_FIELD_FILTER_EXECUTABLE_PATH: gl_query_add_match (query, "_EXE", search_text, search_type); break; } } static void query_set_day_timestamps (GlQuery *query, gint start_day_offset, gint end_day_offset) { GDateTime *now; GDateTime *today_start; GDateTime *today_end; guint64 start_timestamp; guint64 end_timestamp; now = g_date_time_new_now_local(); today_start = g_date_time_new_local (g_date_time_get_year (now), g_date_time_get_month (now), g_date_time_get_day_of_month (now) - start_day_offset, 23, 59, 59.0); start_timestamp = g_date_time_to_unix (today_start) * G_USEC_PER_SEC; today_end = g_date_time_new_local (g_date_time_get_year (now), g_date_time_get_month (now), g_date_time_get_day_of_month (now) - end_day_offset, 0, 0, 0.0); end_timestamp = g_date_time_to_unix (today_end) * G_USEC_PER_SEC; gl_query_set_journal_timestamp_range (query, start_timestamp, end_timestamp); g_date_time_unref (now); g_date_time_unref (today_start); g_date_time_unref (today_end); } static void query_add_journal_range_filter (GlQuery *query, GlEventViewList *view) { GlEventViewListPrivate *priv; priv = gl_event_view_list_get_instance_private (view); /* Add range filters */ switch (priv->journal_timestamp_range) { case GL_SEARCH_POPOVER_JOURNAL_TIMESTAMP_RANGE_CURRENT_BOOT: { /* Get current boot id */ gchar *boot_match = NULL; /* Don't add match when priv->boot_match equals NULL. This * happens when users don't have permissions to view both * system and user logs. */ if (priv->boot_match != NULL) { boot_match = get_current_boot_id (priv->boot_match); gl_query_add_match (query, "_BOOT_ID", boot_match, GL_QUERY_SEARCH_TYPE_EXACT); } g_free (boot_match); } break; case GL_SEARCH_POPOVER_JOURNAL_TIMESTAMP_RANGE_PREVIOUS_BOOT: { GArray *boot_ids; GlJournalBootID *boot_id; boot_ids = gl_event_view_list_get_boot_ids (view); boot_id = &g_array_index (boot_ids, GlJournalBootID, boot_ids->len - 2); gl_query_set_journal_timestamp_range (query, boot_id->realtime_last, boot_id->realtime_first); } break; case GL_SEARCH_POPOVER_JOURNAL_TIMESTAMP_RANGE_TODAY: query_set_day_timestamps (query, 0, 0); break; case GL_SEARCH_POPOVER_JOURNAL_TIMESTAMP_RANGE_YESTERDAY: query_set_day_timestamps (query, 1, 1); break; case GL_SEARCH_POPOVER_JOURNAL_TIMESTAMP_RANGE_LAST_3_DAYS: query_set_day_timestamps (query, 0, 2); break; case GL_SEARCH_POPOVER_JOURNAL_TIMESTAMP_RANGE_CUSTOM: { GlSearchPopover *popover; guint64 custom_start_timestamp; guint64 custom_end_timestamp; popover = GL_SEARCH_POPOVER (priv->search_popover); custom_start_timestamp = gl_search_popover_get_custom_start_timestamp (popover); custom_end_timestamp = gl_search_popover_get_custom_end_timestamp (popover); gl_query_set_journal_timestamp_range (query, custom_start_timestamp, custom_end_timestamp); break; } default: /* By default, search the entire journal */ break; } } /* Create query object according to selected category */ static GlQuery * create_query_object (GlEventViewList *view) { GlEventViewListPrivate *priv; GlQuery *query; GlCategoryList *list; GSettings *settings; gint sort_order; priv = gl_event_view_list_get_instance_private (view); list = GL_CATEGORY_LIST (priv->categories); /* Get the sorting order from GSettings key */ settings = g_settings_new (SETTINGS_SCHEMA); sort_order = g_settings_get_enum (settings, SORT_ORDER); /* Create new query object */ query = gl_query_new (); /* Set journal timestamp range */ query_add_journal_range_filter (query, view); query_add_category_matches (query, list); query_add_search_matches (query, priv->search_text, priv->journal_search_field, priv->search_type); gl_query_set_search_type (query, priv->search_type); gl_query_set_sort_order (query, sort_order); g_object_unref (settings); return query; } static void on_notify_category (GlCategoryList *list, GParamSpec *pspec, gpointer user_data) { GlEventViewList *view; GlEventViewListPrivate *priv; GlQuery *query; view = GL_EVENT_VIEW_LIST (user_data); priv = gl_event_view_list_get_instance_private (view); /* Create the query object */ query = create_query_object (view); /* Set the created query on the journal model */ gl_journal_model_take_query (priv->journal_model, query); } void gl_event_view_list_view_boot (GlEventViewList *view, const gchar *match) { GlEventViewListPrivate *priv; GlSearchPopover *popover; g_return_if_fail (GL_EVENT_VIEW_LIST (view)); priv = gl_event_view_list_get_instance_private (view); popover = GL_SEARCH_POPOVER (priv->search_popover); g_free (priv->boot_match); priv->boot_match = g_strdup (match); /* Make search popover journal timestamp range label consistent with event-toolbar boot selection menu */ gl_search_popover_set_journal_timestamp_range_current_boot (popover); } void gl_event_view_list_set_sort_order (GlEventViewList *view, GlSortOrder sort_order) { GlEventViewListPrivate *priv; GlQuery *query; g_return_if_fail (GL_EVENT_VIEW_LIST (view)); priv = gl_event_view_list_get_instance_private (view); /* Create the query object */ query = create_query_object (view); /* The sort order is taken from the GSettings key irrespective * of current model sort order and the created query is passed * on to model */ gl_journal_model_take_query (priv->journal_model, query); } static void on_search_entry_changed (GtkSearchEntry *entry, gpointer user_data) { GlEventViewList *view; GlEventViewListPrivate *priv; GlQuery *query; view = GL_EVENT_VIEW_LIST (user_data); priv = gl_event_view_list_get_instance_private (view); g_free (priv->search_text); priv->search_text = g_strdup (gtk_editable_get_text (GTK_EDITABLE (priv->search_entry))); /* Create the query object */ query = create_query_object (view); /* Set the created query on the journal model */ gl_journal_model_take_query (priv->journal_model, query); } static void on_search_bar_notify_search_mode_enabled (GtkSearchBar *search_bar, GParamSpec *pspec, gpointer user_data) { GAction *search; GtkRoot *root; GActionMap *appwindow; root = gtk_widget_get_root (GTK_WIDGET (user_data)); if (G_IS_ACTION_MAP (root)) { appwindow = G_ACTION_MAP (root); search = g_action_map_lookup_action (appwindow, "search"); } else { /* TODO: Investigate whether this only happens during dispose. */ g_debug ("%s", "Search bar activated while not in a toplevel"); return; } g_action_change_state (search, g_variant_new_boolean (gtk_search_bar_get_search_mode (search_bar))); } static void gl_event_list_view_edge_reached (GtkScrolledWindow *scrolled, GtkPositionType pos, gpointer user_data) { GlEventViewList *view = user_data; GlEventViewListPrivate *priv = gl_event_view_list_get_instance_private (view); if (pos == GTK_POS_BOTTOM) gl_journal_model_fetch_more_entries (priv->journal_model, FALSE); } static void search_popover_journal_search_field_changed (GlSearchPopover *popover, GParamSpec *psec, GlEventViewList *view) { GlEventViewListPrivate *priv = gl_event_view_list_get_instance_private (view); GlQuery *query; priv->journal_search_field = gl_search_popover_get_journal_search_field (popover); query = create_query_object (view); gl_journal_model_take_query (priv->journal_model, query); } static void search_popover_search_type_changed (GlSearchPopover *popover, GParamSpec *psec, GlEventViewList *view) { GlEventViewListPrivate *priv = gl_event_view_list_get_instance_private (view); GlQuery *query; priv->search_type = gl_search_popover_get_query_search_type (popover); query = create_query_object (view); gl_journal_model_take_query (priv->journal_model, query); } static void search_popover_journal_timestamp_range_changed (GlSearchPopover *popover, GParamSpec *psec, GlEventViewList *view) { GlEventViewListPrivate *priv = gl_event_view_list_get_instance_private (view); GlQuery *query; priv->journal_timestamp_range = gl_search_popover_get_journal_timestamp_range (popover); query = create_query_object (view); gl_journal_model_take_query (priv->journal_model, query); } /* Get the view elements from ui file and link it with the drop down button */ static void set_up_search_popover (GlEventViewList *view) { GlEventViewListPrivate *priv; priv = gl_event_view_list_get_instance_private (view); priv->search_popover = gl_search_popover_new (); /* Grab/Remove keyboard focus from popover menu when it is opened or closed */ g_signal_connect (priv->search_popover, "show", (GCallback) gtk_widget_grab_focus, NULL); g_signal_connect_swapped (priv->search_popover, "closed", (GCallback) gtk_widget_grab_focus, view); g_signal_connect (priv->search_popover, "notify::journal-search-field", G_CALLBACK (search_popover_journal_search_field_changed), view); g_signal_connect (priv->search_popover, "notify::search-type", G_CALLBACK (search_popover_search_type_changed), view); g_signal_connect (priv->search_popover, "notify::journal-timestamp-range", G_CALLBACK (search_popover_journal_timestamp_range_changed), view); /* Link the drop down button with search popover */ gtk_menu_button_set_popover (GTK_MENU_BUTTON (priv->search_dropdown_button), priv->search_popover); } static void gl_event_view_list_finalize (GObject *object) { GlEventViewList *view = GL_EVENT_VIEW_LIST (object); GlEventViewListPrivate *priv = gl_event_view_list_get_instance_private (view); g_free (priv->boot_match); g_clear_object (&priv->journal_model); g_clear_pointer (&priv->search_text, g_free); g_object_unref (priv->category_sizegroup); G_OBJECT_CLASS (gl_event_view_list_parent_class)->finalize (object); } static void gl_event_view_list_realize (GtkWidget *widget) { GlEventViewList *view = GL_EVENT_VIEW_LIST (widget); GlEventViewListPrivate *priv = gl_event_view_list_get_instance_private (view); GtkRoot *root; GTK_WIDGET_CLASS (gl_event_view_list_parent_class)->realize (widget); root = gtk_widget_get_root (widget); gtk_search_bar_set_key_capture_widget (GTK_SEARCH_BAR (priv->event_search), GTK_WIDGET (root)); } static void gl_event_view_list_class_init (GlEventViewListClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); gobject_class->finalize = gl_event_view_list_finalize; widget_class->realize = gl_event_view_list_realize; gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Logs/gl-eventviewlist.ui"); gtk_widget_class_bind_template_child_private (widget_class, GlEventViewList, entries_box); gtk_widget_class_bind_template_child_private (widget_class, GlEventViewList, categories); gtk_widget_class_bind_template_child_private (widget_class, GlEventViewList, event_search); gtk_widget_class_bind_template_child_private (widget_class, GlEventViewList, event_scrolled); gtk_widget_class_bind_template_child_private (widget_class, GlEventViewList, search_entry); gtk_widget_class_bind_template_child_private (widget_class, GlEventViewList, search_dropdown_button); gtk_widget_class_bind_template_callback (widget_class, on_search_entry_changed); gtk_widget_class_bind_template_callback (widget_class, on_search_bar_notify_search_mode_enabled); } static void gl_event_view_list_init (GlEventViewList *view) { GlCategoryList *categories; GlEventViewListPrivate *priv; GSettings *settings; gtk_widget_init_template (GTK_WIDGET (view)); priv = gl_event_view_list_get_instance_private (view); priv->search_text = NULL; priv->boot_match = NULL; priv->category_sizegroup = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); categories = GL_CATEGORY_LIST (priv->categories); priv->journal_model = gl_journal_model_new (); g_application_bind_busy_property (g_application_get_default (), priv->journal_model, "loading"); g_signal_connect (priv->event_scrolled, "edge-reached", G_CALLBACK (gl_event_list_view_edge_reached), view); gtk_list_box_bind_model (GTK_LIST_BOX (priv->entries_box), G_LIST_MODEL (priv->journal_model), gl_event_list_view_create_row_widget, view, NULL); gtk_list_box_set_header_func (GTK_LIST_BOX (priv->entries_box), (GtkListBoxUpdateHeaderFunc) listbox_update_header_func, NULL, NULL); gtk_list_box_set_placeholder (GTK_LIST_BOX (priv->entries_box), gl_event_view_create_empty (view)); g_signal_connect (priv->entries_box, "row-activated", G_CALLBACK (on_listbox_row_activated), GTK_BOX (view)); gtk_search_bar_connect_entry (GTK_SEARCH_BAR (priv->event_search), GTK_EDITABLE (priv->search_entry)); /* TODO: Monitor and propagate any GSettings changes. */ settings = g_settings_new (DESKTOP_SCHEMA); priv->clock_format = g_settings_get_enum (settings, CLOCK_FORMAT); g_object_unref (settings); set_up_search_popover (view); g_signal_connect (categories, "notify::category", G_CALLBACK (on_notify_category), view); } GtkWidget * gl_event_view_list_new (void) { return g_object_new (GL_TYPE_EVENT_VIEW_LIST, NULL); }