[gnome-logs] Compress similar events in GlEventViewList



commit 3bb233264c9de1a471db86344656ffa77ca9a1e4
Author: Pranav Ganorkar <pranavg189 gmail com>
Date:   Fri Jun 9 19:01:04 2017 +0530

    Compress similar events in GlEventViewList
    
    The compression information is stored in GlRowEntry objects
    during population of the model array.The similarity criteria
    for grouping adjacent messages is:
    
    * Messages whose first word is same
    * Messages sent by the same process
    
    The compression information is then parsed in the view classes
    and accordingly the events to be compressed are hidden in GlEventViewList.
    
    The visibility of compressed events can be toggled by
    activating the header row representing those compressed events.
    
    When compressed events are expanded using the header, sorting order
    specified by 'sort-order' GSettings key is maintained in GlEventViewList.
    
    Headers are removed from the compressed rows to improve their visibility
    when they are shown by activating the header.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=709294

 data/gl-style.css      |   16 ++++
 src/gl-eventviewlist.c |   87 +++++++++++++++-----
 src/gl-eventviewrow.c  |   63 +++++++++++++++-
 src/gl-journal-model.c |  202 +++++++++++++++++++++++++++++++++++++++++++++++-
 4 files changed, 341 insertions(+), 27 deletions(-)
---
diff --git a/data/gl-style.css b/data/gl-style.css
index 491ee90..ce48a7f 100644
--- a/data/gl-style.css
+++ b/data/gl-style.css
@@ -33,3 +33,19 @@
     padding-top: 0px;
     padding-bottom: 0px;
 }
+
+.compressed-entries-label {
+    background-color: #d4d4d4;
+    font-size: small;
+    border-radius: 3px;
+    padding-left: 5px;
+    padding-right: 5px;
+}
+
+.compressed-row {
+    background-color: #f9f9f9;
+}
+
+.compressed-row-header {
+    background-color: #f0f0f0;
+}
diff --git a/src/gl-eventviewlist.c b/src/gl-eventviewlist.c
index ae16419..1877083 100644
--- a/src/gl-eventviewlist.c
+++ b/src/gl-eventviewlist.c
@@ -137,6 +137,7 @@ listbox_update_header_func (GtkListBoxRow *row,
                             gpointer user_data)
 {
     GtkWidget *current;
+    GlRowEntry *row_entry;
 
     if (before == NULL)
     {
@@ -146,11 +147,16 @@ listbox_update_header_func (GtkListBoxRow *row,
 
     current = gtk_list_box_row_get_header (row);
 
-    if (current == NULL)
+    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))
     {
-        current = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
-        gtk_widget_show (current);
-        gtk_list_box_row_set_header (row, current);
+        if (current == NULL)
+        {
+            current = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
+            gtk_widget_show (current);
+            gtk_list_box_row_set_header (row, current);
+        }
     }
 }
 
