summaryrefslogtreecommitdiffstats
path: root/sediff/results.c
diff options
context:
space:
mode:
Diffstat (limited to 'sediff/results.c')
-rw-r--r--sediff/results.c708
1 files changed, 708 insertions, 0 deletions
diff --git a/sediff/results.c b/sediff/results.c
new file mode 100644
index 0000000..f19689f
--- /dev/null
+++ b/sediff/results.c
@@ -0,0 +1,708 @@
+/**
+ * @file
+ * Routines for displaying the results after running poldiff.
+ *
+ * @author Jeremy A. Mowery jmowery@tresys.com
+ * @author Jason Tang jtang@tresys.com
+ * @author Brandon Whalen bwhalen@tresys.com
+ * @author Randy Wicks rwicks@tresys.com
+ *
+ * Copyright (C) 2005-2007 Tresys Technology, LLC
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <config.h>
+
+#include "result_item.h"
+#include "results.h"
+#include "utilgui.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <glade/glade.h>
+#include <qpol/cond_query.h>
+
+enum
+{
+ RESULTS_SUMMARY_COLUMN_LABEL = 0,
+ RESULTS_SUMMARY_COLUMN_FORM,
+ RESULTS_SUMMARY_COLUMN_ITEM,
+ RESULTS_SUMMARY_COLUMN_STYLE,
+ RESULTS_SUMMARY_COLUMN_NUM
+};
+
+#define NUM_RESULT_ITEMS 19
+
+struct results
+{
+ toplevel_t *top;
+ GladeXML *xml;
+ GtkTreeStore *summary_tree;
+ GtkTreeView *summary_view;
+ GtkTextBuffer *main_buffer, *key_buffer;
+ GtkTextView *view;
+ GtkTextTag *policy_orig_tag, *policy_mod_tag;
+ GtkLabel *stats;
+ /** pointer to within items[] of the currently selected result
+ * item, or NULL if the summary page or none are selected */
+ result_item_t *current_item;
+ /** form that is currently selected, or POLDIFF_FORM_NONE if
+ * the summary or item's summary page is selected */
+ poldiff_form_e current_form;
+ /** saved cursor's line number for the summary page */
+ gint summary_offset;
+ result_item_t *items[NUM_RESULT_ITEMS];
+};
+
+static const poldiff_form_e form_map[] = {
+ POLDIFF_FORM_ADDED, POLDIFF_FORM_ADD_TYPE,
+ POLDIFF_FORM_REMOVED, POLDIFF_FORM_REMOVE_TYPE,
+ POLDIFF_FORM_MODIFIED
+};
+
+/**
+ * Array or result_item constructors. Note that the order given below
+ * governs the order that the items appear in the results summary
+ * tree.
+ */
+static result_item_t *(*result_item_constructors[NUM_RESULT_ITEMS]) (GtkTextTagTable *) = {
+result_item_create_commons, result_item_create_classes,
+ result_item_create_levels, result_item_create_categories,
+ result_item_create_types, result_item_create_attributes,
+ result_item_create_roles, result_item_create_users,
+ result_item_create_booleans,
+ result_item_create_avrules_allow,
+ result_item_create_avrules_auditallow,
+ result_item_create_avrules_dontaudit,
+ result_item_create_avrules_neverallow,
+ result_item_create_terules_change,
+ result_item_create_terules_member,
+ result_item_create_terules_trans,
+ result_item_create_role_allows, result_item_create_role_trans, result_item_create_range_trans};
+
+static void results_summary_on_change(GtkTreeSelection * selection, gpointer user_data);
+
+static gboolean results_on_inline_link_event(GtkTextTag * tag, GObject * event_object,
+ GdkEvent * event, const GtkTextIter * iter, gpointer user_data);
+static gboolean results_on_line_event(GtkTextTag * tag, GObject * event_object,
+ GdkEvent * event, const GtkTextIter * iter, gpointer user_data);
+static gboolean results_on_popup_menu(GtkWidget * widget, gpointer user_data);
+static gboolean results_on_text_view_motion(GtkWidget * widget, GdkEventMotion * event, gpointer user_data);
+/**
+ * Callback whenever the user double-clicks a row in the summary tree.
+ */
+static void results_summary_on_row_activate(GtkTreeView * tree_view, GtkTreePath * path, GtkTreeViewColumn * column,
+ gpointer user_data);
+
+/**
+ * Build a GTK tree store to hold the summary table of contents; then
+ * add that (empty) tree to the tree view.
+ */
+static void results_create_summary(results_t * r)
+{
+ GtkTreeViewColumn *col;
+ GtkCellRenderer *renderer;
+ GtkTreeSelection *selection;
+
+ r->summary_tree = gtk_tree_store_new(RESULTS_SUMMARY_COLUMN_NUM, G_TYPE_STRING, G_TYPE_INT, G_TYPE_POINTER, G_TYPE_BOOLEAN);
+ r->summary_view = GTK_TREE_VIEW(glade_xml_get_widget(r->xml, "toplevel summary view"));
+ assert(r->summary_view != NULL);
+ col = gtk_tree_view_column_new();
+ gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
+ gtk_tree_view_column_set_title(col, "Differences");
+ gtk_tree_view_append_column(r->summary_view, col);
+ renderer = gtk_cell_renderer_text_new();
+ gtk_tree_view_column_pack_start(col, renderer, TRUE);
+ gtk_tree_view_column_set_attributes(col, renderer,
+ "text", RESULTS_SUMMARY_COLUMN_LABEL,
+ "strikethrough", RESULTS_SUMMARY_COLUMN_STYLE, NULL);;
+ gtk_tree_view_set_model(r->summary_view, GTK_TREE_MODEL(r->summary_tree));
+
+ selection = gtk_tree_view_get_selection(r->summary_view);
+ gtk_tree_selection_set_mode(selection, GTK_SELECTION_BROWSE);
+ g_signal_connect(G_OBJECT(selection), "changed", G_CALLBACK(results_summary_on_change), r);
+ g_signal_connect(r->summary_view, "row-activated", G_CALLBACK(results_summary_on_row_activate), r);
+}
+
+results_t *results_create(toplevel_t * top)
+{
+ results_t *r;
+ int i;
+ GtkTextTagTable *tag_table;
+ GtkTextAttributes *attr;
+ GtkTextView *text_view;
+ gint size;
+ PangoTabArray *tabs;
+
+ if ((r = calloc(1, sizeof(*r))) == NULL) {
+ return NULL;
+ }
+ r->top = top;
+ r->xml = glade_get_widget_tree(GTK_WIDGET(toplevel_get_window(r->top)));
+ results_create_summary(r);
+
+ tag_table = gtk_text_tag_table_new();
+ r->main_buffer = gtk_text_buffer_new(tag_table);
+ gtk_text_buffer_create_tag(r->main_buffer, "header", "style", PANGO_STYLE_ITALIC, "weight", PANGO_WEIGHT_BOLD, "family",
+ NULL, NULL);
+ gtk_text_buffer_create_tag(r->main_buffer, "subheader", "weight", PANGO_WEIGHT_BOLD, "underline", PANGO_UNDERLINE_SINGLE,
+ "family", NULL, NULL);
+ gtk_text_buffer_create_tag(r->main_buffer, "removed-header", "foreground", "red", "weight", PANGO_WEIGHT_BOLD, NULL);
+ gtk_text_buffer_create_tag(r->main_buffer, "added-header", "foreground", "dark green", "weight", PANGO_WEIGHT_BOLD, NULL);
+ gtk_text_buffer_create_tag(r->main_buffer, "modified-header", "foreground", "dark blue", "weight", PANGO_WEIGHT_BOLD, NULL);
+ gtk_text_buffer_create_tag(r->main_buffer, "removed", "foreground", "red", NULL);
+ gtk_text_buffer_create_tag(r->main_buffer, "added", "foreground", "dark green", NULL);
+ gtk_text_buffer_create_tag(r->main_buffer, "modified", "foreground", "dark blue", NULL);
+ GtkTextTag *inline_tag = gtk_text_buffer_create_tag(r->main_buffer, "inline-link", NULL);
+ g_signal_connect_after(G_OBJECT(inline_tag), "event", G_CALLBACK(results_on_inline_link_event), r);
+ r->policy_orig_tag = gtk_text_buffer_create_tag(r->main_buffer, "line-pol_orig",
+ "foreground", "blue", "underline", PANGO_UNDERLINE_SINGLE, NULL);
+ g_signal_connect_after(G_OBJECT(r->policy_orig_tag), "event", G_CALLBACK(results_on_line_event), r);
+ r->policy_mod_tag = gtk_text_buffer_create_tag(r->main_buffer, "line-pol_mod",
+ "foreground", "blue", "underline", PANGO_UNDERLINE_SINGLE, NULL);
+ g_signal_connect_after(G_OBJECT(r->policy_mod_tag), "event", G_CALLBACK(results_on_line_event), r);
+
+ r->view = GTK_TEXT_VIEW(glade_xml_get_widget(r->xml, "toplevel results view"));
+ assert(r->view != NULL);
+ g_signal_connect(G_OBJECT(r->view), "popup-menu", G_CALLBACK(results_on_popup_menu), r);
+ g_signal_connect(G_OBJECT(r->view), "motion-notify-event", G_CALLBACK(results_on_text_view_motion), r);
+ attr = gtk_text_view_get_default_attributes(r->view);
+ size = pango_font_description_get_size(attr->font);
+ tabs = pango_tab_array_new_with_positions(4,
+ FALSE,
+ PANGO_TAB_LEFT, 3 * size,
+ PANGO_TAB_LEFT, 6 * size, PANGO_TAB_LEFT, 9 * size, PANGO_TAB_LEFT, 12 * size);
+ gtk_text_view_set_tabs(r->view, tabs);
+ gtk_text_view_set_buffer(r->view, r->main_buffer);
+
+ r->key_buffer = gtk_text_buffer_new(tag_table);
+ text_view = GTK_TEXT_VIEW(glade_xml_get_widget(r->xml, "toplevel key view"));
+ assert(text_view != NULL);
+ gtk_text_view_set_buffer(text_view, r->key_buffer);
+
+ PangoFontDescription *font_desc = pango_font_description_new();
+ pango_font_description_set_family_static(font_desc, "monospace");
+ gtk_widget_modify_font(GTK_WIDGET(r->view), font_desc);
+ gtk_widget_modify_font(GTK_WIDGET(text_view), font_desc);
+ pango_font_description_free(font_desc);
+
+ r->stats = GTK_LABEL((glade_xml_get_widget(r->xml, "toplevel stats label")));
+ assert(r->stats != NULL);
+ gtk_label_set_text(r->stats, "");
+
+ for (i = 0; i < NUM_RESULT_ITEMS; i++) {
+ if ((r->items[i] = result_item_constructors[i] (tag_table)) == NULL) {
+ results_destroy(&r);
+ return NULL;
+ }
+ }
+ return r;
+}
+
+void results_destroy(results_t ** r)
+{
+ if (r != NULL && *r != NULL) {
+ int i;
+ for (i = 0; i < NUM_RESULT_ITEMS; i++) {
+ result_item_destroy(&((*r)->items[i]));
+ }
+ free(*r);
+ *r = NULL;
+ }
+}
+
+void results_open_policies(results_t * r, apol_policy_t * orig, apol_policy_t * mod)
+{
+ int i;
+ for (i = 0; i < NUM_RESULT_ITEMS; i++) {
+ result_item_policy_changed(r->items[i], orig, mod);
+ }
+}
+
+void results_clear(results_t * r)
+{
+ gtk_tree_store_clear(r->summary_tree);
+ gtk_text_view_set_buffer(r->view, r->main_buffer);
+ util_text_buffer_clear(r->main_buffer);
+ util_text_buffer_clear(r->key_buffer);
+ gtk_label_set_text(r->stats, "");
+ r->current_item = NULL;
+ r->current_form = POLDIFF_FORM_NONE;
+ r->summary_offset = 0;
+}
+
+/**
+ * Update the summary tree and summary buffer to reflect the number of
+ * items added/removed/modified.
+ */
+static void results_update_summary(results_t * r)
+{
+ GtkTreeIter topiter, childiter;
+ GtkTextIter iter;
+ size_t sum_diffs;
+ int i, j, forms[5];
+ GString *s = g_string_new("");
+
+ gtk_tree_store_append(r->summary_tree, &topiter, NULL);
+ gtk_tree_store_set(r->summary_tree, &topiter,
+ RESULTS_SUMMARY_COLUMN_LABEL, "Summary",
+ RESULTS_SUMMARY_COLUMN_FORM, POLDIFF_FORM_NONE, RESULTS_SUMMARY_COLUMN_ITEM, NULL, -1);
+ gtk_text_buffer_get_start_iter(r->main_buffer, &iter);
+ gtk_text_buffer_insert_with_tags_by_name(r->main_buffer, &iter, "Policy Difference Statistics\n", -1, "header", NULL);
+ static const char *form_name_short_map[] = {
+ "Added", "Added Type", "Removed", "Removed Type", "Modified"
+ };
+ static const char *form_name_long_map[] = {
+ "Added", "Added because of new type", "Removed", "Removed because of missing type", "Modified"
+ };
+ static const char *tag_map[] = {
+ "added-header", "added-header", "removed-header", "removed-header", "modified-header"
+ };
+ for (i = 0; i < NUM_RESULT_ITEMS; i++) {
+ const char *label = result_item_get_label(r->items[i]);
+ gtk_tree_store_append(r->summary_tree, &topiter, NULL);
+ gtk_tree_store_set(r->summary_tree, &topiter,
+ RESULTS_SUMMARY_COLUMN_FORM, POLDIFF_FORM_NONE, RESULTS_SUMMARY_COLUMN_ITEM, r->items[i], -1);
+ if (result_item_is_supported(r->items[i])) {
+ result_item_get_forms(r->items[i], forms);
+ sum_diffs = 0;
+ g_string_printf(s, "\n%s:\n", label);
+ gtk_text_buffer_insert_with_tags_by_name(r->main_buffer, &iter, s->str, -1, "subheader", NULL);
+ int was_run = 0;
+ for (j = 0; j < 5; j++) {
+ if (forms[j] > 0) {
+ size_t num_diffs;
+ num_diffs = result_item_get_num_differences(r->items[i], form_map[j]);
+ g_string_printf(s, "\t%s: %zd\n", form_name_long_map[j], num_diffs);
+ gtk_text_buffer_insert_with_tags_by_name(r->main_buffer, &iter, s->str, -1, tag_map[j],
+ NULL);
+ sum_diffs += num_diffs;
+ gtk_tree_store_append(r->summary_tree, &childiter, &topiter);
+ g_string_printf(s, "%s (%zd)", form_name_short_map[j], num_diffs);
+ gtk_tree_store_set(r->summary_tree, &childiter,
+ RESULTS_SUMMARY_COLUMN_LABEL, s->str,
+ RESULTS_SUMMARY_COLUMN_FORM, form_map[j],
+ RESULTS_SUMMARY_COLUMN_ITEM, r->items[i], -1);
+ was_run = 1;
+ }
+ }
+ if (!was_run) {
+ g_string_printf(s, "%s (\?\?\?)", label);
+ } else {
+ g_string_printf(s, "%s (%zd)", label, sum_diffs);
+ }
+ gtk_tree_store_set(r->summary_tree, &topiter,
+ RESULTS_SUMMARY_COLUMN_LABEL, s->str, RESULTS_SUMMARY_COLUMN_STYLE, FALSE, -1);
+ } else {
+ /* item is not supported at all */
+ g_string_printf(s, "%s (N/A)", label);
+ gtk_tree_store_set(r->summary_tree, &topiter,
+ RESULTS_SUMMARY_COLUMN_LABEL, s->str, RESULTS_SUMMARY_COLUMN_STYLE, FALSE, -1);
+ }
+ }
+
+ g_string_free(s, TRUE);
+}
+
+/**
+ * Show the legend of the symbols used in results displays.
+ */
+static void results_populate_key_buffer(results_t * r)
+{
+ GString *string = g_string_new("");
+ GtkTextIter iter;
+
+ gtk_text_buffer_get_end_iter(r->key_buffer, &iter);
+
+ g_string_printf(string, " Added(+):\n Items added in\n modified policy.\n\n");
+ gtk_text_buffer_insert_with_tags_by_name(r->key_buffer, &iter, string->str, -1, "added", NULL);
+ g_string_printf(string, " Removed(-):\n Items removed\n from original\n policy.\n\n");
+ gtk_text_buffer_insert_with_tags_by_name(r->key_buffer, &iter, string->str, -1, "removed", NULL);
+ g_string_printf(string, " Modified(*):\n Items modified\n from original\n policy to\n modified policy.");
+ gtk_text_buffer_insert_with_tags_by_name(r->key_buffer, &iter, string->str, -1, "modified", NULL);
+ g_string_free(string, TRUE);
+}
+
+/**
+ * Populate the status bar with summary info of our diff.
+ */
+static void results_update_stats(results_t * r)
+{
+ GString *string = g_string_new("");
+ int i, j, forms[5];
+ size_t diffs[5] = { 0, 0, 0, 0, 0 };
+ for (i = 0; i < NUM_RESULT_ITEMS; i++) {
+ if (result_item_is_supported(r->items[i])) {
+ const char *label;
+ result_item_get_forms(r->items[i], forms);
+ label = result_item_get_label(r->items[i]);
+ for (j = 0; j < 5; j++) {
+ if (forms[j] > 0) {
+ diffs[j] += result_item_get_num_differences(r->items[i], form_map[j]);
+ }
+ }
+ }
+ }
+ g_string_printf(string, "Total Differences: %zd", diffs[0] + diffs[1] + diffs[2] + diffs[3] + diffs[4]);
+ gtk_label_set_text(r->stats, string->str);
+ g_string_free(string, TRUE);
+}
+
+void results_update(results_t * r)
+{
+ int i, j, forms[5], was_diff_run = 0;
+ poldiff_t *diff = toplevel_get_poldiff(r->top);
+
+ results_clear(r);
+
+ for (i = 0; i < NUM_RESULT_ITEMS; i++) {
+ result_item_poldiff_run(r->items[i], diff, 0);
+ }
+ /* only show diff-relevant buffers if a diff was actually run */
+ for (i = 0; i < NUM_RESULT_ITEMS; i++) {
+ if (result_item_is_supported(r->items[i])) {
+ result_item_get_forms(r->items[i], forms);
+ for (j = 0; j < 5; j++) {
+ if (forms[j] > 0) {
+ was_diff_run = 1;
+ break;
+ }
+ }
+ }
+ }
+ if (was_diff_run) {
+ results_update_summary(r);
+ results_populate_key_buffer(r);
+ results_update_stats(r);
+
+ /* select the summary item */
+ GtkTreeSelection *selection = gtk_tree_view_get_selection(r->summary_view);
+ GtkTreeIter iter;
+ gtk_tree_model_get_iter_first(GTK_TREE_MODEL(r->summary_tree), &iter);
+ gtk_tree_selection_select_iter(selection, &iter);
+ }
+}
+
+void results_switch_to_page(results_t * r)
+{
+ GtkTreeSelection *selection = gtk_tree_view_get_selection(r->summary_view);
+ GtkTreeIter iter;
+ gboolean sens = FALSE;
+ if (gtk_tree_selection_get_selected(selection, NULL, &iter)) {
+ int f;
+ result_item_t *item;
+ results_sort_e sort;
+ results_sort_dir_e dir;
+ gtk_tree_model_get(GTK_TREE_MODEL(r->summary_tree), &iter, RESULTS_SUMMARY_COLUMN_FORM, &f,
+ RESULTS_SUMMARY_COLUMN_ITEM, &item, -1);
+ poldiff_form_e form = (poldiff_form_e) f;
+ if (item != NULL && result_item_get_current_sort(item, form, &sort, &dir)) {
+ sens = TRUE;
+ }
+ }
+ toplevel_set_sort_menu_sensitivity(r->top, sens);
+}
+
+struct run_datum
+{
+ results_t *r;
+ progress_t *progress;
+ GtkTextBuffer *tb;
+};
+
+static gpointer results_get_slow_buffer_runner(gpointer data)
+{
+ struct run_datum *run = (struct run_datum *)data;
+ results_t *r = run->r;
+ run->tb = result_item_get_buffer(r->current_item, r->current_form);
+ progress_done(run->progress);
+ return NULL;
+}
+
+/**
+ * Spawn a thread that will get the result_item buffer as given by
+ * r->current_item and r->current_form. This will display a progress
+ * dialog while waiting.
+ */
+static GtkTextBuffer *results_get_slow_buffer(results_t * r, const char *action)
+{
+ struct run_datum run;
+ run.r = r;
+ run.progress = toplevel_get_progress(r->top);
+ util_cursor_wait(GTK_WIDGET(toplevel_get_window(r->top)));
+ GString *s = g_string_new("");
+ g_string_printf(s, "Rendering %s", result_item_get_label(r->current_item));
+ progress_show(run.progress, s->str);
+ g_string_free(s, TRUE);
+ progress_update(run.progress, "%s", action);
+ g_thread_create(results_get_slow_buffer_runner, &run, FALSE, NULL);
+ progress_wait(run.progress);
+ util_cursor_clear(GTK_WIDGET(toplevel_get_window(r->top)));
+ progress_hide(run.progress);
+ return run.tb;
+}
+
+/**
+ * Callback invoked when the user selects an entry from the summary
+ * tree.
+ */
+static void results_summary_on_change(GtkTreeSelection * selection, gpointer user_data)
+{
+ results_t *r = (results_t *) user_data;
+ GtkTreeIter iter;
+ if (gtk_tree_selection_get_selected(selection, NULL, &iter)) {
+ int form;
+ GdkRectangle rect;
+ GtkTextIter textiter;
+ GtkTextMark *mark;
+ gint offset;
+ GtkTextBuffer *tb;
+ results_sort_e sort;
+ results_sort_dir_e dir;
+ gboolean sens = FALSE;
+
+ gtk_text_view_get_visible_rect(r->view, &rect);
+ gtk_text_view_get_iter_at_location(r->view, &textiter, rect.x, rect.y);
+ offset = gtk_text_iter_get_offset(&textiter);
+ if (r->current_item == NULL) {
+ r->summary_offset = offset;
+ } else {
+ result_item_save_current_line(r->current_item, r->current_form, offset);
+ }
+ gtk_tree_model_get(GTK_TREE_MODEL(r->summary_tree), &iter, RESULTS_SUMMARY_COLUMN_FORM, &form,
+ RESULTS_SUMMARY_COLUMN_ITEM, &r->current_item, -1);
+ r->current_form = (poldiff_form_e) form;
+ if (r->current_item == NULL) {
+ tb = r->main_buffer;
+ offset = r->summary_offset;
+ } else {
+ int render_is_slow = result_item_is_render_slow(r->current_item, r->current_form);
+ if (result_item_get_current_sort(r->current_item, r->current_form, &sort, &dir)) {
+ sens = TRUE;
+ toplevel_set_sort_menu_selection(r->top, sort, dir);
+ }
+ if (render_is_slow) {
+ tb = results_get_slow_buffer(r, "Displaying Items");
+ } else {
+ tb = result_item_get_buffer(r->current_item, r->current_form);
+ }
+ offset = result_item_get_current_line(r->current_item, r->current_form);
+ }
+
+ gtk_text_view_set_buffer(r->view, tb);
+
+ /* restore saved location. use marks to ensure that
+ * we go to this position even if it hasn't been
+ * drawn. */
+ gtk_text_buffer_get_start_iter(tb, &textiter);
+ gtk_text_iter_set_offset(&textiter, offset);
+ mark = gtk_text_buffer_create_mark(tb, "location-mark", &textiter, FALSE);
+ gtk_text_view_scroll_to_mark(r->view, mark, 0.0, TRUE, 0.0, 0.0);
+ gtk_text_buffer_delete_mark(tb, mark);
+
+ toplevel_set_sort_menu_sensitivity(r->top, sens);
+ }
+}
+
+static void results_summary_on_row_activate(GtkTreeView * tree_view, GtkTreePath * path, GtkTreeViewColumn * column
+ __attribute__ ((unused)), gpointer user_data __attribute__ ((unused)))
+{
+ gboolean expanded = gtk_tree_view_row_expanded(tree_view, path);
+ if (!expanded) {
+ gtk_tree_view_expand_row(tree_view, path, 1);
+ } else {
+ gtk_tree_view_collapse_row(tree_view, path);
+ }
+}
+
+/**
+ * Callback invoked when the user clicks on an inline link. This will
+ * spawn a pop-up menu where the user and get more information on the
+ * clicked string (which is hopefully an AV rule's permission).
+ */
+static gboolean results_on_inline_link_event(GtkTextTag * tag, GObject * event_object
+ __attribute__ ((unused)), GdkEvent * event, const GtkTextIter * iter,
+ gpointer user_data)
+{
+ results_t *r = (results_t *) user_data;
+ if (event->type == GDK_BUTTON_PRESS) {
+ GtkTextIter *start = gtk_text_iter_copy(iter);
+ while (!gtk_text_iter_begins_tag(start, tag))
+ gtk_text_iter_backward_char(start);
+ GtkTextIter *end = gtk_text_iter_copy(start);
+ while (!gtk_text_iter_ends_tag(end, tag))
+ gtk_text_iter_forward_char(end);
+ gint line = gtk_text_iter_get_line(start);
+ char *s = gtk_text_iter_get_slice(start, end);
+ result_item_inline_link_event(r->current_item, r->top, GTK_WIDGET(r->view), (GdkEventButton *) event,
+ r->current_form, line, s);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * Callback invoked when the user clicks on a line number tag. This
+ * will flip to the appropriate policy's source page and jump to that
+ * line.
+ */
+static gboolean results_on_line_event(GtkTextTag * tag, GObject * event_object __attribute__ ((unused)),
+ GdkEvent * event, const GtkTextIter * iter, gpointer user_data)
+{
+ results_t *r = (results_t *) user_data;
+ sediffx_policy_e which_pol = -1;
+ unsigned long line;
+ GtkTextIter *start, *end;
+ if (event->type == GDK_BUTTON_PRESS) {
+ start = gtk_text_iter_copy(iter);
+
+ while (!gtk_text_iter_starts_word(start))
+ gtk_text_iter_backward_char(start);
+ end = gtk_text_iter_copy(start);
+ while (!gtk_text_iter_ends_word(end))
+ gtk_text_iter_forward_char(end);
+
+ /* the line # in policy starts with 1, in the buffer it
+ * starts at 0 */
+ line = atoi(gtk_text_iter_get_slice(start, end)) - 1;
+ if (tag == r->policy_orig_tag) {
+ which_pol = SEDIFFX_POLICY_ORIG;
+ } else if (tag == r->policy_mod_tag) {
+ which_pol = SEDIFFX_POLICY_MOD;
+ } else {
+ /* should never get here */
+ assert(0);
+ }
+ toplevel_show_policy_line(r->top, which_pol, line);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static gboolean results_on_popup_menu(GtkWidget * widget, gpointer user_data)
+{
+ results_t *r = (results_t *) user_data;
+ GtkTextView *textview = GTK_TEXT_VIEW(widget);
+ gint ex, ey, x, y;
+ GtkTextBuffer *buffer;
+ GtkTextIter iter;
+ GSList *tags, *tagp;
+ gboolean hovering = FALSE;
+ gdk_window_at_pointer(&ex, &ey);
+ gtk_text_view_window_to_buffer_coords(textview, GTK_TEXT_WINDOW_WIDGET, ex, ey, &x, &y);
+ buffer = gtk_text_view_get_buffer(textview);
+ gtk_text_view_get_iter_at_location(textview, &iter, x, y);
+ tags = gtk_text_iter_get_tags(&iter);
+ for (tagp = tags; tagp != NULL; tagp = tagp->next) {
+ if (strcmp(GTK_TEXT_TAG(tagp->data)->name, "inline-link") == 0) {
+ hovering = TRUE;
+ break;
+ }
+ }
+ if (hovering) {
+ GtkTextIter *start = gtk_text_iter_copy(&iter);
+ while (!gtk_text_iter_begins_tag(start, GTK_TEXT_TAG(tagp->data)))
+ gtk_text_iter_backward_char(start);
+ GtkTextIter *end = gtk_text_iter_copy(start);
+ while (!gtk_text_iter_ends_tag(end, GTK_TEXT_TAG(tagp->data)))
+ gtk_text_iter_forward_char(end);
+ gint line = gtk_text_iter_get_line(start);
+ char *s = gtk_text_iter_get_slice(start, end);
+ g_slist_free(tags);
+ result_item_inline_link_event(r->current_item, r->top, widget, NULL, r->current_form, line, s);
+ return TRUE;
+ }
+ g_slist_free(tags);
+ return FALSE;
+}
+
+/**
+ * Set the cursor to a hand when user scrolls over a line number in
+ * when displaying te diff.
+ */
+static gboolean results_on_text_view_motion(GtkWidget * widget, GdkEventMotion * event, gpointer user_data __attribute__ ((unused)))
+{
+ GtkTextBuffer *buffer;
+ GtkTextView *textview;
+ GdkCursor *cursor;
+ GtkTextIter iter;
+ GSList *tags, *tagp;
+ gint x, ex, ey, y;
+ int hovering = 0;
+
+ textview = GTK_TEXT_VIEW(widget);
+
+ if (event->is_hint) {
+ gdk_window_get_pointer(event->window, &ex, &ey, NULL);
+ } else {
+ ex = event->x;
+ ey = event->y;
+ }
+
+ gtk_text_view_window_to_buffer_coords(textview, GTK_TEXT_WINDOW_WIDGET, ex, ey, &x, &y);
+ buffer = gtk_text_view_get_buffer(textview);
+ gtk_text_view_get_iter_at_location(textview, &iter, x, y);
+ tags = gtk_text_iter_get_tags(&iter);
+ for (tagp = tags; tagp != NULL; tagp = tagp->next) {
+ if (strncmp(GTK_TEXT_TAG(tagp->data)->name, "line", 4) == 0 ||
+ strcmp(GTK_TEXT_TAG(tagp->data)->name, "inline-link") == 0) {
+ hovering = TRUE;
+ break;
+ }
+ }
+ if (hovering) {
+ cursor = gdk_cursor_new(GDK_HAND2);
+ gdk_window_set_cursor(event->window, cursor);
+ gdk_cursor_unref(cursor);
+ gdk_flush();
+ } else {
+ gdk_window_set_cursor(event->window, NULL);
+ }
+ g_slist_free(tags);
+ return FALSE;
+}
+
+void results_sort(results_t * r, results_sort_e field, results_sort_dir_e direction)
+{
+ GtkTreeSelection *selection = gtk_tree_view_get_selection(r->summary_view);
+ GtkTreeIter iter;
+ int f;
+ result_item_t *item;
+#ifndef NDEBUG
+ results_sort_e sort;
+ results_sort_dir_e dir;
+#endif
+ if (!gtk_tree_selection_get_selected(selection, NULL, &iter)) {
+ return;
+ }
+ gtk_tree_model_get(GTK_TREE_MODEL(r->summary_tree), &iter, RESULTS_SUMMARY_COLUMN_FORM, &f,
+ RESULTS_SUMMARY_COLUMN_ITEM, &item, -1);
+ poldiff_form_e form = (poldiff_form_e) f;
+ assert(item != NULL && item == r->current_item && form == r->current_form
+ && result_item_get_current_sort(item, form, &sort, &dir) != 0);
+ result_item_set_current_sort(item, form, field, direction);
+ if (result_item_is_render_slow(r->current_item, form)) {
+ results_get_slow_buffer(r, "Sorting Items");
+ } else {
+ result_item_get_buffer(item, form);
+ }
+}
+
+GtkTextView *results_get_text_view(results_t * r)
+{
+ return r->view;
+}