You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
gnucash/lib/goffice/graph/gog-guru.c

1414 lines
39 KiB

/* vim: set sw=8: */
/*
* go-graph-guru.c: The Graph guru
*
* Copyright (C) 2000-2004 Jody Goldberg (jody@gnome.org)
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*/
#include <goffice/goffice-config.h>
#include <goffice/graph/gog-guru.h>
#include <goffice/graph/gog-object.h>
#include <goffice/graph/gog-graph.h>
#include <goffice/graph/gog-object.h>
#include <goffice/graph/gog-chart.h>
#include <goffice/graph/gog-plot.h>
#include <goffice/graph/gog-view.h>
#include <goffice/graph/gog-plot-engine.h>
#include <goffice/graph/gog-data-allocator.h>
#include <goffice/graph/gog-control-foocanvas.h>
#include <glib/gi18n.h>
#include <gui-util.h>
#include <libxml/parser.h>
#include <libfoocanvas/foo-canvas.h>
#include <libfoocanvas/foo-canvas-pixbuf.h>
#include <libfoocanvas/foo-canvas-rect-ellipse.h>
#include <gdk/gdkkeysyms.h>
#include <gtk/gtknotebook.h>
#include <gtk/gtklabel.h>
#include <gtk/gtktreeselection.h>
#include <gtk/gtktreeview.h>
#include <gtk/gtktreestore.h>
#include <gtk/gtkimage.h>
#include <gtk/gtkimagemenuitem.h>
#include <gtk/gtkframe.h>
#include <gtk/gtkviewport.h>
#include <gtk/gtkvbox.h>
#include <gtk/gtkhbox.h>
#include <gtk/gtkcellrenderertext.h>
#include <gtk/gtkcellrendererpixbuf.h>
#include <gtk/gtkstock.h>
#include <gtk/gtkliststore.h>
#include <string.h>
typedef struct _GraphGuruState GraphGuruState;
typedef struct _GraphGuruTypeSelector GraphGuruTypeSelector;
struct _GraphGuruState {
GogGraph *graph;
GogChart *chart;
GogPlot *plot;
GnmCmdContext *cc;
GogDataAllocator *dalloc;
GClosure *register_closure;
/* GUI accessors */
GladeXML *gui;
GtkWidget *dialog;
GtkWidget *button_cancel;
GtkWidget *button_navigate;
GtkWidget *button_ok;
GtkNotebook *steps;
GtkWidget *add_menu, *delete_button;
FooCanvasItem *sample_graph_item;
GtkContainer *prop_container;
GtkTreeSelection *prop_selection;
GtkTreeView *prop_view;
GtkTreeStore *prop_model;
GtkTreeIter prop_iter;
GogObject *prop_object;
GraphGuruTypeSelector *type_selector;
struct {
GtkWidget *inc, *dec, *first, *last, *menu;
} prec;
/* internal state */
int current_page, initial_page;
gboolean valid;
gboolean updating;
gboolean fmt_page_initialized;
gboolean editing;
/* hackish reuse of State as a closure */
GogObject *search_target, *new_child;
};
struct _GraphGuruTypeSelector {
GladeXML *gui;
GtkWidget *canvas;
GtkWidget *sample_button;
GtkLabel *label;
GtkTreeView *list_view;
GtkListStore *model;
FooCanvasItem *selector;
FooCanvasItem *sample_graph_item;
GraphGuruState *state;
FooCanvasGroup *graph_group;
xmlNode const *plots;
GogPlotFamily *current_family;
GogPlotType *current_type;
FooCanvasGroup const *current_family_item;
FooCanvasItem const *current_minor_item;
};
enum {
PLOT_FAMILY_TYPE_IMAGE,
PLOT_FAMILY_TYPE_NAME,
PLOT_FAMILY_TYPE_CANVAS_GROUP,
PLOT_FAMILY_NUM_COLUMNS
};
enum {
PLOT_ATTR_NAME,
PLOT_ATTR_OBJECT,
PLOT_ATTR_NUM_COLUMNS
};
#define MINOR_PIXMAP_WIDTH 64
#define MINOR_PIXMAP_HEIGHT 60
#define BORDER 5
#define PLOT_TYPE_KEY "plot_type"
#define FIRST_MINOR_TYPE "first_minor_type"
#define ROLE_KEY "role"
#define STATE_KEY "plot_type"
static GdkPixbuf *
get_pixbuf (char const *image_file)
{
static GHashTable *cache = NULL;
GdkPixbuf *pixbuf;
if (cache != NULL) {
pixbuf = g_hash_table_lookup (cache, image_file);
if (pixbuf != NULL)
return pixbuf;
} else
cache = g_hash_table_new_full (g_str_hash, g_str_equal,
NULL, g_object_unref);
pixbuf = gnumeric_load_pixbuf (image_file);
g_hash_table_insert (cache, (gpointer)image_file, pixbuf);
return pixbuf;
}
static void
get_pos (int col, int row, double *x, double *y)
{
*x = (col-1) * (MINOR_PIXMAP_WIDTH + BORDER) + BORDER;
*y = (row-1) * (MINOR_PIXMAP_HEIGHT + BORDER) + BORDER;
}
/*
* graph_typeselect_minor :
*
* @typesel :
* @item : A CanvasItem in the selector.
*
* Moves the typesel::selector overlay above the @item.
* Assumes that the item is visible
*/
static void
graph_typeselect_minor (GraphGuruTypeSelector *typesel, FooCanvasItem *item)
{
GraphGuruState *s = typesel->state;
GogPlotType *type;
double x1, y1, x2, y2;
gboolean enable_next_button;
GogPlot *plot;
if (typesel->current_minor_item == item)
return;
type = g_object_get_data (G_OBJECT (item), PLOT_TYPE_KEY);
g_return_if_fail (type != NULL);
typesel->current_type = type;
typesel->current_minor_item = item;
foo_canvas_item_get_bounds (item, &x1, &y1, &x2, &y2);
foo_canvas_item_set (FOO_CANVAS_ITEM (typesel->selector),
"x1", x1-1., "y1", y1-1.,
"x2", x2+1., "y2", y2+1.,
NULL);
gtk_label_set_text (typesel->label, _(type->description));
gtk_widget_set_sensitive (typesel->sample_button, TRUE);
enable_next_button = (s->plot == NULL);
plot = gog_plot_new_by_type (type);
g_return_if_fail (plot != NULL);
if (s->plot != NULL) {
GogObject *obj = GOG_OBJECT (s->plot);
gog_object_clear_parent (obj);
g_object_unref (obj);
}
gog_object_add_by_name (GOG_OBJECT (s->chart),
"Plot", GOG_OBJECT (s->plot = plot));
#if 0
if (s->original_plot != NULL &&
!gog_plot_make_similar (s->plot, s->original_plot))
return;
#endif
if (s->dalloc != NULL)
gog_data_allocator_allocate (s->dalloc, s->plot);
if (s->current_page == 0 && enable_next_button)
gtk_widget_set_sensitive (s->button_navigate, TRUE);
}
static gboolean
graph_typeselect_minor_x_y (GraphGuruTypeSelector *typesel,
double x, double y)
{
FooCanvasItem *item = foo_canvas_get_item_at (
FOO_CANVAS (typesel->canvas), x, y);
if (item != NULL) {
if(item != typesel->selector)
graph_typeselect_minor (typesel, item);
foo_canvas_item_grab_focus (item);
return TRUE;
}
return FALSE;
}
static gboolean
cb_key_press_event (G_GNUC_UNUSED GtkWidget *wrapper,
GdkEventKey *event,
GraphGuruTypeSelector *typesel)
{
GtkCornerType corner;
int col, row;
double x, y;
GogPlotType const *type = g_object_get_data (
G_OBJECT (typesel->current_minor_item), PLOT_TYPE_KEY);
g_return_val_if_fail (type != NULL, FALSE);
col = type->col;
row = type->row;
switch (event->keyval){
case GDK_KP_Left: case GDK_Left:
corner = GTK_CORNER_BOTTOM_RIGHT;
--col;
break;
case GDK_KP_Up: case GDK_Up:
corner = GTK_CORNER_BOTTOM_RIGHT;
--row;
break;
case GDK_KP_Right: case GDK_Right:
corner = GTK_CORNER_TOP_LEFT;
++col;
break;
case GDK_KP_Down: case GDK_Down:
corner = GTK_CORNER_TOP_LEFT;
++row;
break;
default:
return FALSE;
}
get_pos (col, row, &x, &y);
graph_typeselect_minor_x_y (typesel, x, y);
return TRUE;
}
static gint
cb_button_press_event (GtkWidget *widget, GdkEventButton *event,
GraphGuruTypeSelector *typesel)
{
if (event->button == 1) {
FooCanvas *c = FOO_CANVAS (widget);
double x, y;
foo_canvas_window_to_world (c, event->x, event->y, &x, &y);
graph_typeselect_minor_x_y (typesel, x, y);
}
return FALSE;
}
static void
cb_selection_changed (GraphGuruTypeSelector *typesel)
{
GtkTreeSelection *selection = gtk_tree_view_get_selection (typesel->list_view);
GtkTreeIter iter;
FooCanvasItem *item;
FooCanvasGroup *group;
if (typesel->current_family_item != NULL)
foo_canvas_item_hide (FOO_CANVAS_ITEM (typesel->current_family_item));
if (!gtk_tree_selection_get_selected (selection, NULL, &iter))
return;
gtk_tree_model_get (GTK_TREE_MODEL (typesel->model), &iter,
PLOT_FAMILY_TYPE_CANVAS_GROUP, &group,
-1);
foo_canvas_item_show (FOO_CANVAS_ITEM (group));
typesel->current_family_item = group;
foo_canvas_item_hide (FOO_CANVAS_ITEM (typesel->selector));
item = g_object_get_data (G_OBJECT (group), FIRST_MINOR_TYPE);
if (item != NULL)
graph_typeselect_minor (typesel, item);
foo_canvas_item_show (FOO_CANVAS_ITEM (typesel->selector));
}
static void
cb_typesel_sample_plot_resize (FooCanvas *canvas,
GtkAllocation *alloc, GraphGuruTypeSelector *typesel)
{
/* Use 96dpi and 50% zoom. Hard code the zoom because it is not
* active when sample button is not depressed */
if (typesel->sample_graph_item != NULL)
foo_canvas_item_set (typesel->sample_graph_item,
"w", (double)alloc->width * 2.,
"h", (double)alloc->height * 2.,
"logical_width_pts", (2. * 72. * (double)alloc->width) / 96.,
"logical_height_pts", (2. * 72. * (double)alloc->height) / 96.,
NULL);
}
static void
cb_sample_pressed (GraphGuruTypeSelector *typesel)
{
if (typesel->current_family_item == NULL)
return;
foo_canvas_set_pixels_per_unit (FOO_CANVAS (typesel->canvas), .5); /* 50% zoom */
if (typesel->sample_graph_item == NULL) {
GtkAllocation *size = &GTK_WIDGET (typesel->canvas)->allocation;
typesel->sample_graph_item = foo_canvas_item_new (typesel->graph_group,
GOG_CONTROL_FOOCANVAS_TYPE,
"model", typesel->state->graph,
NULL);
cb_typesel_sample_plot_resize (FOO_CANVAS (typesel->canvas),
size, typesel);
g_return_if_fail (typesel->sample_graph_item != NULL);
}
foo_canvas_item_hide (FOO_CANVAS_ITEM (typesel->current_family_item));
foo_canvas_item_hide (FOO_CANVAS_ITEM (typesel->selector));
foo_canvas_item_show (FOO_CANVAS_ITEM (typesel->graph_group));
}
static void
cb_sample_released (GraphGuruTypeSelector *typesel)
{
if (typesel->current_family_item == NULL)
return;
foo_canvas_set_pixels_per_unit (FOO_CANVAS (typesel->canvas), 1.);
foo_canvas_item_hide (FOO_CANVAS_ITEM (typesel->graph_group));
foo_canvas_item_show (FOO_CANVAS_ITEM (typesel->current_family_item));
foo_canvas_item_show (FOO_CANVAS_ITEM (typesel->selector));
}
typedef struct {
GraphGuruTypeSelector *typesel;
FooCanvasGroup *group;
FooCanvasItem *current_item;
GogPlotType *current_type;
int col, row;
} type_list_closure;
static void
cb_plot_types_init (char const *id, GogPlotType *type,
type_list_closure *closure)
{
double x1, y1, w, h;
FooCanvasItem *item;
int col, row;
GdkPixbuf *image = get_pixbuf (type->sample_image_file);
g_return_if_fail (image != NULL);
col = type->col;
row = type->row;
get_pos (col, row, &x1, &y1);
w = gdk_pixbuf_get_width (image);
if (w > MINOR_PIXMAP_WIDTH)
w = MINOR_PIXMAP_WIDTH;
h = gdk_pixbuf_get_height (image);
if (h > MINOR_PIXMAP_HEIGHT)
h = MINOR_PIXMAP_HEIGHT;
item = foo_canvas_item_new (closure->group,
foo_canvas_pixbuf_get_type (),
"x", x1, "y", y1,
"width", w, "height", h,
"pixbuf", image,
"point_ignores_alpha", TRUE,
NULL);
g_object_set_data (G_OBJECT (item), PLOT_TYPE_KEY, (gpointer)type);
if (closure->current_type == NULL ||
closure->row > row ||
(closure->row == row && closure->col > col)) {
closure->current_type = type;
closure->current_item = item;
closure->col = col;
closure->row = row;
}
}
static void
cb_plot_families_init (char const *id, GogPlotFamily *family,
GraphGuruTypeSelector *typesel)
{
FooCanvasGroup *group;
GtkTreeIter iter;
type_list_closure closure;
if (g_hash_table_size (family->types) <= 0)
return;
/* Define a canvas group for all the minor types */
group = FOO_CANVAS_GROUP (foo_canvas_item_new (
foo_canvas_root (FOO_CANVAS (typesel->canvas)),
foo_canvas_group_get_type (),
"x", 0.0,
"y", 0.0,
NULL));
foo_canvas_item_hide (FOO_CANVAS_ITEM (group));
gtk_list_store_append (typesel->model, &iter);
gtk_list_store_set (typesel->model, &iter,
PLOT_FAMILY_TYPE_IMAGE, get_pixbuf (family->sample_image_file),
PLOT_FAMILY_TYPE_NAME, _(family->name),
PLOT_FAMILY_TYPE_CANVAS_GROUP, group,
-1);
closure.typesel = typesel;
closure.group = group;
closure.current_type = NULL;
closure.current_item = NULL;
/* Init the list and the canvas group for each family */
g_hash_table_foreach (family->types,
(GHFunc) cb_plot_types_init, &closure);
g_object_set_data (G_OBJECT (group), FIRST_MINOR_TYPE,
closure.current_item);
}
static void
cb_canvas_realized (GtkLayout *widget)
{
gdk_window_set_back_pixmap (widget->bin_window, NULL, FALSE);
}
static void
graph_guru_state_destroy (GraphGuruState *state)
{
g_return_if_fail (state != NULL);
if (state->graph != NULL) {
g_object_unref (state->graph);
state->graph = NULL;
}
if (state->gui != NULL) {
g_object_unref (state->gui);
state->gui = NULL;
}
if (state->register_closure != NULL) {
g_closure_unref (state->register_closure);
state->register_closure = NULL;
}
state->dialog = NULL;
g_free (state);
}
static void populate_graph_item_list (GogObject *obj, GogObject *select,
GraphGuruState *s, GtkTreeIter *parent,
gboolean insert);
static void
cb_graph_guru_add_item (GtkWidget *w, GraphGuruState *s)
{
gog_object_add_by_role (s->prop_object,
g_object_get_data (G_OBJECT (w), ROLE_KEY), NULL);
}
static void
cb_graph_guru_delete_item (GraphGuruState *s)
{
if (s->prop_object != NULL) {
GtkTreeIter iter;
GogObject *obj = s->prop_object;
/* select the parent before we delete */
gtk_tree_model_iter_parent (GTK_TREE_MODEL (s->prop_model),
&iter, &s->prop_iter);
gtk_tree_selection_select_iter (s->prop_selection, &iter);
gog_object_clear_parent (obj);
g_object_unref (obj);
}
}
static void
update_prec_menu (GraphGuruState *s, gboolean inc_ok, gboolean dec_ok)
{
gtk_widget_set_sensitive (s->prec.first, inc_ok);
gtk_widget_set_sensitive (s->prec.inc, inc_ok);
gtk_widget_set_sensitive (s->prec.dec, dec_ok);
gtk_widget_set_sensitive (s->prec.last, dec_ok);
gtk_widget_set_sensitive (s->prec.menu, dec_ok | inc_ok);
}
static gboolean
cb_reordered_find (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
GraphGuruState *s)
{
GogObject *obj;
gtk_tree_model_get (model, iter, PLOT_ATTR_OBJECT, &obj, -1);
if (obj == s->search_target) {
gtk_tree_store_move_after (s->prop_model, &s->prop_iter, iter);
return TRUE;
}
return FALSE;
}
static void
reorder (GraphGuruState *s, gboolean inc, gboolean goto_max)
{
gboolean inc_ok, dec_ok;
GogObject *after;
g_return_if_fail (s->search_target == NULL);
after = gog_object_reorder (s->prop_object, inc, goto_max);
if (after != NULL) {
s->search_target = after;
gtk_tree_model_foreach (GTK_TREE_MODEL (s->prop_model),
(GtkTreeModelForeachFunc) cb_reordered_find, s);
s->search_target = NULL;
} else
gtk_tree_store_move_after (s->prop_model, &s->prop_iter, NULL);
gog_object_can_reorder (s->prop_object, &inc_ok, &dec_ok);
update_prec_menu (s, inc_ok, dec_ok);
}
static void cb_prec_first (GraphGuruState *s) { reorder (s, TRUE, TRUE); }
static void cb_prec_inc (GraphGuruState *s) { reorder (s, TRUE, FALSE); }
static void cb_prec_dec (GraphGuruState *s) { reorder (s, FALSE, FALSE); }
static void cb_prec_last (GraphGuruState *s) { reorder (s, FALSE, TRUE); }
struct type_menu_create {
GraphGuruState *state;
GtkWidget *menu;
gboolean non_blank;
};
static gint
cb_cmp_plot_type (GogPlotType const *a, GogPlotType const *b)
{
if (a->row == b->row)
return a->col - b->col;
return a->row - b->row;
}
static void
cb_plot_type_list (char const *id, GogPlotType *type, GSList **list)
{
*list = g_slist_insert_sorted (*list, type,
(GCompareFunc) cb_cmp_plot_type);
}
static void
cb_graph_guru_add_plot (GtkWidget *w, GraphGuruState *s)
{
GogPlotType *type = g_object_get_data (G_OBJECT (w), PLOT_TYPE_KEY);
GogPlot *plot = gog_plot_new_by_type (type);
gog_object_add_by_name (GOG_OBJECT (s->prop_object),
"Plot", GOG_OBJECT (plot));
/* as a convenience add a series to the newly created plot */
gog_object_add_by_name (GOG_OBJECT (plot), "Series", NULL);
}
static void
cb_plot_family_menu_create (char const *id, GogPlotFamily *family,
struct type_menu_create *closure)
{
GtkWidget *w, *menu;
GSList *ptr, *types = NULL;
GogPlotType *type;
if (g_hash_table_size (family->types) <= 0)
return;
menu = gtk_image_menu_item_new_with_label (_(family->name));
gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu),
gtk_image_new_from_pixbuf (
get_pixbuf (family->sample_image_file)));
gtk_menu_shell_append (GTK_MENU_SHELL (closure->menu), menu);
closure->non_blank = TRUE;
w = gtk_menu_new ();
gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), w);
menu = w;
g_hash_table_foreach (family->types,
(GHFunc) cb_plot_type_list, &types);
for (ptr = types ; ptr != NULL ; ptr = ptr->next) {
type = ptr->data;
w = gtk_image_menu_item_new_with_label (_(type->name));
gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (w),
gtk_image_new_from_pixbuf (
get_pixbuf (type->sample_image_file)));
g_object_set_data (G_OBJECT (w), PLOT_TYPE_KEY, type);
g_signal_connect (G_OBJECT (w),
"activate",
G_CALLBACK (cb_graph_guru_add_plot), closure->state);
gtk_menu_shell_append (GTK_MENU_SHELL (menu), w);
}
g_slist_free (types);
}
/* return TRUE if there are plot types to add */
static GtkWidget *
plot_type_menu_create (GraphGuruState *s)
{
struct type_menu_create closure;
closure.state = s;
closure.menu = gtk_menu_new ();
closure.non_blank = FALSE;
g_hash_table_foreach ((GHashTable *)gog_plot_families (),
(GHFunc) cb_plot_family_menu_create, &closure);
if (closure.non_blank)
return closure.menu;
gtk_object_destroy (GTK_OBJECT (closure.menu));
return NULL;
}
static void
cb_attr_tree_selection_change (GraphGuruState *s)
{
gboolean add_ok = FALSE;
gboolean delete_ok = FALSE;
gboolean inc_ok = FALSE;
gboolean dec_ok = FALSE;
GtkTreeModel *model;
GogObject *obj = NULL;
GtkWidget *w, *editor, *notebook;
GtkTreePath *path;
if (gtk_tree_selection_get_selected (s->prop_selection, &model, &s->prop_iter))
gtk_tree_model_get (model, &s->prop_iter,
PLOT_ATTR_OBJECT, &obj,
-1);
if (s->prop_object == obj)
return;
path = gtk_tree_model_get_path (GTK_TREE_MODEL (s->prop_model), &s->prop_iter);
gtk_tree_view_scroll_to_cell (s->prop_view, path, NULL, FALSE, 0, 0);
gtk_tree_path_free (path);
/* remove the old prop page */
s->prop_object = obj;
w = gtk_bin_get_child (GTK_BIN (s->prop_container));
if (w != NULL)
gtk_container_remove (s->prop_container, w);
if (s->prop_object != NULL) {
/* Setup up the additions menu */
GSList *additions = gog_object_possible_additions (s->prop_object);
if (additions != NULL) {
GogObjectRole const *role;
GSList *ptr;
GtkWidget *tmp, *menu;
menu = gtk_menu_new ();
for (ptr = additions ; ptr != NULL ; ptr = ptr->next) {
role = ptr->data;
/* somewhat hackish, but I do not see a need
* for anything more general yet */
if (strcmp (role->id, "Plot")) {
tmp = gtk_menu_item_new_with_label (_(role->id));
g_object_set_data (G_OBJECT (tmp), ROLE_KEY,
(gpointer)role);
g_signal_connect (G_OBJECT (tmp),
"activate",
G_CALLBACK (cb_graph_guru_add_item), s);
} else {
GtkWidget *submenu = plot_type_menu_create (s);
if (submenu != NULL) {
tmp = gtk_menu_item_new_with_label (_(role->id));
gtk_menu_item_set_submenu (GTK_MENU_ITEM (tmp), submenu);
} else
continue;
}
gtk_menu_shell_append (GTK_MENU_SHELL (menu), tmp);
}
add_ok = (additions != NULL);
g_slist_free (additions);
gtk_menu_item_set_submenu (GTK_MENU_ITEM (s->add_menu), menu);
gtk_widget_show_all (s->add_menu);
}
/* if we ever go back to the typeselector be sure to
* add the plot to the last selected chart */
s->chart = (GogChart *)
gog_object_get_parent_typed (obj, GOG_CHART_TYPE);
s->plot = (GogPlot *)
gog_object_get_parent_typed (obj, GOG_PLOT_TYPE);
if (s->plot == NULL) {
if (s->chart == NULL && s->graph != NULL) {
GSList *charts = gog_object_get_children (GOG_OBJECT (s->graph),
gog_object_find_role_by_name (GOG_OBJECT (s->graph), "Chart"));
if (charts != NULL && charts->next == NULL)
s->chart = charts->data;
g_slist_free (charts);
}
if (s->chart != NULL) {
GSList *plots = gog_object_get_children (GOG_OBJECT (s->chart),
gog_object_find_role_by_name (GOG_OBJECT (s->chart), "Plot"));
if (plots != NULL && plots->next == NULL)
s->plot = plots->data;
g_slist_free (plots);
}
}
gtk_widget_set_sensitive (s->button_navigate, s->chart != NULL);
delete_ok = gog_object_is_deletable (s->prop_object);
gog_object_can_reorder (obj, &inc_ok, &dec_ok);
/* create a prefs page for the graph obj */
editor = gog_object_get_editor (obj, s->dalloc, s->cc);
if (GTK_IS_NOTEBOOK (editor)) {
notebook = editor;
} else {
notebook = gtk_notebook_new ();
gtk_notebook_set_show_tabs (GTK_NOTEBOOK (notebook), FALSE);
if (editor == NULL)
editor = gtk_label_new (NULL); /* dummy widget for empty page */
gtk_notebook_prepend_page (GTK_NOTEBOOK (notebook), editor, NULL);
gtk_widget_show (editor);
}
gtk_container_add (s->prop_container, notebook);
gtk_widget_show (notebook);
}
gtk_widget_set_sensitive (s->delete_button, delete_ok);
gtk_widget_set_sensitive (s->add_menu, add_ok);
update_prec_menu (s, inc_ok, dec_ok);
}
static gboolean
cb_find_renamed_item (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
GraphGuruState *s)
{
GogObject *obj;
gtk_tree_model_get (model, iter, PLOT_ATTR_OBJECT, &obj, -1);
if (obj == s->search_target) {
s->search_target = NULL;
gtk_tree_store_set (s->prop_model, iter,
PLOT_ATTR_NAME, gog_object_get_name (obj),
-1);
return TRUE;
}
return FALSE;
}
static void
cb_obj_name_changed (GogObject *obj, GraphGuruState *s)
{
s->search_target = obj;
gtk_tree_model_foreach (GTK_TREE_MODEL (s->prop_model),
(GtkTreeModelForeachFunc) cb_find_renamed_item, s);
}
static gboolean
cb_find_child_added (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
GraphGuruState *s)
{
GogObject *obj;
gtk_tree_model_get (model, iter, PLOT_ATTR_OBJECT, &obj, -1);
if (obj == s->search_target) {
s->search_target = NULL;
populate_graph_item_list (s->new_child, s->new_child, s, iter, TRUE);
return TRUE;
}
return FALSE;
}
static void
cb_obj_child_added (GogObject *parent, GogObject *child, GraphGuruState *s)
{
s->search_target = parent;
s->new_child = child;
gtk_tree_model_foreach (GTK_TREE_MODEL (s->prop_model),
(GtkTreeModelForeachFunc) cb_find_child_added, s);
s->new_child = NULL;
}
static gboolean
cb_find_child_removed (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
GraphGuruState *s)
{
GogObject *obj;
GtkWidget *w;
gtk_tree_model_get (model, iter, PLOT_ATTR_OBJECT, &obj, -1);
if (obj == s->search_target) {
s->search_target = NULL;
/* remove the tree element and the prop page */
gtk_tree_store_remove (s->prop_model, iter);
w = gtk_bin_get_child (GTK_BIN (s->prop_container));
if (w != NULL)
gtk_container_remove (s->prop_container, w);
return TRUE;
}
return FALSE;
}
static void
cb_obj_child_removed (GogObject *parent, GogObject *child, GraphGuruState *s)
{
s->search_target = child;
gtk_tree_model_foreach (GTK_TREE_MODEL (s->prop_model),
(GtkTreeModelForeachFunc) cb_find_child_removed, s);
if (s->chart == (gpointer)child) {
s->chart = NULL;
s->plot = NULL;
gtk_widget_set_sensitive (s->button_navigate, FALSE);
} else if (s->plot == (gpointer)child)
s->plot = NULL;
}
static void
populate_graph_item_list (GogObject *obj, GogObject *select, GraphGuruState *s,
GtkTreeIter *parent, gboolean insert)
{
GSList *children, *ptr;
GtkTreeIter iter;
GtkTreePath *path;
GClosure *closure;
if (insert) {
GogObject *gparent = gog_object_get_parent (obj);
gint i = g_slist_index (gparent->children, obj);
if (i > 0) {
GtkTreeIter sibling;
gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (s->prop_model),
&sibling, parent, i-1);
gtk_tree_store_insert_after (s->prop_model, &iter,
parent, &sibling);
} else
gtk_tree_store_prepend (s->prop_model, &iter, parent);
} else
gtk_tree_store_append (s->prop_model, &iter, parent);
gtk_tree_store_set (s->prop_model, &iter,
PLOT_ATTR_OBJECT, obj,
PLOT_ATTR_NAME, gog_object_get_name (obj),
-1);
/* remove the signal handlers when the guru goes away */
closure = g_cclosure_new (G_CALLBACK (cb_obj_name_changed), s, NULL);
g_object_watch_closure (G_OBJECT (s->prop_view), closure);
g_signal_connect_closure (G_OBJECT (obj),
"name-changed",
closure, FALSE);
closure = g_cclosure_new (G_CALLBACK (cb_obj_child_added), s, NULL);
g_object_watch_closure (G_OBJECT (s->prop_view), closure);
g_signal_connect_closure (G_OBJECT (obj),
"child-added",
closure, FALSE);
closure = g_cclosure_new (G_CALLBACK (cb_obj_child_removed), s, NULL);
g_object_watch_closure (G_OBJECT (s->prop_view), closure);
g_signal_connect_closure (G_OBJECT (obj),
"child-removed",
closure, FALSE);
children = gog_object_get_children (obj, NULL);
for (ptr = children ; ptr != NULL ; ptr = ptr->next)
populate_graph_item_list (ptr->data, select, s, &iter, FALSE);
g_slist_free (children);
/* ensure that new items are visible */
path = gtk_tree_model_get_path (
GTK_TREE_MODEL (s->prop_model), &iter);
gtk_tree_view_expand_to_path (s->prop_view, path);
gtk_tree_path_free (path);
if (obj == select)
/* select after expanding so that we do not lose selection due
* to visibility */
gtk_tree_selection_select_iter (s->prop_selection, &iter);
}
static gboolean
cb_find_item (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
GraphGuruState *s)
{
GogObject *obj;
gtk_tree_model_get (model, iter, PLOT_ATTR_OBJECT, &obj, -1);
if (obj == s->search_target) {
gtk_tree_selection_select_iter (s->prop_selection, iter);
return TRUE;
}
return FALSE;
}
static gint
cb_canvas_select_item (FooCanvas *canvas, GdkEventButton *event,
GraphGuruState *s)
{
GogView *view;
GogRenderer *rend;
double x, y;
g_return_val_if_fail (FOO_IS_CANVAS (canvas), FALSE);
if (canvas->current_item == NULL)
return FALSE;
g_object_get (G_OBJECT (s->sample_graph_item), "renderer", &rend, NULL);
g_object_get (G_OBJECT (rend), "view", &view, NULL);
foo_canvas_window_to_world (canvas, event->x, event->y, &x, &y);
gog_view_info_at_point (view,
x * canvas->pixels_per_unit, y * canvas->pixels_per_unit,
s->prop_object, &s->search_target, NULL);
if (s->search_target == NULL)
return FALSE;
gtk_tree_model_foreach (GTK_TREE_MODEL (s->prop_model),
(GtkTreeModelForeachFunc) cb_find_item, s);
s->search_target = NULL;
return TRUE;
}
static void
cb_sample_plot_resize (FooCanvas *canvas,
GtkAllocation *alloc, GraphGuruState *s)
{
/* Use 96dpi and zoom */
double zoom = 1. / canvas->pixels_per_unit;
foo_canvas_item_set (s->sample_graph_item,
"w", (double)alloc->width * zoom,
"h", (double)alloc->height * zoom,
"logical_width_pts", (72. * zoom * (double)alloc->width) / 96.,
"logical_height_pts", (72. * zoom * (double)alloc->height) / 96.,
NULL);
}
static void
graph_guru_init_format_page (GraphGuruState *s)
{
GtkWidget *w;
GtkTreeViewColumn *column;
if (s->fmt_page_initialized)
return;
s->fmt_page_initialized = TRUE;
s->add_menu = glade_xml_get_widget (s->gui, "add_menu");
s->delete_button = glade_xml_get_widget (s->gui, "delete");
s->prec.menu = glade_xml_get_widget (s->gui, "precedence_menu");
s->prec.inc = glade_xml_get_widget (s->gui, "inc_precedence");
s->prec.dec = glade_xml_get_widget (s->gui, "dec_precedence");
s->prec.first = glade_xml_get_widget (s->gui, "first_precedence");
s->prec.last = glade_xml_get_widget (s->gui, "last_precedence");
g_signal_connect_swapped (G_OBJECT (s->delete_button),
"clicked",
G_CALLBACK (cb_graph_guru_delete_item), s);
g_signal_connect_swapped (G_OBJECT (s->prec.first),
"activate",
G_CALLBACK (cb_prec_first), s);
g_signal_connect_swapped (G_OBJECT (s->prec.inc),
"activate",
G_CALLBACK (cb_prec_inc), s);
g_signal_connect_swapped (G_OBJECT (s->prec.dec),
"activate",
G_CALLBACK (cb_prec_dec), s);
g_signal_connect_swapped (G_OBJECT (s->prec.last),
"activate",
G_CALLBACK (cb_prec_last), s);
/* Load up the sample view and make it fill the entire canvas */
w = glade_xml_get_widget (s->gui, "sample_canvas");
foo_canvas_set_pixels_per_unit (FOO_CANVAS (w), .5); /* 50% zoom */
s->sample_graph_item = foo_canvas_item_new (
foo_canvas_root (FOO_CANVAS (w)), GOG_CONTROL_FOOCANVAS_TYPE,
"model", s->graph,
NULL);
cb_sample_plot_resize (FOO_CANVAS (w), &w->allocation, s);
g_signal_connect (G_OBJECT (w),
"size_allocate",
G_CALLBACK (cb_sample_plot_resize), s);
g_signal_connect_after (G_OBJECT (w),
"button_press_event",
G_CALLBACK (cb_canvas_select_item), s);
gtk_widget_show (w);
w = glade_xml_get_widget (s->gui, "prop_alignment");
s->prop_container = GTK_CONTAINER (w);
s->prop_model = gtk_tree_store_new (PLOT_ATTR_NUM_COLUMNS,
G_TYPE_STRING, G_TYPE_POINTER);
s->prop_view = GTK_TREE_VIEW (gtk_tree_view_new_with_model (
GTK_TREE_MODEL (s->prop_model)));
s->prop_selection = gtk_tree_view_get_selection (s->prop_view);
gtk_tree_selection_set_mode (s->prop_selection, GTK_SELECTION_BROWSE);
g_signal_connect_swapped (s->prop_selection,
"changed",
G_CALLBACK (cb_attr_tree_selection_change), s);
column = gtk_tree_view_column_new_with_attributes (_("Name"),
gtk_cell_renderer_text_new (),
"text", PLOT_ATTR_NAME, NULL);
gtk_tree_view_append_column (s->prop_view, column);
gtk_tree_view_set_headers_visible (s->prop_view, FALSE);
gtk_tree_store_clear (s->prop_model);
populate_graph_item_list (GOG_OBJECT (s->graph), GOG_OBJECT (s->graph),
s, NULL, FALSE);
gtk_tree_view_expand_all (s->prop_view);
w = glade_xml_get_widget (s->gui, "attr_window");
gtk_container_add (GTK_CONTAINER (w), GTK_WIDGET (s->prop_view));
gtk_widget_show_all (w);
}
static void
graph_guru_set_page (GraphGuruState *s, int page)
{
char *name;
if (s->current_page == page)
return;
switch (page) {
case 0: name = _("Step 1 of 2: Select Chart Type");
gtk_widget_set_sensitive (s->button_navigate, s->plot != NULL);
gtk_button_set_label (GTK_BUTTON (s->button_navigate),
GTK_STOCK_GO_FORWARD);
break;
case 1:
if (s->initial_page == 0) {
name = _("Step 2 of 2: Customize Chart");
gtk_widget_set_sensitive (s->button_navigate, s->chart != NULL);
gtk_button_set_label (GTK_BUTTON (s->button_navigate),
GTK_STOCK_GO_BACK);
} else {
name = _("Customize Chart");
gtk_widget_hide (s->button_navigate);
}
graph_guru_init_format_page (s);
break;
default:
g_warning ("Invalid Chart Guru page");
return;
}
s->current_page = page;
gtk_notebook_set_current_page (s->steps, page - s->initial_page);
gtk_window_set_title (GTK_WINDOW (s->dialog), name);
}
static void
cb_graph_guru_clicked (GtkWidget *button, GraphGuruState *s)
{
if (s->dialog == NULL)
return;
if (button == s->button_navigate) {
graph_guru_set_page (s, (s->current_page + 1) % 2);
return;
} else if (button == s->button_ok &&
s->register_closure != NULL && s->graph != NULL) {
/* Invoking closure */
GValue instance_and_params[2];
gpointer data = s->register_closure->is_invalid ?
NULL : s->register_closure->data;
instance_and_params[0].g_type = 0;
g_value_init (&instance_and_params[0], GOG_GRAPH_TYPE);
g_value_set_instance (&instance_and_params[0], s->graph);
instance_and_params[1].g_type = 0;
g_value_init (&instance_and_params[1], G_TYPE_POINTER);
g_value_set_pointer (&instance_and_params[1], data);
g_closure_set_marshal (s->register_closure,
g_cclosure_marshal_VOID__POINTER);
g_closure_invoke (s->register_closure,
NULL,
2,
instance_and_params,
NULL);
g_value_unset (instance_and_params + 0);
}
gtk_widget_destroy (GTK_WIDGET (s->dialog));
}
static GtkWidget *
graph_guru_init_button (GraphGuruState *s, char const *widget_name)
{
GtkWidget *button = glade_xml_get_widget (s->gui, widget_name);
g_signal_connect (G_OBJECT (button),
"clicked",
G_CALLBACK (cb_graph_guru_clicked), s);
return button;
}
static GtkWidget *
graph_guru_init_ok_button (GraphGuruState *s)
{
GtkButton *button = GTK_BUTTON (glade_xml_get_widget
(s->gui, "button_ok"));
if (s->editing) {
gtk_button_set_label (button, GTK_STOCK_APPLY);
gtk_button_set_use_stock (button, TRUE);
} else {
gtk_button_set_use_stock (button, FALSE);
gtk_button_set_use_underline (button, TRUE);
gtk_button_set_label (button, _("_Insert"));
}
g_signal_connect (G_OBJECT (button),
"clicked",
G_CALLBACK (cb_graph_guru_clicked), s);
return GTK_WIDGET (button);
}
static void
typesel_set_selection_color (GraphGuruTypeSelector *typesel)
{
GtkWidget *w = gtk_entry_new ();
GdkColor *color = &w->style->base [GTK_WIDGET_HAS_FOCUS (typesel->canvas)
? GTK_STATE_SELECTED : GTK_STATE_ACTIVE];
guint32 select_color = 0;
select_color |= ((color->red >> 8) & 0xff) << 24;
select_color |= ((color->green >> 8) & 0xff) << 16;
select_color |= ((color->blue >> 8) & 0xff) << 8;
select_color |= 0x40; /* alpha of 25% */
foo_canvas_item_set (typesel->selector,
"fill_color_rgba", select_color,
NULL);
gtk_object_destroy (GTK_OBJECT (w));
}
static GtkWidget *
graph_guru_type_selector_new (GraphGuruState *s)
{
GtkTreeSelection *selection;
GraphGuruTypeSelector *typesel;
GtkWidget *selector;
GladeXML *gui;
gui = gnm_glade_xml_new (s->cc, "gog-guru-type-selector.glade", "type_selector", NULL);
typesel = g_new0 (GraphGuruTypeSelector, 1);
typesel->state = s;
typesel->current_family_item = NULL;
typesel->current_minor_item = NULL;
typesel->current_type = NULL;
typesel->sample_graph_item = NULL;
selector = glade_xml_get_widget (gui, "type_selector");
/* List of family types */
typesel->model = gtk_list_store_new (PLOT_FAMILY_NUM_COLUMNS,
GDK_TYPE_PIXBUF,
G_TYPE_STRING,
G_TYPE_POINTER);
typesel->list_view = GTK_TREE_VIEW (glade_xml_get_widget (gui, "type_treeview"));
gtk_tree_view_set_model (typesel->list_view, GTK_TREE_MODEL (typesel->model));
gtk_tree_view_append_column (typesel->list_view,
gtk_tree_view_column_new_with_attributes ("",
gtk_cell_renderer_pixbuf_new (),
"pixbuf", PLOT_FAMILY_TYPE_IMAGE,
NULL));
gtk_tree_view_append_column (typesel->list_view,
gtk_tree_view_column_new_with_attributes (_("_Plot Type"),
gtk_cell_renderer_text_new (),
"text", PLOT_FAMILY_TYPE_NAME,
NULL));
selection = gtk_tree_view_get_selection (typesel->list_view);
gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
g_signal_connect_swapped (selection,
"changed",
G_CALLBACK (cb_selection_changed), typesel);
/* Setup an canvas to display the sample image & the sample plot. */
typesel->canvas = foo_canvas_new ();
typesel->graph_group = FOO_CANVAS_GROUP (foo_canvas_item_new (
foo_canvas_root (FOO_CANVAS (typesel->canvas)),
foo_canvas_group_get_type (),
"x", 0.0,
"y", 0.0,
NULL));
g_object_connect (typesel->canvas,
"signal::realize", G_CALLBACK (cb_canvas_realized), typesel,
"signal::size_allocate", G_CALLBACK (cb_typesel_sample_plot_resize), typesel,
"signal_after::key_press_event", G_CALLBACK (cb_key_press_event), typesel,
"signal::button_press_event", G_CALLBACK (cb_button_press_event), typesel,
"swapped_signal::focus_in_event", G_CALLBACK (typesel_set_selection_color), typesel,
"swapped_signal::focus_out_event", G_CALLBACK (typesel_set_selection_color), typesel,
NULL);
gtk_widget_set_size_request (typesel->canvas,
MINOR_PIXMAP_WIDTH*3 + BORDER*5,
MINOR_PIXMAP_HEIGHT*3 + BORDER*5);
foo_canvas_scroll_to (FOO_CANVAS (typesel->canvas), 0, 0);
gtk_container_add (GTK_CONTAINER (glade_xml_get_widget (gui, "canvas_container")),
typesel->canvas);
/* Init the list and the canvas group for each family */
g_hash_table_foreach ((GHashTable *)gog_plot_families (),
(GHFunc) cb_plot_families_init, typesel);
/* The alpha blended selection box */
typesel->selector = foo_canvas_item_new (
foo_canvas_root (FOO_CANVAS (typesel->canvas)),
foo_canvas_rect_get_type (),
"outline_color_rgba", 0x000000ff, /* black */
"width_pixels", 1,
NULL);
typesel_set_selection_color (typesel);
/* Setup the description label */
typesel->label = GTK_LABEL (glade_xml_get_widget (gui, "description_label"));
/* Set up sample button */
typesel->sample_button = glade_xml_get_widget (gui, "sample_button");
g_signal_connect_swapped (G_OBJECT (typesel->sample_button),
"pressed",
G_CALLBACK (cb_sample_pressed), typesel);
g_signal_connect_swapped (G_OBJECT (typesel->sample_button),
"released",
G_CALLBACK (cb_sample_released), typesel);
g_object_set_data_full (G_OBJECT (selector),
"state", typesel, (GDestroyNotify) g_free);
g_object_unref (G_OBJECT (gui));
return selector;
}
static gboolean
graph_guru_init (GraphGuruState *s)
{
s->gui = gnm_glade_xml_new (s->cc, "gog-guru.glade", NULL, NULL);
if (s->gui == NULL)
return TRUE;
s->dialog = glade_xml_get_widget (s->gui, "GraphGuru");
s->steps = GTK_NOTEBOOK (glade_xml_get_widget (s->gui, "notebook"));
/* Buttons */
s->button_cancel = graph_guru_init_button (s, "button_cancel");
s->button_navigate = graph_guru_init_button (s, "button_navigate");
s->button_ok = graph_guru_init_ok_button (s);
gnumeric_init_help_button (
glade_xml_get_widget (s->gui, "help_button"),
"sect-graphics-plots");
return FALSE;
}
/**
* dialog_graph_guru
* @wb : The workbook to use as a parent window.
* @graph : the graph to edit
* @page : the page to start on.
*
* Pop up a graph guru.
*/
GtkWidget *
gog_guru (GogGraph *graph, GogDataAllocator *dalloc,
GnmCmdContext *cc, GtkWindow *toplevel,
GClosure *closure)
{
int page = (graph != NULL) ? 1 : 0;
GraphGuruState *state;
state = g_new0 (GraphGuruState, 1);
state->valid = FALSE;
state->updating = FALSE;
state->fmt_page_initialized = FALSE;
state->editing = (graph != NULL);
state->gui = NULL;
state->cc = cc;
state->dalloc = dalloc;
state->current_page = -1;
state->register_closure = closure;
g_closure_ref (closure);
if (graph != NULL) {
g_return_val_if_fail (IS_GOG_GRAPH (graph), NULL);
state->graph = gog_graph_dup (graph);
state->chart = NULL;
state->plot = NULL;
} else {
state->plot = NULL;
state->graph = g_object_new (GOG_GRAPH_TYPE, NULL);
state->chart = GOG_CHART (gog_object_add_by_name (
GOG_OBJECT (state->graph), "Chart", NULL));
}
if (state->graph == NULL || graph_guru_init (state)) {
graph_guru_state_destroy (state);
return NULL;
}
/* Ok everything is hooked up. Let-er rip */
state->valid = TRUE;
state->initial_page = page;
if (page == 0) {
GtkWidget *w = graph_guru_type_selector_new (state);
gtk_notebook_prepend_page (state->steps, w, NULL);
gtk_widget_show_all (w);
}
graph_guru_set_page (state, page);
/* a candidate for merging into attach guru */
g_object_set_data_full (G_OBJECT (state->dialog),
"state", state, (GDestroyNotify) graph_guru_state_destroy);
gnumeric_non_modal_dialog (toplevel, GTK_WINDOW (state->dialog));
gtk_widget_show (GTK_WIDGET (state->dialog));
return state->dialog;
}
#if 0
gtk_tree_sortable_set_sort_func (store, column, compare_rows,
GUINT_TO_POINTER (column), NULL);
for each column of your database table.
int
compare_rows (GtkTreeModel *model,
GtkTreeIter *a,
GtkTreeIter *b,
gpointer user_data)
{
int column = GPOINTER_TO_UINT (user_data);
MyRow *row_a, row_b;
gtk_tree_model_get (model, DATA_COLUMN, a, &row_a, -1);
gtk_tree_model_get (model, DATA_COLUMN, b, &row_b, -1);
return compare_cells (row_a->cells[column], row_b->cells[column]);
}
#endif