@@ -162,30 +168,71 @@ on_listbox_row_activated (GtkListBox *listbox,
                           GlEventViewList *view)
 {
     GlEventViewListPrivate *priv;
-    GtkWidget *toplevel;
 
     priv = gl_event_view_list_get_instance_private (view);
     priv->entry = gl_event_view_row_get_entry (GL_EVENT_VIEW_ROW (row));
 
-    toplevel = gtk_widget_get_toplevel (GTK_WIDGET (view));
-
-    if (gtk_widget_is_toplevel (toplevel))
+    if (gl_row_entry_get_row_type (priv->entry) == GL_ROW_ENTRY_TYPE_HEADER)
     {
-        GAction *mode;
-        GEnumClass *eclass;
-        GEnumValue *evalue;
+        guint compressed_entries;
+        gint header_row_index;
+        gint index;
 
-        mode = g_action_map_lookup_action (G_ACTION_MAP (toplevel), "view-mode");
-        eclass = g_type_class_ref (GL_TYPE_EVENT_VIEW_MODE);
-        evalue = g_enum_get_value (eclass, GL_EVENT_VIEW_MODE_DETAIL);
+        compressed_entries = gl_row_entry_get_compressed_entries (priv->entry);
+        header_row_index = gtk_list_box_row_get_index (row);
 
-        g_action_activate (mode, g_variant_new_string (evalue->value_nick));
+        for (index = header_row_index + 1; compressed_entries != 0; index++)
+        {
+            GtkListBoxRow *compressed_row;
+            GtkStyleContext *context;
 
-        g_type_class_unref (eclass);
+            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");
+
+            context = gtk_widget_get_style_context (GTK_WIDGET (row));
+
+            /* Toggle the visibility */
+            if (gtk_widget_get_visible (GTK_WIDGET (compressed_row)))
+            {
+                gtk_widget_hide (GTK_WIDGET (compressed_row));
+                gtk_style_context_remove_class (context, "compressed-row-header");
+            }
+            else
+            {
+                gtk_widget_show (GTK_WIDGET (compressed_row));
+                gtk_style_context_add_class (context, "compressed-row-header");
+            }
+
+            compressed_entries--;
+        }
     }
     else
     {
-        g_debug ("Widget not in toplevel window, not switching toolbar mode");
+        GtkWidget *toplevel;
+
+        toplevel = gtk_widget_get_toplevel (GTK_WIDGET (view));
+
+        if (gtk_widget_is_toplevel (toplevel))
+        {
+            GAction *mode;
+            GEnumClass *eclass;
+            GEnumValue *evalue;
+
+            mode = g_action_map_lookup_action (G_ACTION_MAP (toplevel), "view-mode");
+            eclass = g_type_class_ref (GL_TYPE_EVENT_VIEW_MODE);
+            evalue = g_enum_get_value (eclass, GL_EVENT_VIEW_MODE_DETAIL);
+
+            g_action_activate (mode, g_variant_new_string (evalue->value_nick));
+
+            g_type_class_unref (eclass);
+        }
+        else
+        {
+            g_debug ("Widget not in toplevel window, not switching toolbar mode");
+        }
     }
 }
 
@@ -308,7 +355,6 @@ gl_event_list_view_create_row_widget (gpointer item,
                                       gpointer user_data)
 {
     GtkWidget *rtn;
-    GtkWidget *message_label;
     GtkWidget *time_label;
     GlCategoryList *list;
     GlCategoryListFilter filter;
@@ -338,11 +384,8 @@ gl_event_list_view_create_row_widget (gpointer item,
                                      GL_EVENT_VIEW_ROW_CATEGORY_NONE);
     }
 
-    message_label = gl_event_view_row_get_message_label (GL_EVENT_VIEW_ROW (rtn));
     time_label = gl_event_view_row_get_time_label (GL_EVENT_VIEW_ROW (rtn));
 
-    gtk_size_group_add_widget (GTK_SIZE_GROUP (priv->message_sizegroup),
-                               message_label);
     gtk_size_group_add_widget (GTK_SIZE_GROUP (priv->time_sizegroup),
                                time_label);
 
diff --git a/src/gl-eventviewrow.c b/src/gl-eventviewrow.c
index 399fb8e..f109863 100644
--- a/src/gl-eventviewrow.c
+++ b/src/gl-eventviewrow.c
@@ -336,8 +336,45 @@ gl_event_view_row_constructed (GObject *object)
                              PANGO_ELLIPSIZE_END);
     gtk_label_set_xalign (GTK_LABEL (priv->message_label), 0);
     gtk_label_set_single_line_mode (GTK_LABEL (priv->message_label), TRUE);
