diff options
Diffstat (limited to 'libreport/src/gui-wizard-gtk/wizard.c')
-rw-r--r-- | libreport/src/gui-wizard-gtk/wizard.c | 1782 |
1 files changed, 0 insertions, 1782 deletions
diff --git a/libreport/src/gui-wizard-gtk/wizard.c b/libreport/src/gui-wizard-gtk/wizard.c deleted file mode 100644 index d7164d6d..00000000 --- a/libreport/src/gui-wizard-gtk/wizard.c +++ /dev/null @@ -1,1782 +0,0 @@ -/* - Copyright (C) 2011 ABRT Team - Copyright (C) 2011 RedHat 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 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 <gtk/gtk.h> -#include "abrt_dbus.h" -#include "libreport-gtk.h" -#include "wizard.h" - -#define DEFAULT_WIDTH 800 -#define DEFAULT_HEIGHT 500 - -#if GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION < 22 -# define gtk_assistant_commit(...) ((void)0) -#endif - -typedef struct event_gui_data_t -{ - char *event_name; - GtkToggleButton *toggle_button; -} event_gui_data_t; - - -static GtkAssistant *g_assistant; - -static char *g_analyze_event_selected; -static char *g_reporter_events_selected; -static unsigned g_black_event_count = 0; - -static GtkBox *g_box_analyzers; -/* List of event_gui_data's */ -static GList *g_list_analyzers; -static GtkLabel *g_lbl_analyze_log; -static GtkTextView *g_tv_analyze_log; - -static GtkBox *g_box_reporters; -/* List of event_gui_data's */ -static GList *g_list_reporters; -static GtkLabel *g_lbl_report_log; -static GtkTextView *g_tv_report_log; - -static GtkContainer *g_container_details1; -static GtkContainer *g_container_details2; - -static GtkLabel *g_lbl_cd_reason; -static GtkTextView *g_tv_backtrace; -static GtkTextView *g_tv_comment; -static GtkEventBox *g_eb_comment; -static GtkWidget *g_widget_warnings_area; -static GtkBox *g_box_warning_labels; -static GtkToggleButton *g_tb_approve_bt; -static GtkButton *g_btn_refresh; - -static GtkLabel *g_lbl_reporters; -static GtkLabel *g_lbl_size; - -static GtkTreeView *g_tv_details; -static GtkCellRenderer *g_tv_details_renderer_value; -static GtkTreeViewColumn *g_tv_details_col_checkbox; -//static GtkCellRenderer *g_tv_details_renderer_checkbox; -static GtkListStore *g_ls_details; -enum -{ - /* Note: need to update types in - * gtk_list_store_new(DETAIL_NUM_COLUMNS, TYPE1, TYPE2...) - * if you change these: - */ - DETAIL_COLUMN_CHECKBOX, - DETAIL_COLUMN_NAME, - DETAIL_COLUMN_VALUE, - DETAIL_NUM_COLUMNS, -}; - -/* Search in bt */ -static guint g_timeout = 0; -static GtkEntry *g_search_entry_bt; - -static GtkBuilder *builder; -static PangoFontDescription *monospace_font; - - -/* THE PAGE FLOW - * page_5: user comments - * page_1: analyze action selection - * page_2: analyze progress - * page_3: reporter selection - * page_4: backtrace editor - * page_6: summary - * page_7: reporting progress - */ -enum { - PAGENO_SUMMARY, - PAGENO_EDIT_COMMENT, - PAGENO_ANALYZE_SELECTOR, - PAGENO_ANALYZE_PROGRESS, - PAGENO_REPORTER_SELECTOR, - PAGENO_EDIT_BACKTRACE, - PAGENO_REVIEW_DATA, - PAGENO_REPORT_PROGRESS, - PAGENO_REPORT_DONE, - PAGENO_NOT_SHOWN, - NUM_PAGES -}; - -/* Use of arrays (instead of, say, #defines to C strings) - * allows cheaper page_obj_t->name == PAGE_FOO comparisons - * instead of strcmp. - */ -static const gchar PAGE_SUMMARY[] = "page_0"; -static const gchar PAGE_EDIT_COMMENT[] = "page_1"; -static const gchar PAGE_ANALYZE_SELECTOR[] = "page_2"; -static const gchar PAGE_ANALYZE_PROGRESS[] = "page_3"; -static const gchar PAGE_REPORTER_SELECTOR[] = "page_4_report"; -static const gchar PAGE_EDIT_BACKTRACE[] = "page_5"; -static const gchar PAGE_REVIEW_DATA[] = "page_6_report"; -static const gchar PAGE_REPORT_PROGRESS[] = "page_7_report"; -static const gchar PAGE_REPORT_DONE[] = "page_8_report"; -static const gchar PAGE_NOT_SHOWN[] = "page_9_report"; - -static const gchar *const page_names[] = -{ - PAGE_SUMMARY, - PAGE_EDIT_COMMENT, - PAGE_ANALYZE_SELECTOR, - PAGE_ANALYZE_PROGRESS, - PAGE_REPORTER_SELECTOR, - PAGE_EDIT_BACKTRACE, - PAGE_REVIEW_DATA, - PAGE_REPORT_PROGRESS, - PAGE_REPORT_DONE, - PAGE_NOT_SHOWN, - NULL -}; - -typedef struct -{ - const gchar *name; - const gchar *title; - GtkAssistantPageType type; - GtkWidget *page_widget; -} page_obj_t; - -static page_obj_t pages[] = -{ - /* Page types: - * CONTENT: normal page (has all btns: [Cancel] [Last] [Back] [Fwd]) - * INTRO: only [Fwd] button is shown - * (we use these where we want to suppress [Back]-navigation) - * CONFIRM: has [Apply] instead of [Fwd] and emits "apply" signal - * PROGRESS: skipped on [Back] navigation - * SUMMARY: has only [Close] button - * - * Note that we suppress [Cancel] everywhere once and for all - * using gtk_assistant_commit at init time. - */ - /* glade element name , on-screen text , type */ - { PAGE_SUMMARY , "Problem description" , GTK_ASSISTANT_PAGE_CONTENT }, - { PAGE_EDIT_COMMENT,"Provide additional information", GTK_ASSISTANT_PAGE_CONTENT }, - { PAGE_ANALYZE_SELECTOR , "Select analyzer" , GTK_ASSISTANT_PAGE_CONFIRM }, - { PAGE_ANALYZE_PROGRESS , "Analyzing" , GTK_ASSISTANT_PAGE_INTRO }, - /* Some reporters don't need backtrace, we can skip bt page for them. - * Therefore we want to know reporters _before_ we go to bt page - */ - { PAGE_REPORTER_SELECTOR , "Select reporter" , GTK_ASSISTANT_PAGE_CONTENT }, - { PAGE_EDIT_BACKTRACE , "Review the backtrace" , GTK_ASSISTANT_PAGE_CONTENT }, - { PAGE_REVIEW_DATA , "Confirm data to report", GTK_ASSISTANT_PAGE_CONFIRM }, - /* Was GTK_ASSISTANT_PAGE_PROGRESS, but we want to allow returning to it */ - { PAGE_REPORT_PROGRESS , "Reporting" , GTK_ASSISTANT_PAGE_INTRO }, - { PAGE_REPORT_DONE , "Reporting done" , GTK_ASSISTANT_PAGE_CONTENT }, - /* We prevent user from reaching this page, as SUMMARY can't be navigated away - * (must be always closed) and we don't want that - */ - { PAGE_NOT_SHOWN , "" , GTK_ASSISTANT_PAGE_SUMMARY }, - { NULL } -}; - -static page_obj_t *added_pages[NUM_PAGES]; - - -/* Utility functions */ - -static void remove_child_widget(GtkWidget *widget, gpointer unused) -{ - /* Destroy will safely remove it and free the memory - * if there are no refs left - */ - gtk_widget_destroy(widget); -} - -static void save_dialog_response(GtkDialog *dialog, gint response_id, gpointer user_data) -{ - *(gint*)user_data = response_id; -} - -struct dump_dir *steal_if_needed(struct dump_dir *dd) -{ - if (!dd) - xfunc_die(); /* error msg was already logged */ - - if (dd->locked) - return dd; - - dd_close(dd); - - char *HOME = getenv("HOME"); - if (!HOME || !HOME[0]) - { - struct passwd *pw = getpwuid(getuid()); - HOME = pw ? pw->pw_dir : NULL; - } - if (HOME && HOME[0]) - HOME = concat_path_file(HOME, ".abrt/spool"); - else - HOME = xstrdup("/tmp"); - - GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(g_assistant), - GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_MESSAGE_QUESTION, - GTK_BUTTONS_OK_CANCEL, - _("Need writable directory, but '%s' is not writable." - " Move it to '%s' and operate on the moved copy?"), - g_dump_dir_name, HOME - ); - gint response = GTK_RESPONSE_CANCEL; - g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(save_dialog_response), &response); - gtk_dialog_run(GTK_DIALOG(dialog)); - gtk_widget_destroy(dialog); - - if (response != GTK_RESPONSE_OK) - return NULL; - - dd = steal_directory(HOME, g_dump_dir_name); - if (!dd) - return NULL; /* Stealing failed. Error msg was already logged */ - - /* Delete old dir and switch to new one. - * Don't want to keep new dd open across deletion, - * therefore it's a bit more complicated. - */ - char *old_name = g_dump_dir_name; - g_dump_dir_name = xstrdup(dd->dd_dirname); - dd_close(dd); - - gtk_window_set_title(GTK_WINDOW(g_assistant), g_dump_dir_name); - delete_dump_dir_possibly_using_abrtd(old_name); //TODO: if (deletion_failed) error_msg("BAD")? - free(old_name); - - dd = dd_opendir(g_dump_dir_name, 0); - if (!dd) - xfunc_die(); /* error msg was already logged */ - - return dd; -} - -void show_error_as_msgbox(const char *msg) -{ - GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(g_assistant), - GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_MESSAGE_WARNING, - GTK_BUTTONS_CLOSE, - "%s", msg - ); - gtk_dialog_run(GTK_DIALOG(dialog)); - gtk_widget_destroy(dialog); -} - -static void load_text_to_text_view(GtkTextView *tv, const char *name) -{ - const char *str = g_cd ? get_problem_item_content_or_NULL(g_cd, name) : NULL; - gtk_text_buffer_set_text(gtk_text_view_get_buffer(tv), (str ? str : ""), -1); -} - -static gchar *get_malloced_string_from_text_view(GtkTextView *tv) -{ - GtkTextBuffer *buffer = gtk_text_view_get_buffer(tv); - GtkTextIter start; - GtkTextIter end; - gtk_text_buffer_get_start_iter(buffer, &start); - gtk_text_buffer_get_end_iter(buffer, &end); - return gtk_text_buffer_get_text(buffer, &start, &end, FALSE); -} - -static void save_text_if_changed(const char *name, const char *new_value) -{ - const char *old_value = g_cd ? get_problem_item_content_or_NULL(g_cd, name) : ""; - if (!old_value) - old_value = ""; - if (strcmp(new_value, old_value) != 0) - { - struct dump_dir *dd = dd_opendir(g_dump_dir_name, DD_OPEN_READONLY); - dd = steal_if_needed(dd); - if (dd && dd->locked) - { - dd_save_text(dd, name, new_value); - } -//FIXME: else: what to do with still-unsaved data in the widget?? - dd_close(dd); - reload_problem_data_from_dump_dir(); - update_gui_state_from_problem_data(); - } -} - -static void save_text_from_text_view(GtkTextView *tv, const char *name) -{ - gchar *new_str = get_malloced_string_from_text_view(tv); - save_text_if_changed(name, new_str); - free(new_str); -} - -static void append_to_textview(GtkTextView *tv, const char *str) -{ - GtkTextBuffer *tb = gtk_text_view_get_buffer(tv); - - /* Ensure we insert text at the end */ - GtkTextIter text_iter; - gtk_text_buffer_get_iter_at_offset(tb, &text_iter, -1); - gtk_text_buffer_place_cursor(tb, &text_iter); - - gtk_text_buffer_insert_at_cursor(tb, str, strlen(str)); - - /* Scroll so that the end of the log is visible */ - gtk_text_buffer_get_iter_at_offset(tb, &text_iter, -1); - gtk_text_view_scroll_to_iter(tv, &text_iter, - /*within_margin:*/ 0.0, /*use_align:*/ FALSE, /*xalign:*/ 0, /*yalign:*/ 0); -} - - -/* event_gui_data_t */ - -static event_gui_data_t *new_event_gui_data_t(void) -{ - return xzalloc(sizeof(event_gui_data_t)); -} - -static void free_event_gui_data_t(event_gui_data_t *evdata, void *unused) -{ - if (evdata) - { - free(evdata->event_name); - free(evdata); - } -} - - -/* tv_details handling */ - -static struct problem_item *get_current_problem_item_or_NULL(GtkTreeView *tree_view, gchar **pp_item_name) -{ - GtkTreeModel *model; - GtkTreeIter iter; - GtkTreeSelection* selection = gtk_tree_view_get_selection(tree_view); - if (!gtk_tree_selection_get_selected(selection, &model, &iter)) - return NULL; - - *pp_item_name = NULL; - gtk_tree_model_get(model, &iter, - DETAIL_COLUMN_NAME, pp_item_name, - -1); - if (!*pp_item_name) /* paranoia, should never happen */ - return NULL; - struct problem_item *item = get_problem_data_item_or_NULL(g_cd, *pp_item_name); - - return item; -} - -static void tv_details_row_activated( - GtkTreeView *tree_view, - GtkTreePath *tree_path_UNUSED, - GtkTreeViewColumn *column, - gpointer user_data) -{ - gchar *item_name; - struct problem_item *item = get_current_problem_item_or_NULL(tree_view, &item_name); - if (!item || !(item->flags & CD_FLAG_TXT)) - goto ret; - if (!strchr(item->content, '\n')) /* one line? */ - goto ret; /* yes */ - - gchar *arg[3]; - arg[0] = (char *) "xdg-open"; - arg[1] = concat_path_file(g_dump_dir_name, item_name); - arg[2] = NULL; - - g_spawn_sync(NULL, arg, NULL, - G_SPAWN_SEARCH_PATH | G_SPAWN_STDOUT_TO_DEV_NULL, - NULL, NULL, NULL, NULL, NULL, NULL); - - free(arg[1]); - ret: - g_free(item_name); -} - -/* static gboolean tv_details_select_cursor_row( - GtkTreeView *tree_view, - gboolean arg1, - gpointer user_data) {...} */ - -static void tv_details_cursor_changed( - GtkTreeView *tree_view, - gpointer user_data_UNUSED) -{ - gchar *item_name; - struct problem_item *item = get_current_problem_item_or_NULL(tree_view, &item_name); - g_free(item_name); - - gboolean editable = (item && (item->flags & CD_FLAG_TXT) && !strchr(item->content, '\n')); - - /* Allow user to select the text with mouse. - * Has undesirable side-effect of allowing user to "edit" the text, - * but changes aren't saved (the old text reappears as soon as user - * leaves the field). Need to disable editing somehow. - */ - g_object_set(G_OBJECT(g_tv_details_renderer_value), - "editable", editable, - NULL); -} - -static void g_tv_details_checkbox_toggled( - GtkCellRendererToggle *cell_renderer_UNUSED, - gchar *tree_path, - gpointer user_data_UNUSED) -{ - //log("%s: path:'%s'", __func__, tree_path); - GtkTreeIter iter; - if (!gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(g_ls_details), &iter, tree_path)) - return; - - gchar *item_name = NULL; - gtk_tree_model_get(GTK_TREE_MODEL(g_ls_details), &iter, - DETAIL_COLUMN_NAME, &item_name, - -1); - if (!item_name) /* paranoia, should never happen */ - return; - struct problem_item *item = get_problem_data_item_or_NULL(g_cd, item_name); - g_free(item_name); - if (!item) /* paranoia */ - return; - - int cur_value; - if (item->selected_by_user == 0) - cur_value = item->default_by_reporter; - else - cur_value = !!(item->selected_by_user + 1); /* map -1,1 to 0,1 */ - //log("%s: allowed:%d reqd:%d def:%d user:%d cur:%d", __func__, - // item->allowed_by_reporter, - // item->required_by_reporter, - // item->default_by_reporter, - // item->selected_by_user, - // cur_value - //); - if (item->allowed_by_reporter && !item->required_by_reporter) - { - cur_value = !cur_value; - item->selected_by_user = cur_value * 2 - 1; /* map 0,1 to -1,1 */ - //log("%s: now ->selected_by_user=%d", __func__, item->selected_by_user); - gtk_list_store_set(g_ls_details, &iter, - DETAIL_COLUMN_CHECKBOX, cur_value, - -1); - } -} - - -/* update_gui_state_from_problem_data */ - -static gint find_by_button(gconstpointer a, gconstpointer button) -{ - const event_gui_data_t *evdata = a; - return (evdata->toggle_button != button); -} - -static void analyze_rb_was_toggled(GtkButton *button, gpointer user_data) -{ - free(g_analyze_event_selected); - g_analyze_event_selected = NULL; - GList *found = g_list_find_custom(g_list_analyzers, button, find_by_button); - if (found) - { - event_gui_data_t *evdata = found->data; - g_analyze_event_selected = xstrdup(evdata->event_name); - } -} - -static void report_tb_was_toggled(GtkButton *button_unused, gpointer user_data_unused) -{ - struct strbuf *reporters_string = strbuf_new(); - GList *li = g_list_reporters; - for (; li; li = li->next) - { - event_gui_data_t *event_gui_data = li->data; - if (gtk_toggle_button_get_active(event_gui_data->toggle_button) == TRUE) - { - strbuf_append_strf(reporters_string, - "%s%s", - (reporters_string->len != 0 ? ", " : ""), - event_gui_data->event_name - ); - g_validate_event(event_gui_data->event_name); - } - } - - gtk_assistant_set_page_complete(g_assistant, - pages[PAGENO_REPORTER_SELECTOR].page_widget, - reporters_string->len != 0 /* true if at least one checkbox is active */ - ); - - /* Update "list of reporters" label */ - free(g_reporter_events_selected); - g_reporter_events_selected = strbuf_free_nobuf(reporters_string); - gtk_label_set_text(g_lbl_reporters, g_reporter_events_selected); -} - -/* event_name contains "EVENT1\nEVENT2\nEVENT3\n". - * Add new {radio/check}buttons to GtkBox for each EVENTn (type depends on bool radio). - * Remember them in GList **p_event_list (list of event_gui_data_t's). - * Set "toggled" callback on each button to given GCallback if it's not NULL. - * Return active button (or NULL if none created). - */ -static event_gui_data_t *add_event_buttons(GtkBox *box, - GList **p_event_list, - char *event_name, - GCallback func, - bool radio) -{ - //VERB2 log("removing all buttons from box %p", box); - gtk_container_foreach(GTK_CONTAINER(box), &remove_child_widget, NULL); - g_list_foreach(*p_event_list, (GFunc)free_event_gui_data_t, NULL); - g_list_free(*p_event_list); - *p_event_list = NULL; - - if (radio) - g_black_event_count = 0; - - event_gui_data_t *first_button = NULL; - event_gui_data_t *active_button = NULL; - while (event_name[0]) - { - char *event_name_end = strchr(event_name, '\n'); - *event_name_end = '\0'; - - event_config_t *cfg = get_event_config(event_name); - - /* Form a pretty text representation of event */ - /* By default, use event name, just strip "foo_" prefix if it exists: */ - const char *event_screen_name = strchr(event_name, '_'); - if (event_screen_name) - event_screen_name++; - else - event_screen_name = event_name; - - const char *event_description = NULL; - char *tmp_description = NULL; - bool green_choice = false; - if (cfg) - { - /* .xml has (presumably) prettier description, use it: */ - if (cfg->screen_name) - event_screen_name = cfg->screen_name; - event_description = cfg->description; - if (cfg->ec_creates_items) - { - if (get_problem_data_item_or_NULL(g_cd, cfg->ec_creates_items)) - { - green_choice = true; - event_description = tmp_description = xasprintf(_("(not needed, '%s' already exists)"), cfg->ec_creates_items); - } - } - } - if (radio && !green_choice) - g_black_event_count++; - - //VERB2 log("adding button '%s' to box %p", event_name, box); - char *event_label = xasprintf("%s%s%s", - event_screen_name, - (event_description ? " - " : ""), - event_description ? event_description : "" - ); - free(tmp_description); - - GtkWidget *button = radio - ? gtk_radio_button_new_with_label_from_widget( - (first_button ? GTK_RADIO_BUTTON(first_button->toggle_button) : NULL), - event_label - ) - : gtk_check_button_new_with_label(event_label); - free(event_label); - - if (green_choice) - { - //static const GdkColor red = { .red = 0xffff }; - //gtk_widget_modify_text(button, GTK_STATE_NORMAL, &red); - GtkWidget *child = gtk_bin_get_child(GTK_BIN(button)); - if (child) - { - static const GdkColor green = { .green = 0x7fff }; - gtk_widget_modify_fg(child, GTK_STATE_NORMAL, &green); - gtk_widget_modify_fg(child, GTK_STATE_ACTIVE, &green); - gtk_widget_modify_fg(child, GTK_STATE_PRELIGHT, &green); - } - } - - if (func) - g_signal_connect(G_OBJECT(button), "toggled", func, NULL); - if (cfg && cfg->long_descr) - gtk_widget_set_tooltip_text(button, cfg->long_descr); - - event_gui_data_t *event_gui_data = new_event_gui_data_t(); - event_gui_data->event_name = xstrdup(event_name); - event_gui_data->toggle_button = GTK_TOGGLE_BUTTON(button); - *p_event_list = g_list_append(*p_event_list, event_gui_data); - - if (!first_button) - first_button = event_gui_data; - - if (radio && !green_choice && !active_button) - { - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), true); - active_button = event_gui_data; - } - - *event_name_end = '\n'; - event_name = event_name_end + 1; - - gtk_box_pack_start(box, button, /*expand*/ false, /*fill*/ false, /*padding*/ 0); - } - - if (radio) - { - const char *msg_proceed_to_reporting = _("Go to reporting step"); - GtkWidget *button = radio - ? gtk_radio_button_new_with_label_from_widget( - (first_button ? GTK_RADIO_BUTTON(first_button->toggle_button) : NULL), - msg_proceed_to_reporting - ) - : gtk_check_button_new_with_label(msg_proceed_to_reporting); - if (func) - g_signal_connect(G_OBJECT(button), "toggled", func, NULL); - - event_gui_data_t *event_gui_data = new_event_gui_data_t(); - event_gui_data->event_name = xstrdup(""); - event_gui_data->toggle_button = GTK_TOGGLE_BUTTON(button); - *p_event_list = g_list_append(*p_event_list, event_gui_data); - - if (!active_button) - { - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), true); - active_button = event_gui_data; - } - - gtk_box_pack_start(box, button, /*expand*/ false, /*fill*/ false, /*padding*/ 0); - } - - return active_button; -} - -struct cd_stats { - off_t filesize; - unsigned filecount; -}; - -static void append_item_to_ls_details(gpointer name, gpointer value, gpointer data) -{ - problem_item *item = (problem_item*)value; - struct cd_stats *stats = data; - GtkTreeIter iter; - - gtk_list_store_append(g_ls_details, &iter); - stats->filecount++; - - //FIXME: use the human-readable format_problem_item(item) instead of item->content. - if (item->flags & CD_FLAG_TXT) - { - stats->filesize += strlen(item->content); - /* If not multiline... */ - if (!strchr(item->content, '\n')) - { - gtk_list_store_set(g_ls_details, &iter, - DETAIL_COLUMN_NAME, (char *)name, - DETAIL_COLUMN_VALUE, item->content, - -1); - } - else - { - gtk_list_store_set(g_ls_details, &iter, - DETAIL_COLUMN_NAME, (char *)name, - DETAIL_COLUMN_VALUE, _("(click here to view/edit)"), - -1); - } - } - else if (item->flags & CD_FLAG_BIN) - { - struct stat statbuf; - statbuf.st_size = 0; - stat(item->content, &statbuf); - stats->filesize += statbuf.st_size; - char *msg = xasprintf(_("(binary file, %llu bytes)"), (long long)statbuf.st_size); - gtk_list_store_set(g_ls_details, &iter, - DETAIL_COLUMN_NAME, (char *)name, - DETAIL_COLUMN_VALUE, msg, - -1); - free(msg); - } -} - -void update_gui_state_from_problem_data(void) -{ - gtk_window_set_title(GTK_WINDOW(g_assistant), g_dump_dir_name); - - const char *reason = get_problem_item_content_or_NULL(g_cd, FILENAME_REASON); - gtk_label_set_text(g_lbl_cd_reason, reason ? reason : _("(no description)")); - - gtk_list_store_clear(g_ls_details); - struct cd_stats stats = { 0 }; - g_hash_table_foreach(g_cd, append_item_to_ls_details, &stats); - char *msg = xasprintf(_("%llu bytes, %u files"), (long long)stats.filesize, stats.filecount); - gtk_label_set_text(g_lbl_size, msg); - free(msg); - - load_text_to_text_view(g_tv_backtrace, FILENAME_BACKTRACE); - load_text_to_text_view(g_tv_comment, FILENAME_COMMENT); - - /* Update analyze radio buttons */ - event_gui_data_t *active_button = add_event_buttons(g_box_analyzers, &g_list_analyzers, - g_analyze_events, G_CALLBACK(analyze_rb_was_toggled), - /*radio:*/ true - ); - /* Update the value of currently selected analyzer */ - if (active_button) - { - free(g_analyze_event_selected); - g_analyze_event_selected = xstrdup(active_button->event_name); - VERB2 log("g_analyze_event_selected='%s'", g_analyze_event_selected); - } - - /* Update reporter checkboxes */ - /* Remember names of selected reporters */ - GList *old_reporters = NULL; - GList *li = g_list_reporters; - for (; li; li = li->next) - { - event_gui_data_t *event_gui_data = li->data; - if (gtk_toggle_button_get_active(event_gui_data->toggle_button) == TRUE) - { - /* order isn't important. prepend is faster */ - old_reporters = g_list_prepend(old_reporters, xstrdup(event_gui_data->event_name)); - } - } - /* Delete old checkboxes and create new ones */ - add_event_buttons(g_box_reporters, &g_list_reporters, - g_report_events, /*callback:*/ G_CALLBACK(report_tb_was_toggled), - /*radio:*/ false - ); - /* Re-select new reporters which were selected before we deleted them */ - GList *li_new = g_list_reporters; - for (; li_new; li_new = li_new->next) - { - event_gui_data_t *new_gui_data = li_new->data; - GList *li_old = old_reporters; - for (; li_old; li_old = li_old->next) - { - if (strcmp(new_gui_data->event_name, li_old->data) == 0) - { - gtk_toggle_button_set_active(new_gui_data->toggle_button, true); - break; - } - } - } - list_free_with_free(old_reporters); - - /* Update readiness state of reporter selector page and "list of reporters" label */ - report_tb_was_toggled(NULL, NULL); - - /* We can't just do gtk_widget_show_all once in main: - * We created new widgets (buttons). Need to make them visible. - */ - gtk_widget_show_all(GTK_WIDGET(g_assistant)); - - if (g_analyze_events[0]) - gtk_widget_show(GTK_WIDGET(g_btn_refresh)); - else - gtk_widget_hide(GTK_WIDGET(g_btn_refresh)); -} - - -/* start_event_run */ - -struct analyze_event_data -{ - struct run_event_state *run_state; - const char *event_name; - GList *more_events; - GList *env_list; - GtkWidget *page_widget; - GtkLabel *status_label; - GtkTextView *tv_log; - const char *end_msg; - GIOChannel *channel; - struct strbuf *event_log; - int event_log_state; - int fd; - /*guint event_source_id;*/ -}; -enum { - LOGSTATE_FIRSTLINE = 0, - LOGSTATE_BEGLINE, - LOGSTATE_ERRLINE, - LOGSTATE_MIDLINE, -}; - -static void set_excluded_envvar(void) -{ - struct strbuf *item_list = strbuf_new(); - const char *fmt = "%s"; - - GtkTreeIter iter; - if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(g_ls_details), &iter)) - { - do { - gchar *item_name = NULL; - gboolean checked = 0; - gtk_tree_model_get(GTK_TREE_MODEL(g_ls_details), &iter, - DETAIL_COLUMN_NAME, &item_name, - DETAIL_COLUMN_CHECKBOX, &checked, - -1); - if (!item_name) /* paranoia, should never happen */ - continue; - if (!checked) - { - strbuf_append_strf(item_list, fmt, item_name); - fmt = ",%s"; - } - g_free(item_name); - } while (gtk_tree_model_iter_next(GTK_TREE_MODEL(g_ls_details), &iter)); - } - char *var = strbuf_free_nobuf(item_list); - //log("EXCLUDE_FROM_REPORT='%s'", var); - if (var) - { - xsetenv("EXCLUDE_FROM_REPORT", var); - free(var); - } - else - unsetenv("EXCLUDE_FROM_REPORT"); -} - -static int spawn_next_command_in_evd(struct analyze_event_data *evd) -{ - evd->env_list = export_event_config(evd->event_name); - int r = spawn_next_command(evd->run_state, g_dump_dir_name, evd->event_name); - if (r < 0) - { - unexport_event_config(evd->env_list); - evd->env_list = NULL; - } - return r; -} - -static void save_to_event_log(struct analyze_event_data *evd, const char *str) -{ - static const char delim[] = { - [LOGSTATE_FIRSTLINE] = '>', - [LOGSTATE_BEGLINE] = ' ', - [LOGSTATE_ERRLINE] = '*', - }; - - while (str[0]) - { - char *end = strchrnul(str, '\n'); - char end_char = *end; - if (end_char == '\n') - end++; - switch (evd->event_log_state) - { - case LOGSTATE_FIRSTLINE: - case LOGSTATE_BEGLINE: - case LOGSTATE_ERRLINE: - /* skip empty lines */ - if (str[0] == '\n') - goto next; - strbuf_append_strf(evd->event_log, "%s%c %.*s", - iso_date_string(NULL), - delim[evd->event_log_state], - (int)(end - str), str - ); - break; - case LOGSTATE_MIDLINE: - strbuf_append_strf(evd->event_log, "%.*s", (int)(end - str), str); - break; - } - evd->event_log_state = LOGSTATE_MIDLINE; - if (end_char != '\n') - break; - evd->event_log_state = LOGSTATE_BEGLINE; - next: - str = end; - } -} - -static void update_event_log_on_disk(const char *str) -{ - /* Load existing log */ - struct dump_dir *dd = dd_opendir(g_dump_dir_name, 0); - if (!dd) - return; - char *event_log = dd_load_text_ext(dd, FILENAME_EVENT_LOG, DD_FAIL_QUIETLY_ENOENT); - - /* Append new log part to existing log */ - unsigned len = strlen(event_log); - if (len != 0 && event_log[len - 1] != '\n') - event_log = append_to_malloced_string(event_log, "\n"); - event_log = append_to_malloced_string(event_log, str); - - /* Trim log according to size watermarks */ - len = strlen(event_log); - char *new_log = event_log; - if (len > EVENT_LOG_HIGH_WATERMARK) - { - new_log += len - EVENT_LOG_LOW_WATERMARK; - new_log = strchrnul(new_log, '\n'); - if (new_log[0]) - new_log++; - } - - /* Save */ - dd_save_text(dd, FILENAME_EVENT_LOG, new_log); - free(event_log); - dd_close(dd); -} - -static gboolean consume_cmd_output(GIOChannel *source, GIOCondition condition, gpointer data) -{ - struct analyze_event_data *evd = data; - - /* Read and insert the output into the log pane */ - char buf[257]; /* usually we get one line, no need to have big buf */ - int r; - while ((r = read(evd->fd, buf, sizeof(buf)-1)) > 0) - { - buf[r] = '\0'; - append_to_textview(evd->tv_log, buf); - save_to_event_log(evd, buf); - } - - if (r < 0 && errno == EAGAIN) - /* We got all buffered data, but fd is still open. Done for now */ - return TRUE; /* "please don't remove this event (yet)" */ - - /* EOF/error */ - - unexport_event_config(evd->env_list); - evd->env_list = NULL; - - /* Wait for child to actually exit, collect status */ - int status; - waitpid(evd->run_state->command_pid, &status, 0); - int retval = WEXITSTATUS(status); - if (WIFSIGNALED(status)) - retval = WTERMSIG(status) + 128; - - /* Write a final message to the log */ - if (evd->event_log->len != 0 && evd->event_log->buf[evd->event_log->len - 1] != '\n') - save_to_event_log(evd, "\n"); - /* If program failed, or if it finished successfully without saying anything... */ - if (retval != 0 || evd->event_log_state == LOGSTATE_FIRSTLINE) - { - if (retval != 0) /* If program failed, emit error line */ - evd->event_log_state = LOGSTATE_ERRLINE; - char *msg; - if (WIFSIGNALED(status)) - msg = xasprintf("(killed by signal %u)\n", WTERMSIG(status)); - else - msg = xasprintf("(exited with %u)\n", retval); - append_to_textview(evd->tv_log, msg); - save_to_event_log(evd, msg); - free(msg); - } - - /* Append log to FILENAME_EVENT_LOG */ - update_event_log_on_disk(evd->event_log->buf); - strbuf_clear(evd->event_log); - evd->event_log_state = LOGSTATE_FIRSTLINE; - - if (geteuid() == 0) - { - /* Reset mode/uig/gid to correct values for all files created by event run */ - struct dump_dir *dd = dd_opendir(g_dump_dir_name, 0); - if (dd) - { - dd_sanitize_mode_and_owner(dd); - dd_close(dd); - } - } - - /* Stop if exit code is not 0, or no more commands */ - if (retval != 0 - || spawn_next_command_in_evd(evd) < 0 - ) { - VERB1 log("done running event on '%s': %d", g_dump_dir_name, retval); - append_to_textview(evd->tv_log, "\n"); - - for (;;) - { - if (!evd->more_events) - { - char *msg = xasprintf(evd->end_msg, retval); - gtk_label_set_text(evd->status_label, msg); - free(msg); - - /* Enable (un-gray out) navigation buttons */ - gtk_widget_set_sensitive(GTK_WIDGET(g_assistant), true); - - /*g_source_remove(evd->event_source_id);*/ - close(evd->fd); - free_run_event_state(evd->run_state); - strbuf_free(evd->event_log); - free(evd); - - reload_problem_data_from_dump_dir(); - update_gui_state_from_problem_data(); - - /* Inform abrt-gui that it is a good idea to rescan the directory */ - kill(getppid(), SIGCHLD); - - return FALSE; /* "please remove this event" */ - } - - evd->event_name = evd->more_events->data; - evd->more_events = g_list_remove(evd->more_events, evd->more_events->data); - - if (prepare_commands(evd->run_state, g_dump_dir_name, evd->event_name) != 0 - && spawn_next_command_in_evd(evd) >= 0 - ) { - VERB1 log("running event '%s' on '%s'", evd->event_name, g_dump_dir_name); - char *msg = xasprintf("--- Running %s ---\n", evd->event_name); - append_to_textview(evd->tv_log, msg); - free(msg); - break; - } - /* No commands needed?! (This is untypical) */ - } - } - - /* New command was started. Continue waiting for input */ - - /* Transplant cmd's output fd onto old one, so that main loop - * is none the wiser that fd it waits on has changed - */ - xmove_fd(evd->run_state->command_out_fd, evd->fd); - evd->run_state->command_out_fd = evd->fd; /* just to keep it consistent */ - ndelay_on(evd->fd); - - return TRUE; /* "please don't remove this event (yet)" */ -} - -static void start_event_run(const char *event_name, - GList *more_events, - GtkWidget *page, - GtkTextView *tv_log, - GtkLabel *status_label, - const char *start_msg, - const char *end_msg -) { - /* Start event asynchronously on the dump dir - * (synchronous run would freeze GUI until completion) - */ - struct run_event_state *state = new_run_event_state(); - - if (prepare_commands(state, g_dump_dir_name, event_name) == 0) - { - no_cmds: - /* No commands needed?! (This is untypical) */ - free_run_event_state(state); -//TODO: better msg? - char *msg = xasprintf(_("No processing for event '%s' is defined"), event_name); - gtk_label_set_text(status_label, msg); - free(msg); - return; - } - - struct dump_dir *dd = dd_opendir(g_dump_dir_name, DD_OPEN_READONLY); - dd = steal_if_needed(dd); - int locked = (dd && dd->locked); - dd_close(dd); - if (!locked) - return; /* user refused to steal, or write error, etc... */ - - set_excluded_envvar(); - GList *env_list = export_event_config(event_name); - - if (spawn_next_command(state, g_dump_dir_name, event_name) < 0) - { - unexport_event_config(env_list); - goto no_cmds; - } - - /* At least one command is needed, and we started first one. - * Hook its output fd to the main loop. - */ - struct analyze_event_data *evd = xzalloc(sizeof(*evd)); - evd->run_state = state; - evd->event_name = event_name; - evd->more_events = more_events; - evd->env_list = env_list; - evd->page_widget = page; - evd->status_label = status_label; - evd->tv_log = tv_log; - evd->end_msg = end_msg; - evd->event_log = strbuf_new(); - evd->fd = state->command_out_fd; - ndelay_on(evd->fd); - evd->channel = g_io_channel_unix_new(evd->fd); - /*evd->event_source_id = */ g_io_add_watch(evd->channel, - G_IO_IN | G_IO_ERR | G_IO_HUP, /* need HUP to detect EOF w/o any data */ - consume_cmd_output, - evd - ); - - gtk_label_set_text(status_label, start_msg); - - VERB1 log("running event '%s' on '%s'", event_name, g_dump_dir_name); -//TODO: save_to_event_log(evd, "message that we run event foo")? - char *msg = xasprintf("--- Running %s ---\n", event_name); - append_to_textview(evd->tv_log, msg); - free(msg); - - /* Disable (gray out) navigation buttons */ - gtk_widget_set_sensitive(GTK_WIDGET(g_assistant), false); -} - - -/* Backtrace checkbox handling */ - -static void add_warning(const char *warning) -{ - char *label_str = xasprintf("• %s", warning); - GtkWidget *warning_lbl = gtk_label_new(label_str); - /* should be safe to free it, gtk calls strdup() to copy it */ - free(label_str); - - gtk_misc_set_alignment(GTK_MISC(warning_lbl), 0.0, 0.0); - gtk_label_set_justify(GTK_LABEL(warning_lbl), GTK_JUSTIFY_LEFT); - gtk_box_pack_start(g_box_warning_labels, warning_lbl, false, false, 0); - gtk_widget_show(warning_lbl); -} - -static void check_bt_rating_and_allow_send(void) -{ - bool send = true; - bool warn = false; - - /* erase all warnings */ - gtk_widget_hide(g_widget_warnings_area); - gtk_container_foreach(GTK_CONTAINER(g_box_warning_labels), &remove_child_widget, NULL); - - /* - * FIXME: this should be bind to a reporter not to a compoment - * but so far only oopses don't have rating, so for now we - * skip the "kernel" manually - */ - const char *analyzer = get_problem_item_content_or_NULL(g_cd, FILENAME_ANALYZER); -//FIXME: say "no" to special casing! - if (analyzer && strcmp(analyzer, "Kerneloops") != 0) - { - const char *rating = get_problem_item_content_or_NULL(g_cd, FILENAME_RATING); -//COMPAT, remove after 2.1 release - if (!rating) rating= get_problem_item_content_or_NULL(g_cd, "rating"); - if (rating) switch (*rating) - { - case '4': /* bt is ok - no warning here */ - break; - case '3': /* bt is usable, but not complete, so show a warning */ - add_warning(_("The backtrace is incomplete, please make sure you provide the steps to reproduce.")); - warn = true; - break; - default: - //FIXME: see CreporterAssistant: 394 for ideas - add_warning(_("Reporting disabled because the backtrace is unusable.")); - send = false; - warn = true; - break; - } - } - - if (!gtk_toggle_button_get_active(g_tb_approve_bt)) - { - add_warning(_("You should check the backtrace for sensitive data.")); - add_warning(_("You must agree with sending the backtrace.")); - send = false; - warn = true; - } - - gtk_assistant_set_page_complete(g_assistant, - pages[PAGENO_EDIT_BACKTRACE].page_widget, - send); - if (warn) - gtk_widget_show(g_widget_warnings_area); -} - -static void on_bt_approve_toggle(GtkToggleButton *togglebutton, gpointer user_data) -{ - check_bt_rating_and_allow_send(); -} - -static void on_comment_changed(GtkTextBuffer *buffer, gpointer user_data) -{ - bool good = gtk_text_buffer_get_char_count(buffer) >= 10; - - /* The page doesn't exist with report-only option */ - if (pages[PAGENO_EDIT_COMMENT].page_widget == NULL) - return; - - /* Allow next page only when the comment has at least 10 chars */ - gtk_assistant_set_page_complete(g_assistant, pages[PAGENO_EDIT_COMMENT].page_widget, good); - - /* And show the eventbox with label */ - if (good) - gtk_widget_hide(GTK_WIDGET(g_eb_comment)); - else - gtk_widget_show(GTK_WIDGET(g_eb_comment)); -} - - -/* Refresh button handling */ - -static void on_btn_refresh_clicked(GtkButton *button) -{ - /* Save backtrace text if changed */ - save_text_from_text_view(g_tv_backtrace, FILENAME_BACKTRACE); - - /* Refresh GUI so that we see new analyze buttons */ - update_gui_state_from_problem_data(); - - /* Change page to analyzer selector - let user play with them */ - gtk_assistant_set_current_page(g_assistant, PAGENO_ANALYZE_SELECTOR); -} - - -/* Page navigation handlers */ - -static void next_page(GtkAssistant *assistant, gpointer user_data) -{ - /* page_no is actually the previous page, because this - * function is called before assistant goes to the next page - */ - int page_no = gtk_assistant_get_current_page(assistant); - VERB2 log("page_no:%d", page_no); - - if (added_pages[page_no]->name == PAGE_ANALYZE_SELECTOR) - { - VERB2 log("g_analyze_event_selected:'%s'", g_analyze_event_selected); - if (g_analyze_event_selected - && g_analyze_event_selected[0] - ) { - start_event_run(g_analyze_event_selected, - NULL, - pages[PAGENO_ANALYZE_PROGRESS].page_widget, - g_tv_analyze_log, - g_lbl_analyze_log, - _("Analyzing..."), - _("Analyzing finished with exit code %d") - ); - } - } - - if (added_pages[page_no]->name == PAGE_REVIEW_DATA) - { - GList *reporters = NULL; - GList *li = g_list_reporters; - for (; li; li = li->next) - { - event_gui_data_t *event_gui_data = li->data; - if (gtk_toggle_button_get_active(event_gui_data->toggle_button) == TRUE) - { - reporters = g_list_append(reporters, event_gui_data->event_name); - } - } - if (reporters) - { - char *first_event_name = reporters->data; - reporters = g_list_remove(reporters, reporters->data); - start_event_run(first_event_name, - reporters, - pages[PAGENO_REPORT_PROGRESS].page_widget, - g_tv_report_log, - g_lbl_report_log, - _("Reporting..."), - _("Reporting finished with exit code %d") - ); - } - } -} - -static void on_show_event_list_cb(GtkWidget *button, gpointer user_data) -{ - show_events_list_dialog(GTK_WINDOW(g_assistant)); -} - -#if 0 -static void log_ready_state() -{ - char buf[NUM_PAGES+1]; - for (int i = 0; i < NUM_PAGES; i++) - { - char ch = '_'; - if (pages[i].page_widget) - ch = gtk_assistant_get_page_complete(g_assistant, pages[i].page_widget) ? '+' : '-'; - buf[i] = ch; - } - buf[NUM_PAGES] = 0; - log("Completeness:[%s]", buf); -} -#endif - -static bool is_in_comma_separated_list(const char *value, const char *list) -{ - if (!list) - return false; - unsigned len = strlen(value); - while (*list) - { - const char *comma = strchrnul(list, ','); - if ((comma - list == len) && strncmp(value, list, len) == 0) - return true; - if (!*comma) - break; - list = comma + 1; - } - return false; -} - -static void on_page_prepare(GtkAssistant *assistant, GtkWidget *page, gpointer user_data) -{ - //int page_no = gtk_assistant_get_current_page(g_assistant); - //log_ready_state(); - - /* This suppresses [Last] button: assistant thinks that - * we never have this page ready unless we are on it - * -> therefore there is at least one non-ready page - * -> therefore it won't show [Last] - */ - // Doesn't work: if Completeness:[++++++-+++], - // then [Last] btn will still be shown. - //gtk_assistant_set_page_complete(g_assistant, - // pages[PAGENO_REVIEW_DATA].page_widget, - // pages[PAGENO_REVIEW_DATA].page_widget == page - //); - - if (pages[PAGENO_EDIT_BACKTRACE].page_widget == page) - { - check_bt_rating_and_allow_send(); - } - - /* Save text fields if changed */ - save_text_from_text_view(g_tv_backtrace, FILENAME_BACKTRACE); - save_text_from_text_view(g_tv_comment, FILENAME_COMMENT); - - if (pages[PAGENO_SUMMARY].page_widget == page - || pages[PAGENO_REVIEW_DATA].page_widget == page - ) { - GtkWidget *w = GTK_WIDGET(g_tv_details); - GtkContainer *c = GTK_CONTAINER(gtk_widget_get_parent(w)); - if (c) - gtk_container_remove(c, w); - gtk_container_add(pages[PAGENO_SUMMARY].page_widget == page ? - g_container_details1 : g_container_details2, - w - ); - /* Make checkbox column visible only on the last page */ - gtk_tree_view_column_set_visible(g_tv_details_col_checkbox, - (pages[PAGENO_REVIEW_DATA].page_widget == page) - ); - //gtk_cell_renderer_set_visible(g_tv_details_renderer_checkbox, - // (pages[PAGENO_REVIEW_DATA].page_widget == page) - //); - - if (pages[PAGENO_REVIEW_DATA].page_widget == page) - { - /* Based on selected reporter, update item checkboxes */ - event_config_t *cfg = get_event_config(g_reporter_events_selected ? g_reporter_events_selected : ""); - //log("%s: event:'%s', cfg:'%p'", __func__, g_reporter_events_selected, cfg); - if (cfg) - { - /* Default settings are... */ - int allowed_by_reporter = 1; - if (cfg->ec_exclude_items_always && strcmp(cfg->ec_exclude_items_always, "*") == 0) - allowed_by_reporter = 0; - int default_by_reporter = allowed_by_reporter; - if (cfg->ec_exclude_items_by_default && strcmp(cfg->ec_exclude_items_by_default, "*") == 0) - default_by_reporter = 0; - - GHashTableIter iter; - char *name; - struct problem_item *item; - g_hash_table_iter_init(&iter, g_cd); - while (g_hash_table_iter_next(&iter, (void**)&name, (void**)&item)) - { - /* Decide whether item is allowed, required, and what's the default */ - item->allowed_by_reporter = allowed_by_reporter; - if (is_in_comma_separated_list(name, cfg->ec_exclude_items_always)) - item->allowed_by_reporter = 0; - if ((item->flags & CD_FLAG_BIN) && cfg->ec_exclude_binary_items) - item->allowed_by_reporter = 0; - - item->default_by_reporter = item->allowed_by_reporter ? default_by_reporter : 0; - if (is_in_comma_separated_list(name, cfg->ec_exclude_items_by_default)) - item->default_by_reporter = 0; - if (is_in_comma_separated_list(name, cfg->ec_include_items_by_default)) - item->allowed_by_reporter = item->default_by_reporter = 1; - - item->required_by_reporter = 0; - if (is_in_comma_separated_list(name, cfg->ec_requires_items)) - item->default_by_reporter = item->allowed_by_reporter = item->required_by_reporter = 1; - - int cur_value; - if (item->selected_by_user == 0) - cur_value = item->default_by_reporter; - else - cur_value = !!(item->selected_by_user + 1); /* map -1,1 to 0,1 */ - - //log("%s: '%s' allowed:%d reqd:%d def:%d user:%d", __func__, name, - // item->allowed_by_reporter, - // item->required_by_reporter, - // item->default_by_reporter, - // item->selected_by_user - //); - - /* Find corresponding line and update checkbox */ - GtkTreeIter iter; - if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(g_ls_details), &iter)) - { - do { - gchar *item_name = NULL; - gtk_tree_model_get(GTK_TREE_MODEL(g_ls_details), &iter, - DETAIL_COLUMN_NAME, &item_name, - -1); - if (!item_name) /* paranoia, should never happen */ - continue; - int differ = strcmp(name, item_name); - g_free(item_name); - if (differ) - continue; - gtk_list_store_set(g_ls_details, &iter, - DETAIL_COLUMN_CHECKBOX, cur_value, - -1); - //log("%s: changed gtk_list_store_set to %d", __func__, (item->allowed_by_reporter && item->selected_by_user >= 0)); - break; - } while (gtk_tree_model_iter_next(GTK_TREE_MODEL(g_ls_details), &iter)); - } - } - } - } - } - - if (pages[PAGENO_EDIT_COMMENT].page_widget == page) - on_comment_changed(gtk_text_view_get_buffer(g_tv_comment), NULL); - //log_ready_state(); -} - -static gint select_next_page_no(gint current_page_no, gpointer data) -{ - /* we don't need any magic here if we're in only-report mode */ - if (g_report_only) - return current_page_no + 1; - - gint prev_page_no = current_page_no; - - again: - current_page_no++; - - switch (current_page_no) - { -#if 0 - case PAGENO_EDIT_COMMENT: - if (get_problem_item_content_or_NULL(g_cd, FILENAME_COMMENT)) - goto again; /* no comment, skip this page */ - break; -#endif - - case PAGENO_EDIT_BACKTRACE: - if (!get_problem_item_content_or_NULL(g_cd, FILENAME_BACKTRACE)) - goto again; /* no backtrace, skip this page */ - break; - - case PAGENO_ANALYZE_SELECTOR: - if (!g_analyze_events[0] || g_black_event_count == 0) - { - /* skip analyze selector page and analyze log page */ - current_page_no = PAGENO_REPORTER_SELECTOR-1; - goto again; - } - break; - - case PAGENO_ANALYZE_PROGRESS: - VERB2 log("%s: ANALYZE_PROGRESS: g_analyze_event_selected:'%s'", - __func__, g_analyze_event_selected); - if (!g_analyze_event_selected || !g_analyze_event_selected[0]) - goto again; /* skip this page */ - break; - - case PAGENO_REPORTER_SELECTOR: - VERB2 log("%s: REPORTER_SELECTOR: g_black_event_count:%d", - __func__, g_black_event_count); - /* if we _did_ run an event (didn't skip it) - * and still have analyzers which didn't run - */ - if (prev_page_no == PAGENO_ANALYZE_PROGRESS - && g_black_event_count != 0 - ) { - /* Go back to analyzer selectors */ - current_page_no = PAGENO_ANALYZE_SELECTOR-1; - goto again; - } - break; - case PAGENO_NOT_SHOWN: - /* No! this would SEGV (infinitely recurse into select_next_page_no) */ - /*gtk_assistant_commit(g_assistant);*/ - current_page_no = PAGENO_ANALYZE_SELECTOR-1; - goto again; - } - - VERB2 log("%s: selected page #%d", __func__, current_page_no); - return current_page_no; -} - - -static gboolean highlight_search(gpointer user_data) -{ - GtkEntry *entry = GTK_ENTRY(user_data); - GtkTextBuffer *buffer = gtk_text_view_get_buffer(g_tv_backtrace); - GtkTextIter start_find; - GtkTextIter end_find; - GtkTextIter start_match; - GtkTextIter end_match; - int offset = 0; - - gtk_text_buffer_get_start_iter(buffer, &start_find); - gtk_text_buffer_get_end_iter(buffer, &end_find); - gtk_text_buffer_remove_tag_by_name(buffer, "search_result_bg", &start_find, &end_find); - VERB1 log("searching: %s", gtk_entry_get_text(entry)); - while (gtk_text_iter_forward_search(&start_find, gtk_entry_get_text(entry), - GTK_TEXT_SEARCH_TEXT_ONLY, &start_match, - &end_match, NULL)) - { - gtk_text_buffer_apply_tag_by_name(buffer, "search_result_bg", - &start_match, &end_match); - offset = gtk_text_iter_get_offset(&end_match); - gtk_text_buffer_get_iter_at_offset(buffer, &start_find, offset); - } - - /* returning false will make glib to remove this event */ - return false; -} - -static void search_timeout(GtkEntry *entry) -{ - /* this little hack makes the search start searching after 500 milisec after - * user stops writing into entry box - * if this part is removed, then the search will be started on every - * change of the search entry - */ - if (g_timeout != 0) - g_source_remove(g_timeout); - g_timeout = g_timeout_add(500, &highlight_search, (gpointer)entry); -} - - -/* Initialization */ - -/* wizard.glade file as a string WIZARD_GLADE_CONTENTS: */ -#include "wizard_glade.c" - -static void add_pages() -{ - GError *error = NULL; - if (!g_glade_file) - { - /* Load UI from internal string */ - gtk_builder_add_objects_from_string(builder, - WIZARD_GLADE_CONTENTS, sizeof(WIZARD_GLADE_CONTENTS) - 1, - (gchar**)page_names, - &error); - if (error != NULL) - error_msg_and_die("Error loading glade data: %s", error->message); - } - else - { - /* -g FILE: load IU from it */ - gtk_builder_add_objects_from_file(builder, g_glade_file, (gchar**)page_names, &error); - if (error != NULL) - error_msg_and_die("Can't load %s: %s", g_glade_file, error->message); - } - - int i; - int page_no = 0; - for (i = 0; page_names[i] != NULL; i++) - { - char *delim = strrchr(page_names[i], '_'); - if (delim != NULL) - { - if (g_report_only && (strncmp(delim + 1, "report", strlen("report"))) != 0) - { - pages[i].page_widget = NULL; - continue; - } - } - GtkWidget *page = GTK_WIDGET(gtk_builder_get_object(builder, page_names[i])); - - pages[i].page_widget = page; - added_pages[page_no++] = &pages[i]; - - gtk_assistant_append_page(g_assistant, page); - /* If we set all pages to complete the wizard thinks there is nothing - * to do and shows the button "Last" which allows user to skip all pages - * so we need to set them all as incomplete and complete them one by one - * on proper place - on_page_prepare() ? - */ - gtk_assistant_set_page_complete(g_assistant, page, true); - - gtk_assistant_set_page_title(g_assistant, page, pages[i].title); - gtk_assistant_set_page_type(g_assistant, page, pages[i].type); - - VERB1 log("added page: %s", page_names[i]); - } - - /* Set pointers to objects we might need to work with */ - g_lbl_cd_reason = GTK_LABEL( gtk_builder_get_object(builder, "lbl_cd_reason")); - g_box_analyzers = GTK_BOX( gtk_builder_get_object(builder, "vb_analyzers")); - g_lbl_analyze_log = GTK_LABEL( gtk_builder_get_object(builder, "lbl_analyze_log")); - g_tv_analyze_log = GTK_TEXT_VIEW( gtk_builder_get_object(builder, "tv_analyze_log")); - g_box_reporters = GTK_BOX( gtk_builder_get_object(builder, "vb_reporters")); - g_lbl_report_log = GTK_LABEL( gtk_builder_get_object(builder, "lbl_report_log")); - g_tv_report_log = GTK_TEXT_VIEW( gtk_builder_get_object(builder, "tv_report_log")); - g_tv_backtrace = GTK_TEXT_VIEW( gtk_builder_get_object(builder, "tv_backtrace")); - g_tv_comment = GTK_TEXT_VIEW( gtk_builder_get_object(builder, "tv_comment")); - g_eb_comment = GTK_EVENT_BOX( gtk_builder_get_object(builder, "eb_comment")); - g_tv_details = GTK_TREE_VIEW( gtk_builder_get_object(builder, "tv_details")); - g_box_warning_labels = GTK_BOX( gtk_builder_get_object(builder, "box_warning_labels")); - g_tb_approve_bt = GTK_TOGGLE_BUTTON(gtk_builder_get_object(builder, "cb_approve_bt")); - g_widget_warnings_area = GTK_WIDGET( gtk_builder_get_object(builder, "box_warning_area")); - g_btn_refresh = GTK_BUTTON( gtk_builder_get_object(builder, "btn_refresh")); - g_search_entry_bt = GTK_ENTRY( gtk_builder_get_object(builder, "entry_search_bt")); - g_container_details1 = GTK_CONTAINER( gtk_builder_get_object(builder, "container_details1")); - g_container_details2 = GTK_CONTAINER( gtk_builder_get_object(builder, "container_details2")); - g_lbl_reporters = GTK_LABEL( gtk_builder_get_object(builder, "lbl_reporters")); - g_lbl_size = GTK_LABEL( gtk_builder_get_object(builder, "lbl_size")); - - gtk_widget_modify_font(GTK_WIDGET(g_tv_analyze_log), monospace_font); - gtk_widget_modify_font(GTK_WIDGET(g_tv_report_log), monospace_font); - gtk_widget_modify_font(GTK_WIDGET(g_tv_backtrace), monospace_font); - fix_all_wrapped_labels(GTK_WIDGET(g_assistant)); - - if (pages[PAGENO_EDIT_BACKTRACE].page_widget != NULL) - gtk_assistant_set_page_complete(g_assistant, pages[PAGENO_EDIT_BACKTRACE].page_widget, - gtk_toggle_button_get_active(g_tb_approve_bt)); - - /* Configure btn on select analyzers page */ - GtkWidget *config_btn = GTK_WIDGET(gtk_builder_get_object(builder, "button_cfg1")); - if (config_btn) - g_signal_connect(G_OBJECT(config_btn), "clicked", G_CALLBACK(on_show_event_list_cb), NULL); - - /* Configure btn on select reporters page */ - config_btn = GTK_WIDGET(gtk_builder_get_object(builder, "button_cfg2")); - if (config_btn) - g_signal_connect(G_OBJECT(config_btn), "clicked", G_CALLBACK(on_show_event_list_cb), NULL); - - /* Add "Close" button */ - GtkWidget *w; - w = gtk_button_new_from_stock(GTK_STOCK_CLOSE); - g_signal_connect(w, "clicked", G_CALLBACK(gtk_main_quit), NULL); - gtk_widget_show(w); - gtk_assistant_add_action_widget(g_assistant, w); - /* and hide "Cancel" button - "Close" is a better name for what we want */ - gtk_assistant_commit(g_assistant); - - /* Set color of the comment evenbox */ - GdkColor color; - gdk_color_parse("#CC3333", &color); - gtk_widget_modify_bg(GTK_WIDGET(g_eb_comment), GTK_STATE_NORMAL, &color); -} - -static void create_details_treeview() -{ - GtkCellRenderer *renderer; - GtkTreeViewColumn *column; - - //g_tv_details_renderer_checkbox = - renderer = gtk_cell_renderer_toggle_new(); - g_tv_details_col_checkbox = column = gtk_tree_view_column_new_with_attributes( - _("Include"), renderer, - /* which "attr" of renderer to set from which COLUMN? (can be repeated) */ - "active", DETAIL_COLUMN_CHECKBOX, - NULL); - gtk_tree_view_append_column(g_tv_details, column); - /* This column has a handler */ - g_signal_connect(renderer, "toggled", G_CALLBACK(g_tv_details_checkbox_toggled), NULL); - - renderer = gtk_cell_renderer_text_new(); - column = gtk_tree_view_column_new_with_attributes( - _("Name"), renderer, - "text", DETAIL_COLUMN_NAME, - NULL); - gtk_tree_view_append_column(g_tv_details, column); - - g_tv_details_renderer_value = renderer = gtk_cell_renderer_text_new(); - column = gtk_tree_view_column_new_with_attributes( - _("Value"), renderer, - "text", DETAIL_COLUMN_VALUE, - NULL); - gtk_tree_view_append_column(g_tv_details, column); - - /* - renderer = gtk_cell_renderer_text_new(); - column = gtk_tree_view_column_new_with_attributes( - _("Path"), renderer, - "text", DETAIL_COLUMN_PATH, - NULL); - gtk_tree_view_append_column(g_tv_details, column); - */ - - gtk_tree_view_column_set_sort_column_id(column, DETAIL_COLUMN_NAME); - - g_ls_details = gtk_list_store_new(DETAIL_NUM_COLUMNS, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_STRING); - gtk_tree_view_set_model(g_tv_details, GTK_TREE_MODEL(g_ls_details)); - - g_signal_connect(g_tv_details, "row-activated", G_CALLBACK(tv_details_row_activated), NULL); - g_signal_connect(g_tv_details, "cursor-changed", G_CALLBACK(tv_details_cursor_changed), NULL); - /* [Enter] on a row: - * g_signal_connect(g_tv_details, "select-cursor-row", G_CALLBACK(tv_details_select_cursor_row), NULL); - */ -} - -void create_assistant(void) -{ - monospace_font = pango_font_description_from_string("monospace"); - - builder = gtk_builder_new(); - - g_assistant = GTK_ASSISTANT(gtk_assistant_new()); - - gtk_assistant_set_forward_page_func(g_assistant, select_next_page_no, NULL, NULL); - - GtkWindow *wnd_assistant = GTK_WINDOW(g_assistant); - g_parent_window = wnd_assistant; - gtk_window_set_default_size(wnd_assistant, DEFAULT_WIDTH, DEFAULT_HEIGHT); - /* set_default sets icon for every windows used in this app, so we don't - * have to set the icon for those windows manually - */ - gtk_window_set_default_icon_name("abrt"); - - GObject *obj_assistant = G_OBJECT(g_assistant); - g_signal_connect(obj_assistant, "cancel", G_CALLBACK(gtk_main_quit), NULL); - g_signal_connect(obj_assistant, "close", G_CALLBACK(gtk_main_quit), NULL); - g_signal_connect(obj_assistant, "apply", G_CALLBACK(next_page), NULL); - g_signal_connect(obj_assistant, "prepare", G_CALLBACK(on_page_prepare), NULL); - - add_pages(); - - create_details_treeview(); - - g_signal_connect(g_tb_approve_bt, "toggled", G_CALLBACK(on_bt_approve_toggle), NULL); - g_signal_connect(g_btn_refresh, "clicked", G_CALLBACK(on_btn_refresh_clicked), NULL); - g_signal_connect(gtk_text_view_get_buffer(g_tv_comment), "changed", G_CALLBACK(on_comment_changed), NULL); - - /* init searching */ - GtkTextBuffer *backtrace_buf = gtk_text_view_get_buffer(g_tv_backtrace); - /* found items background */ - gtk_text_buffer_create_tag(backtrace_buf, "search_result_bg", "background", "red", NULL); - g_signal_connect(g_search_entry_bt, "changed", G_CALLBACK(search_timeout), NULL); -} |