/*
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 <gdk/gdk.h>
#include "internal_libreport_gtk.h"
enum
{
COLUMN_UINAME,
COLUMN_NAME,
CONFIG_DIALOG,
NUM_COLUMNS
};
enum
{
TYPE_STR,
TYPE_POINTER
};
struct config_dialog
{
GtkWidget *dialog;
gpointer *data;
config_save_fun_t save_data;
};
GtkListStore *new_conf_liststore(void)
{
/* Create data store for the list and attach it
* COLUMN_UINAME -> name+description
* COLUMN_NAME -> config name so we can retrieve it from the row
*/
return gtk_list_store_new(NUM_COLUMNS,
G_TYPE_STRING, /* Event name + description */
G_TYPE_STRING, /* event name */
G_TYPE_POINTER, /* dialog */
G_TYPE_POINTER /* option_list */
);
}
config_dialog_t *new_config_dialog(GtkWidget *dialog,
gpointer config_data,
config_save_fun_t save_fun)
{
config_dialog_t *cdialog = (config_dialog_t *)xmalloc(sizeof(*cdialog));
cdialog->dialog = dialog;
cdialog->data = config_data;
cdialog->save_data = save_fun;
return cdialog;
}
void cdialog_set_widget(config_dialog_t *cdialog, GtkWidget *widget)
{
//TODO destroy(cdialog-dialog) ??
cdialog->dialog = widget;
}
GtkWidget *cdialog_get_widget(config_dialog_t *cdialog)
{
return cdialog->dialog;
}
gpointer cdialog_get_data(config_dialog_t *cdialog)
{
return cdialog->data;
}
int cdialog_run(config_dialog_t *cdialog, const char *name)
{
if (cdialog == NULL || cdialog->dialog == NULL)
{
log_warning("There is no configurable option for: '%s'", name);
return GTK_RESPONSE_REJECT;
}
const int result = gtk_dialog_run(GTK_DIALOG(cdialog->dialog));
if (result == GTK_RESPONSE_APPLY)
{
if (cdialog->save_data)
cdialog->save_data(cdialog->data, name);
}
else if (result == GTK_RESPONSE_CANCEL)
log_notice("Cancelling on user request");
gtk_widget_hide(GTK_WIDGET(cdialog->dialog));
return result;
}
static const void *get_column_value_from_row(GtkTreeView *treeview, int column, int type)
{
GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview);
const void *retval = NULL;
if (selection)
{
GtkTreeIter iter;
GtkTreeModel *store = gtk_tree_view_get_model(treeview);
if (gtk_tree_selection_get_selected(selection, &store, &iter) == TRUE)
{
GValue value = { 0 };
gtk_tree_model_get_value(store, &iter, column, &value);
switch(type){
case TYPE_STR:
retval = g_value_get_string(&value);
break;
case TYPE_POINTER:
retval = g_value_get_pointer(&value);
}
}
}
return retval;
}
static void save_value_from_widget(gpointer data, gpointer user_data)
{
option_widget_t *ow = (option_widget_t *)data;
const char *val = NULL;
switch (ow->option->eo_type)
{
case OPTION_TYPE_TEXT:
case OPTION_TYPE_NUMBER:
case OPTION_TYPE_PASSWORD:
val = (char *)gtk_entry_get_text(GTK_ENTRY(ow->widget));
break;
case OPTION_TYPE_BOOL:
val = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ow->widget)) ? "yes" : "no";
break;
default:
log_warning("unsupported option type");
}
/* gtk_entry_get_text() returns empty string for empty text value */
/* so if value is empty and the old value is NULL then nothing has */
/* changed and we must not set option's value */
if (val && (val[0] != '\0' || ow->option->eo_value != NULL))
{
free(ow->option->eo_value);
ow->option->eo_value = xstrdup(val);
log_notice("saved: %s:%s", ow->option->eo_name, ow->option->eo_value);
}
}
void dehydrate_config_dialog(GList *option_widgets)
{
if (option_widgets != NULL)
g_list_foreach(option_widgets, &save_value_from_widget, NULL);
}
void add_item_to_config_liststore(gpointer cdialog, gpointer inf, gpointer user_data)
{
INITIALIZE_LIBREPORT();
GtkListStore *list_store = (GtkListStore *)user_data;
config_item_info_t *info = (config_item_info_t *)inf;
log_notice("adding '%s' to workflow list\n", ci_get_screen_name(info));
char *label;
if (ci_get_screen_name(info) != NULL && ci_get_description(info) != NULL)
label = xasprintf("<b>%s</b>\n%s",ci_get_screen_name(info), ci_get_description(info));
else
//if event has no xml description
label = xasprintf("<b>%s</b>\n%s", _("No description available"), ci_get_name(info));
GtkTreeIter iter;
gtk_list_store_append(list_store, &iter);
gtk_list_store_set(list_store, &iter,
COLUMN_UINAME, label,
COLUMN_NAME, ci_get_name(info),
CONFIG_DIALOG, cdialog,
-1);
free(label);
}
//filters configuration - show only those with configurable options trac#881
static gboolean config_filter_func(GtkTreeModel *model,
GtkTreeIter *iter,
gpointer data)
{
gboolean visible = FALSE;
gpointer cdialog;
GValue value = { 0 };
gtk_tree_model_get_value(model, iter, CONFIG_DIALOG, &value);
cdialog = g_value_get_pointer(&value);
visible = (cdialog != NULL);
return visible;
}
static void open_config_for_selected_row(GtkTreeView *tv)
{
config_dialog_t *cdialog = (config_dialog_t *)get_column_value_from_row(tv, CONFIG_DIALOG, TYPE_POINTER);
const char *name = (const char *)get_column_value_from_row(tv, COLUMN_NAME, TYPE_STR);
cdialog_run(cdialog, name);
}
static gboolean on_key_press_event_cb(GtkWidget *btn, GdkEvent *event, gpointer user_data)
{
GdkEventKey *ek = (GdkEventKey *)event;
if (ek->keyval == GDK_KEY_Return)
{
GtkTreeView *tv = (GtkTreeView *)user_data;
open_config_for_selected_row(tv);
}
return FALSE;
}
static gboolean on_button_press_event_cb(GtkWidget *btn, GdkEvent *event, gpointer user_data)
{
GdkEventButton *eb = (GdkEventButton *)event;
if (eb->type == GDK_2BUTTON_PRESS)
{
GtkTreeView *tv = (GtkTreeView *)user_data;
open_config_for_selected_row(tv);
}
return FALSE;
}
GtkWidget *create_config_tab_content(const char *column_label,
GtkListStore *store)
{
GtkWidget *main_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL);
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
GTK_POLICY_NEVER,
GTK_POLICY_AUTOMATIC);
/* workflow list treeview */
GtkWidget *tv = gtk_tree_view_new();
g_signal_connect(tv, "key-press-event", G_CALLBACK(on_key_press_event_cb), tv);
g_signal_connect(tv, "button-press-event", G_CALLBACK(on_button_press_event_cb), tv);
/* column with workflow name and description */
GtkCellRenderer *renderer;
GtkTreeViewColumn *column;
/* add column to tree view */
renderer = gtk_cell_renderer_text_new();
column = gtk_tree_view_column_new_with_attributes(column_label,
renderer,
"markup",
COLUMN_UINAME,
NULL);
gtk_tree_view_column_set_resizable(column, TRUE);
g_object_set(G_OBJECT(renderer), "wrap-mode", PANGO_WRAP_WORD, NULL);
g_object_set(G_OBJECT(renderer), "wrap-width", 440, NULL);
gtk_tree_view_column_set_sort_column_id(column, COLUMN_NAME);
gtk_tree_view_append_column(GTK_TREE_VIEW(tv), column);
// TODO: gtk_tree_view_set_headers_visible(FALSE)? We have only one column anyway...
GtkTreeModel *model = gtk_tree_model_filter_new(GTK_TREE_MODEL(store), NULL);
gtk_tree_model_filter_set_visible_func(GTK_TREE_MODEL_FILTER(model), config_filter_func, NULL, NULL);
gtk_tree_view_set_model(GTK_TREE_VIEW(tv), GTK_TREE_MODEL(model));
{ /* Selected the first row, so we do not need to call gtk_tree_view_scroll_to_cell() */
GtkTreeIter iter;
gtk_tree_model_get_iter_first(model, &iter);
GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv));
gtk_tree_selection_select_iter(selection, &iter);
}
gtk_container_add(GTK_CONTAINER(scroll), tv);
gtk_box_pack_start(GTK_BOX(main_vbox), scroll, true, true, 10);
return main_vbox;
}
static void add_config_tabs(const char *name, GtkListStore *store, gpointer nb)
{
GtkNotebook *ntb = (GtkNotebook *)nb;
GtkWidget *config_list_vbox = create_config_tab_content(
name,
store);
gtk_notebook_append_page(ntb, config_list_vbox, gtk_label_new(name));
}
static void on_configure_cb(GtkWidget *btn, gpointer user_data)
{
GtkNotebook *nb = (GtkNotebook *)user_data;
guint current_page_n = gtk_notebook_get_current_page(nb);
GtkWidget *vbox = gtk_notebook_get_nth_page(nb, current_page_n);
GList *children = gtk_container_get_children(GTK_CONTAINER(vbox));
GtkScrolledWindow *sw = (GtkScrolledWindow *)children->data;
open_config_for_selected_row((GtkTreeView *)gtk_bin_get_child(GTK_BIN(sw)));
}
static void on_close_cb(GtkWidget *btn, gpointer config_list_w)
{
GtkWidget *w = (GtkWidget *)config_list_w;
gtk_widget_hide(w);
}
GtkWindow *create_config_list_window(GHashTable *configs, GtkWindow *parent)
{
INITIALIZE_LIBREPORT();
// config window
GtkWidget *window = NULL;
if (parent != NULL)
{
window = gtk_dialog_new();
gtk_window_set_icon_name(GTK_WINDOW(window), gtk_window_get_icon_name(parent));
gtk_window_set_modal(GTK_WINDOW(window), TRUE);
gtk_window_set_transient_for(GTK_WINDOW(window), parent);
}
else
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_container_set_border_width(GTK_CONTAINER(window), 5);
gtk_window_set_title(GTK_WINDOW(window), _("Configuration"));
gtk_window_set_default_size(GTK_WINDOW(window), 450, 400);
gtk_window_set_position(GTK_WINDOW(window), parent
? GTK_WIN_POS_CENTER_ON_PARENT
: GTK_WIN_POS_CENTER);
//g_signal_connect(window, "destroy", G_CALLBACK (gtk_main_quit), NULL);
GtkWidget *main_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
GtkWidget *config_nb = gtk_notebook_new();
gtk_box_pack_start(GTK_BOX(main_vbox), config_nb, 1, 1, 0);
/* we can't use this, because we want the workflows first and hashtable
* doesn't return the items in the order they were added
*/
//g_hash_table_foreach(configs, (GHFunc)add_config_tabs, config_nb);
gpointer config = g_hash_table_lookup(configs, _("Workflows"));
if (config != NULL)
add_config_tabs(_("Workflows"), config, config_nb);
config = g_hash_table_lookup(configs, _("Events"));
if (config != NULL)
add_config_tabs(_("Events"), config, config_nb);
//buttons
GtkWidget *btn_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL,5);
GtkWidget *configure_btn = gtk_button_new_with_mnemonic(_("C_onfigure"));
GtkWidget *close_btn = gtk_button_new_with_mnemonic(_("_Close"));
GtkSizeGroup *sg = gtk_size_group_new(GTK_SIZE_GROUP_BOTH);
//force apply and close to have the same size
gtk_size_group_add_widget(sg, close_btn);
gtk_size_group_add_widget(sg, configure_btn);
g_signal_connect(configure_btn, "clicked", (GCallback)on_configure_cb, config_nb);
g_signal_connect(close_btn, "clicked", (GCallback)on_close_cb, window);
gtk_box_pack_start(GTK_BOX(btn_box), close_btn, 0, 0, 5);
gtk_box_pack_end(GTK_BOX(btn_box), configure_btn, 0, 0, 5);
gtk_box_pack_start(GTK_BOX(main_vbox), btn_box, 0, 0, 0);
if (parent != NULL)
{
GtkWidget *content = gtk_dialog_get_content_area(GTK_DIALOG(window));
gtk_box_pack_start(GTK_BOX(content), main_vbox, /*expand*/TRUE, /*fill*/TRUE, /*padding*/0);
gtk_widget_show_all(content);
}
else
gtk_container_add(GTK_CONTAINER(window), main_vbox);
//gtk_widget_show_all(window);
return GTK_WINDOW(window);
}
/* Name | vbox with the gtk_Tree
* <String name, GtkWidget *vbox>
*/
void show_config_list_dialog(GtkWindow *parent)
{
INITIALIZE_LIBREPORT();
GHashTable *confs = g_hash_table_new_full(
/*hash_func*/ g_str_hash,
/*key_equal_func:*/ g_str_equal,
/*key_destroy_func:*/ g_free,
/*value_destroy_func:*/ NULL);
//TODO: free the hashtables somewhere!!
GHashTable *events = load_event_config_data();
load_event_config_data_from_user_storage(events);
GHashTable *workflows = load_workflow_config_data(WORKFLOWS_DIR);
load_workflow_config_data_from_user_storage(workflows);
GtkListStore *workflows_store = add_workflows_to_liststore(workflows);
g_hash_table_insert(confs, _("Workflows"), workflows_store);
GtkListStore *events_store = add_events_to_liststore(events);
g_hash_table_insert(confs, _("Events"), events_store);
GtkWindow *window = create_config_list_window(confs, parent);
gtk_widget_show_all(GTK_WIDGET(window));
}