-    gtk_grid_attach (GTK_GRID (grid), priv->message_label,
-                     1, 0, 1, 1);
+
+    if (gl_row_entry_get_row_type (row_entry) == GL_ROW_ENTRY_TYPE_HEADER)
+    {
+        guint compressed_entries;
+        GtkWidget *message_box;
+        GtkWidget *compressed_entries_label;
+        gchar *compressed_entries_string;
+
+        message_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10);
+
+        gtk_box_pack_start (GTK_BOX (message_box),
+                            priv->message_label,
+                            TRUE, TRUE, 2);
+
+        compressed_entries = gl_row_entry_get_compressed_entries (row_entry);
+        compressed_entries_string = g_strdup_printf ("%d", compressed_entries);
+
+        compressed_entries_label = gtk_label_new (compressed_entries_string);
+
+        gtk_widget_set_direction (compressed_entries_label, GTK_TEXT_DIR_LTR);
+        context = gtk_widget_get_style_context (GTK_WIDGET (compressed_entries_label));
+        gtk_style_context_add_class (context, "compressed-entries-label");
+        gtk_widget_set_halign (compressed_entries_label, GTK_ALIGN_START);
+        gtk_label_set_xalign (GTK_LABEL (compressed_entries_label), 0);
+
+        gtk_box_pack_start (GTK_BOX (message_box),
+                            compressed_entries_label,
+                            TRUE, TRUE, 0);
+
+        gtk_grid_attach (GTK_GRID (grid), message_box,
+                         1, 0, 1, 1);
+
+        g_free (compressed_entries_string);
+    }
+    else
+    {
+        gtk_grid_attach (GTK_GRID (grid), priv->message_label,
+                         1, 0, 1, 1);
+    }
 
     now = g_date_time_new_now_local ();
     time = gl_util_timestamp_to_display (gl_journal_entry_get_timestamp (entry),
@@ -364,6 +401,25 @@ gl_event_view_row_constructed (GObject *object)
     G_OBJECT_CLASS (gl_event_view_row_parent_class)->constructed (object);
 }
 
+/* Hide the rows to be compressed */
+static void
+on_parent_set (GtkWidget *widget,
+               GtkWidget *old_parent,
+               gpointer   user_data)
+{
+    GlEventViewRow *row = GL_EVENT_VIEW_ROW (user_data);
+    GlEventViewRowPrivate *priv;
+
+    priv = gl_event_view_row_get_instance_private (row);
+
+    /* Execute only if the parent was not set earlier at all */
+    if (old_parent == NULL &&
+        gl_row_entry_get_row_type (priv->entry) == GL_ROW_ENTRY_TYPE_COMPRESSED)
+    {
+        gtk_widget_hide (widget);
+    }
+}
+
 static void
 gl_event_view_row_class_init (GlEventViewRowClass *klass)
 {
@@ -406,6 +462,9 @@ gl_event_view_row_init (GlEventViewRow *row)
 {
     /* The widgets are initialized in gl_event_view_row_constructed (), because
      * at _init() time the construct-only properties have not been set. */
+
+    g_signal_connect (row, "parent-set",
+                      G_CALLBACK (on_parent_set), row);
 }
 
 GlRowEntry *
diff --git a/src/gl-journal-model.c b/src/gl-journal-model.c
index 83e3b25..0b1d901 100644
--- a/src/gl-journal-model.c
+++ b/src/gl-journal-model.c
@@ -35,7 +35,7 @@ struct _GlRowEntry
     GlRowEntryType row_type;
 
     /* Number of compressed entries represented by
-     * a compressed entries header
+     * a header
      */
     guint compressed_entries;
 };
@@ -55,12 +55,18 @@ struct _GlJournalModel
     guint n_entries_to_fetch;
     gboolean fetched_all;
     guint idle_source;
+
+    guint compressed_entries_counter;
 };
 
 static void gl_journal_model_interface_init (GListModelInterface *iface);
 static GPtrArray *tokenize_search_string (gchar *search_text);
 static gboolean search_in_entry (GlJournalEntry *entry, GlJournalModel *model);
 static gboolean gl_query_check_journal_end (GlQuery *query, GlJournalEntry *entry);
+static gboolean gl_row_entry_check_message_similarity (GlRowEntry *current_row_entry,
+                                                       GlRowEntry *prev_row_entry);
+static void gl_journal_model_add_header (GlJournalModel *model);
+
 
 G_DEFINE_TYPE (GlRowEntry, gl_row_entry, G_TYPE_OBJECT);
 
@@ -99,11 +105,59 @@ gl_journal_model_fetch_idle (gpointer user_data)
     {
         if (search_in_entry (entry, model))
         {
-            model->n_entries_to_fetch--;
-
             row_entry = gl_row_entry_new ();
             row_entry->journal_entry = entry;
 
+            if (last > 0)
+            {
+                GlRowEntry *prev_row_entry = g_ptr_array_index (model->entries, last - 1);
+
+                if (gl_row_entry_check_message_similarity (row_entry,
+                                                           prev_row_entry))
+                {
+
+                    /* Previously similar messages were detected */
+                    if (prev_row_entry->row_type == GL_ROW_ENTRY_TYPE_COMPRESSED)
+                    {
+
+                        model->compressed_entries_counter++;
+                        row_entry->row_type = GL_ROW_ENTRY_TYPE_COMPRESSED;
+                    }
+                    /* First time a similar group of messages is detected */
+                    else
+                    {
+
+                        model->compressed_entries_counter = model->compressed_entries_counter + 2;
+                        prev_row_entry->row_type = GL_ROW_ENTRY_TYPE_COMPRESSED;
+
+                        if (model->query->order == GL_SORT_ORDER_ASCENDING_TIME)
+                        {
+                            g_list_model_items_changed (G_LIST_MODEL (model), 0, 1, 1);
+                        }
+                        else
+                        {
+                            g_list_model_items_changed (G_LIST_MODEL (model), last - 1, 1, 1);
+                        }
+
+                        row_entry->row_type = GL_ROW_ENTRY_TYPE_COMPRESSED;
+                    }
+                }
+                else
+                {
+
+                    /* Add a compressed row header if a group of compressed row entries
+                     * was detected. */
+                    gl_journal_model_add_header (model);
+
+                    /* Reset the count of compressed entries */
+                    model->compressed_entries_counter = 0;
+
+                    model->n_entries_to_fetch--;
+
+                }
+            }
+
+            last = model->entries->len;
             g_ptr_array_add (model->entries, row_entry);
 
             if (model->query->order == GL_SORT_ORDER_ASCENDING_TIME)
@@ -120,6 +174,10 @@ gl_journal_model_fetch_idle (gpointer user_data)
     {
         model->fetched_all = TRUE;
         model->n_entries_to_fetch = 0;
+
+        /* If the last read entry was in a compressed group
+         * then add a row header representing that group. */
+        gl_journal_model_add_header (model);
     }
 
     if (model->n_entries_to_fetch > 0)
@@ -422,6 +480,9 @@ gl_journal_model_process_query (GlJournalModel *model)
 
     gl_journal_set_start_position (model->journal, model->query->start_timestamp);
 
+    /* Reset the count of compressed entries */
+    model->compressed_entries_counter = 0;
+
     /* Start re-population of the journal */
     gl_journal_model_fetch_more_entries (model, FALSE);
 
@@ -1116,3 +1177,138 @@ gl_row_entry_class_init (GlRowEntryClass *class)
 
   object_class->finalize = gl_row_entry_finalize;
 }
+
+
+/**
+ * gl_row_entry_check_message_similarity:
+ * @current_row_entry: a #GlRowEntry
+ * @prev_row_entry: a #GlRowEntry
+ *
+ * Compares two adjacent row entries for grouping them under a
+ * common compressed row header. Returns %TRUE if their messages
+ * have same first word or if they have been sent by the same
+ * sending process.
+ */
+static gboolean
+gl_row_entry_check_message_similarity (GlRowEntry *current_row_entry,
+                                       GlRowEntry *prev_row_entry)
+{
+    GlJournalEntry *current_entry;
+    GlJournalEntry *prev_entry;
+    const gchar *current_entry_message;
+    const gchar *prev_entry_message;
+    const gchar *current_entry_sender;
+    const gchar *prev_entry_sender;
+    const gchar *first_whitespace_index;
+    GString *current_entry_word;
+    GString *prev_entry_word;
+    gboolean result;
+
+    current_entry = gl_row_entry_get_journal_entry (current_row_entry);
+    prev_entry = gl_row_entry_get_journal_entry (prev_row_entry);
+
+    current_entry_message = gl_journal_entry_get_message (current_entry);
+    prev_entry_message = gl_journal_entry_get_message (prev_entry);
+
+    current_entry_sender = gl_journal_entry_get_command_line (current_entry);
+    prev_entry_sender = gl_journal_entry_get_command_line (prev_entry);
+
+    current_entry_word = g_string_new (NULL);
+    prev_entry_word = g_string_new (NULL);
+
+    /* Get the position of first whitespace in the messages of the adjacent
+     * journal entries and copy all the characters before that position
+     * into a new string buffer. */
+    first_whitespace_index = strchr (current_entry_message, ' ');
+
+    g_string_append_len (current_entry_word, current_entry_message,
+                         first_whitespace_index - current_entry_message);
+
+    first_whitespace_index = strchr (prev_entry_message, ' ');
+
+    g_string_append_len (prev_entry_word, prev_entry_message,
+                        first_whitespace_index - prev_entry_message);
+
+    result = FALSE;
+
+    /* Check for similarity criteria */
+    if (g_string_equal (current_entry_word, prev_entry_word))
+    {
+        result = TRUE;
+    }
+    else if (current_entry_sender && prev_entry_sender)
+    {
+        if (g_strcmp0 (current_entry_sender, prev_entry_sender) == 0)
+        {
+            result = TRUE;
+        }
+    }
+
+    g_string_free (current_entry_word, TRUE);
+    g_string_free (prev_entry_word, TRUE);
+
+    return result;
+}
+
+/**
+ * gl_journal_model_add_header:
+ * @model: a #GlJournalModel
+ *
+ * Adds a compressed row header to the @model, which represents
+ * a group of compressed row entries. Depending upon the sorting
+ * order, the compressed row header is inserted in the @model
+ * so that it will always appear before the compressed entries
+ * represented by it in the view.
+ */
+static void
+gl_journal_model_add_header (GlJournalModel *model)
+{
+    guint last;
+    last = model->entries->len;
+
+    /* A group of compressed row entries is detected. */
+    if (model->compressed_entries_counter >= 2)
+    {
+        GlJournalEntry *prev_entry;
+        GlRowEntry *prev_row_entry;
+        GlRowEntry *header;
+
+        /* Get the row entry from the compressed entries group, whose
+         * journal entry details will be stored in the compressed row
+         * header. */
+        if (model->query->order == GL_SORT_ORDER_ASCENDING_TIME)
+        {
+            prev_row_entry = g_ptr_array_index (model->entries, last - 1);
+        }
+        else
+        {
+            prev_row_entry = g_ptr_array_index (model->entries,
+                                                last - model->compressed_entries_counter);
+        }
+
+        prev_entry = prev_row_entry->journal_entry;
+
+        /* Create the compressed row header */
+        header = gl_row_entry_new ();
+        header->journal_entry = g_object_ref (prev_entry);
+        header->row_type = GL_ROW_ENTRY_TYPE_HEADER;
+        header->compressed_entries = model->compressed_entries_counter;
+
+        /* Insert it at a appropriate positon in the model */
+        if (model->query->order == GL_SORT_ORDER_ASCENDING_TIME)
+        {
+            g_ptr_array_add (model->entries, header);
+            g_list_model_items_changed (G_LIST_MODEL (model), 0, 0, 1);
+        }
+        else
+        {
+            g_ptr_array_insert (model->entries,
+                                last - model->compressed_entries_counter,
+                                header);
+
+            g_list_model_items_changed (G_LIST_MODEL (model),
+                                        last - model->compressed_entries_counter,
+                                        0, 1);
+        }
+    }
+}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]