X-Git-Url: https://git.dogcows.com/gitweb?p=chaz%2Fhomebank;a=blobdiff_plain;f=src%2Fgtk-chart-stack.c;fp=src%2Fgtk-chart-stack.c;h=6997608da0ea2207a9cee7b6a0a511c54d5b0389;hp=0000000000000000000000000000000000000000;hb=996fa4ab9f6b836001f8ad0eecbfd3821687fea7;hpb=27f6e3b112df235c8e9afc9911b1f6bce208a001 diff --git a/src/gtk-chart-stack.c b/src/gtk-chart-stack.c new file mode 100644 index 0000000..6997608 --- /dev/null +++ b/src/gtk-chart-stack.c @@ -0,0 +1,1275 @@ +/* HomeBank -- Free, easy, personal accounting for everyone. + * Copyright (C) 1995-2013 Maxime DOYEN + * + * This file is part of HomeBank. + * + * HomeBank 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. + * + * HomeBank 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, see . + */ + + +#include +#include +#include + +#include "homebank.h" +#include "gtk-chart-colors.h" +#include "gtk-chart-stack.h" + +#define MYDEBUG 0 + +#if MYDEBUG +#define DB(x) (x); +#else +#define DB(x); +#endif + + +#define HELPDRAW 0 + + + +/* --- prototypes --- */ +static void ui_chart_stack_class_init (ChartStackClass *klass); +static void ui_chart_stack_init (ChartStack *chart); +static void ui_chart_stack_destroy (GtkWidget *chart); +/*static void ui_chart_stack_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec);*/ + +static gboolean drawarea_configure_event_callback (GtkWidget *widget, GdkEventConfigure *event, gpointer user_data); +static gboolean drawarea_draw_callback(GtkWidget *widget, cairo_t *cr, gpointer user_data); +static gboolean drawarea_scroll_event_callback( GtkWidget *widget, GdkEventScroll *event, gpointer user_data); +static gboolean drawarea_motionnotifyevent_callback(GtkWidget *widget, GdkEventMotion *event, gpointer user_data); +static void ui_chart_stack_first_changed( GtkAdjustment *adj, gpointer user_data); + +static void ui_chart_stack_clear(ChartStack *chart, gboolean store); + +static void ui_chart_stack_queue_redraw(ChartStack *chart); + +/* --- variables --- */ +static GtkBoxClass *parent_class = NULL; + + +/* --- functions --- */ +GType ui_chart_stack_get_type () +{ +static GType ui_chart_stack_type = 0; + + if (G_UNLIKELY(ui_chart_stack_type == 0)) + { + const GTypeInfo ui_chart_stack_info = + { + sizeof (ChartStackClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) ui_chart_stack_class_init, + NULL, /* class_finalize */ + NULL, /* class_init */ + sizeof (ChartStack), + 0, /* n_preallocs */ + (GInstanceInitFunc) ui_chart_stack_init, + NULL /* value_table */ + }; + + ui_chart_stack_type = g_type_register_static (GTK_TYPE_BOX, "ChartStack", + &ui_chart_stack_info, 0); + + } + return ui_chart_stack_type; +} + + +static void ui_chart_stack_class_init (ChartStackClass * class) +{ +//GObjectClass *gobject_class; +GtkWidgetClass *widget_class; + + DB( g_print("\n[chartstack] class_init\n") ); + + //gobject_class = (GObjectClass*) class; + widget_class = (GtkWidgetClass*) class; + + parent_class = g_type_class_peek_parent (class); + + //gobject_class->dispose = ui_chart_stack_dispose; + //gobject_class->finalize = ui_chart_stack_finalize; + //gobject_class->set_property = ui_chart_stack_set_property; + //gobject_class->get_property = ui_chart_stack_get_property; + + widget_class->destroy = ui_chart_stack_destroy; + + +} + +/* get/set properties goes here */ + + +static void +ui_chart_stack_init (ChartStack * chart) +{ +GtkWidget *widget, *hbox, *scrollwin; + + + DB( g_print("\n[chartstack] init\n") ); + + chart->surface = NULL; + chart->nb_items = 0; + chart->active = -1; + chart->title = NULL; + chart->subtitle = NULL; + chart->pfd = NULL; + + chart->budget_title = "Budget"; + chart->result_title = "Result"; + + chart->barw = GTK_CHARTSTACK_BARW; + ui_chart_stack_set_color_scheme(chart, CHART_COLMAP_HOMEBANK); + + widget=GTK_WIDGET(chart); + + gtk_box_set_homogeneous(GTK_BOX(widget), FALSE); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start (GTK_BOX (widget), hbox, TRUE, TRUE, 0); + + /* drawing area */ + scrollwin = gtk_frame_new(NULL); + gtk_frame_set_shadow_type (GTK_FRAME(scrollwin), GTK_SHADOW_ETCHED_IN); + gtk_box_pack_start (GTK_BOX (hbox), scrollwin, TRUE, TRUE, 0); + + //scrollwin = gtk_scrolled_window_new(NULL,NULL); + //gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrollwin), GTK_SHADOW_ETCHED_IN); + //gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwin), GTK_POLICY_NEVER, GTK_POLICY_NEVER); + //gtk_box_pack_start (GTK_BOX (hbox), scrollwin, TRUE, TRUE, 0); + + chart->drawarea = gtk_drawing_area_new(); + //gtk_widget_set_double_buffered (GTK_WIDGET(widget), FALSE); + + gtk_container_add( GTK_CONTAINER(scrollwin), chart->drawarea ); + gtk_widget_set_size_request(chart->drawarea, 150, 150 ); + gtk_widget_set_has_tooltip(chart->drawarea, FALSE); + gtk_widget_show(chart->drawarea); + + /* scrollbar */ + chart->adjustment = GTK_ADJUSTMENT(gtk_adjustment_new (0.0, 0.0, 1.0, 1.0, 1.0, 1.0)); + chart->scrollbar = gtk_scrollbar_new (GTK_ORIENTATION_VERTICAL,GTK_ADJUSTMENT (chart->adjustment)); + gtk_box_pack_start (GTK_BOX (hbox), chart->scrollbar, FALSE, TRUE, 0); + + gtk_widget_set_events(GTK_WIDGET(chart->drawarea), + GDK_EXPOSURE_MASK | + //GDK_POINTER_MOTION_MASK | + //GDK_POINTER_MOTION_HINT_MASK | + GDK_SCROLL_MASK + ); + + g_signal_connect( G_OBJECT(chart->drawarea), "configure-event", G_CALLBACK (drawarea_configure_event_callback), chart); + //g_signal_connect( G_OBJECT(chart->drawarea), "realize", G_CALLBACK(drawarea_realize_callback), chart ) ; + g_signal_connect( G_OBJECT(chart->drawarea), "draw", G_CALLBACK(drawarea_draw_callback), chart ); + //g_signal_connect( G_OBJECT(chart->drawarea), "query-tooltip", G_CALLBACK(drawarea_querytooltip_callback), chart ); + g_signal_connect( G_OBJECT(chart->drawarea), "scroll-event", G_CALLBACK(drawarea_scroll_event_callback), chart ) ; + g_signal_connect( G_OBJECT(chart->drawarea), "motion-notify-event", G_CALLBACK(drawarea_motionnotifyevent_callback), chart ); + + g_signal_connect (G_OBJECT(chart->adjustment), "value-changed", G_CALLBACK (ui_chart_stack_first_changed), chart); + + /* + g_signal_connect( G_OBJECT(chart->drawarea), "leave-notify-event", G_CALLBACK(ui_chart_stack_leave), chart ); + g_signal_connect( G_OBJECT(chart->drawarea), "enter-notify-event", G_CALLBACK(ui_chart_stack_enter), chart ); + g_signal_connect( G_OBJECT(chart->drawarea), "button-press-event", G_CALLBACK(ui_chart_stack_button_press), chart ); + g_signal_connect( G_OBJECT(chart->drawarea), "button-release-event", G_CALLBACK(ui_chart_stack_button_release), chart ); + */ + + +} + + +GtkWidget * +ui_chart_stack_new (void) +{ +GtkWidget *chart; + + chart = (GtkWidget *)g_object_new (GTK_TYPE_CHARTSTACK, NULL); + + return chart; +} + + +void +ui_chart_stack_destroy (GtkWidget * object) +{ +ChartStack *chart = GTK_CHARTSTACK(object); + + g_return_if_fail (GTK_IS_CHARTSTACK (object)); + + ui_chart_stack_clear(GTK_CHARTSTACK (object), FALSE); + + if(chart->pfd) + { + pango_font_description_free (chart->pfd); + chart->pfd = NULL; + } + + if (chart->surface) + { + cairo_surface_destroy (chart->surface); + chart->surface = NULL; + } + + GTK_WIDGET_CLASS (parent_class)->destroy (object); +} + + + + +/* +** print a integer number +*/ +static gchar *ui_chart_stack_print_int(ChartStack *chart, gdouble value) +{ + + hb_strfmon(chart->buffer, CHART_BUFFER_LENGTH-1, value, chart->kcur, chart->minor); + return chart->buffer; +} + + +static void ui_chart_stack_clear(ChartStack *chart, gboolean store) +{ + + DB( g_print("\n[chartstack] clear\n") ); + + //free & clear any previous allocated datas + if(chart->title != NULL) + { + g_free(chart->title); + chart->title = NULL; + } + + if(chart->subtitle != NULL) + { + g_free(chart->subtitle); + chart->subtitle = NULL; + } + + if(chart->items != NULL) + { + /*for(i=0;inb_items;i++) + { + StackItem *item = &g_array_index(chart->items, StackItem, i); + + //g_free(item->legend); + }*/ + g_array_free(chart->items, TRUE); + chart->items = NULL; + } + + chart->nb_items = 0; + +} + + +static void ui_chart_stack_setup_with_model(ChartStack *chart, GtkTreeModel *list_store, gchar *coltitle1, gchar *coltitle2) +{ +guint i; +gboolean valid; +GtkTreeIter iter; + + DB( g_print("\n[chartstack] setup with model\n") ); + + ui_chart_stack_clear(chart, TRUE); + + chart->nb_items = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(list_store), NULL); + + chart->items = g_array_sized_new(FALSE, FALSE, sizeof(StackItem), chart->nb_items); + + DB( g_print(" nb=%d\n", chart->nb_items) ); + + if(coltitle1) + chart->budget_title = coltitle1; + if(coltitle2) + chart->result_title = coltitle2; + + /* Get the first iter in the list */ + valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL(list_store), &iter); + i = 0; + while (valid) + { + gint id; + gchar *label, *status; + gdouble value1, value2; + StackItem item; + + gtk_tree_model_get (GTK_TREE_MODEL(list_store), &iter, + 0, &id, + //1, &pos, + 2, &label, + 3, &value1, //spent + 4, &value2, //budget + //5, &result, + 6, &status, + -1); + + item.label = label; + item.spent = value1; + item.budget = value2; + item.status = status; + + /* additional pre-compute */ + item.result = item.spent - item.budget; + item.rawrate = 0; + if(ABS(item.budget) > 0) + { + item.rawrate = item.spent / item.budget; + } + + item.warn = item.result < 0.0 ? TRUE : FALSE; + + item.rate = CLAMP(item.rawrate, 0, 1.0); + + g_array_append_vals(chart->items, &item, 1); + + i++; + valid = gtk_tree_model_iter_next (list_store, &iter); + } + +} + + +static void ui_chart_stack_set_font_size(ChartStack *chart, gint font_size) +{ +gint size = 10; + + DB( g_print("\n[chartstack] set font size\n") ); + + switch(font_size) + { + case CHART_FONT_SIZE_TITLE: + size = chart->pfd_size + 3; + break; + case CHART_FONT_SIZE_SUBTITLE: + size = chart->pfd_size + 1; + break; + case CHART_FONT_SIZE_NORMAL: + size = chart->pfd_size - 1; + break; + } + + DB( g_print(" size=%d\n", size) ); + + pango_font_description_set_size(chart->pfd, size * PANGO_SCALE); + +} + + + +static void ui_chart_stack_calculation(ChartStack *chart) +{ +GtkWidget *drawarea = chart->drawarea; +cairo_surface_t *surf; +cairo_t *cr; +gint blkw; +gint i; +int tw, th; +gchar *valstr; +GtkAllocation allocation; +PangoLayout *layout; + + DB( g_print("\n[chartstack] bar calculation\n") ); + + gtk_widget_get_allocation(drawarea, &allocation); + + chart->l = CHART_MARGIN; + chart->t = CHART_MARGIN; + chart->r = allocation.width - CHART_MARGIN; + chart->b = allocation.height - CHART_MARGIN; + chart->w = allocation.width - (CHART_MARGIN*2); + chart->h = allocation.height - (CHART_MARGIN*2); + + //todo: seems not working well... + surf = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, allocation.width, allocation.height); + cr = cairo_create (surf); + + /* Create a PangoLayout, set the font and text */ + layout = pango_cairo_create_layout (cr); + + + // compute title + chart->title_zh = 0; + if(chart->title) + { + //pango_font_description_set_size(chart->pfd, CHART_FONT_SIZE_TITLE * PANGO_SCALE); + ui_chart_stack_set_font_size(chart, CHART_FONT_SIZE_TITLE); + pango_layout_set_font_description (layout, chart->pfd); + + pango_layout_set_text (layout, chart->title, -1); + pango_layout_get_size (layout, &tw, &th); + chart->title_zh = (th / PANGO_SCALE) + CHART_SPACING; + DB( g_print(" - title: %s w=%d h=%d\n", chart->title, tw, th) ); + } + + // compute period + chart->subtitle_zh = 0; + if(chart->subtitle) + { + //pango_font_description_set_size(chart->pfd, CHART_FONT_SIZE_PERIOD * PANGO_SCALE); + ui_chart_stack_set_font_size(chart, CHART_FONT_SIZE_SUBTITLE); + pango_layout_set_font_description (layout, chart->pfd); + + pango_layout_set_text (layout, chart->subtitle, -1); + pango_layout_get_size (layout, &tw, &th); + chart->subtitle_zh = (th / PANGO_SCALE) + CHART_SPACING; + DB( g_print(" - period: %s w=%d h=%d\n", chart->subtitle, tw, th) ); + } + + // compute other text + //pango_font_description_set_size(chart->pfd, CHART_FONT_SIZE_NORMAL * PANGO_SCALE); + ui_chart_stack_set_font_size(chart, CHART_FONT_SIZE_NORMAL); + pango_layout_set_font_description (layout, chart->pfd); + + double title_w = 0; + chart->bud_col_w = 0; + chart->rel_col_w = 0; + + gdouble maxbudget = 0; + gdouble maxresult = 0; + gdouble result; + for(i=0;inb_items;i++) + { + StackItem *item = &g_array_index(chart->items, StackItem, i); + + // category width + pango_layout_set_text (layout, item->label, -1); + pango_layout_get_size (layout, &tw, &th); + title_w = MAX(title_w, (tw / PANGO_SCALE)); + + DB( g_print(" - calc '%s' title_w=%f (w=%d)\n", item->label, title_w, tw) ); + + //result = ABS(chart->spent[i]) - ABS(chart->budget[i]); + result = ABS(item->spent - item->budget); + + maxbudget = MAX(maxbudget, ABS(item->budget) ); + maxresult = MAX(maxresult, result); + + DB( g_print(" - maxbudget maxbudget=%f (w=%d)\n", maxbudget, tw) ); + + pango_layout_set_text (layout, item->status, -1); + pango_layout_get_size (layout, &tw, &th); + chart->rel_col_w = MAX(chart->rel_col_w, (tw / PANGO_SCALE)); + } + + chart->rel_col_w += CHART_SPACING; + + // compute budget/result width + valstr = ui_chart_stack_print_int(chart, -maxbudget); + pango_layout_set_text (layout, valstr, -1); + pango_layout_get_size (layout, &tw, &th); + chart->bud_col_w = (tw / PANGO_SCALE); + pango_layout_set_text (layout, chart->budget_title, -1); + pango_layout_get_size (layout, &tw, &th); + chart->bud_col_w = MAX(chart->bud_col_w, (tw / PANGO_SCALE)); + DB( g_print(" - budget-col: w=%f, %.2f, '%s'\n", chart->bud_col_w, maxbudget, valstr) ); + + + valstr = ui_chart_stack_print_int(chart, -maxresult); + pango_layout_set_text (layout, valstr, -1); + pango_layout_get_size (layout, &tw, &th); + chart->res_col_w = (tw / PANGO_SCALE); + pango_layout_set_text (layout, chart->result_title, -1); + pango_layout_get_size (layout, &tw, &th); + chart->res_col_w = MAX(chart->res_col_w, (tw / PANGO_SCALE)); + DB( g_print(" - result-col: w=%f, %.2f, '%s'\n", chart->res_col_w, maxresult, valstr) ); + + + // collect other width, add margins + chart->header_zh = (th / PANGO_SCALE) + CHART_SPACING; + chart->cat_col_w = title_w + CHART_SPACING; + + //chart->title_y = chart->t; + chart->subtitle_y = chart->t + chart->title_zh; + chart->header_y = chart->subtitle_y + chart->subtitle_zh; + + + chart->graph_width = chart->w - chart->cat_col_w - chart->bud_col_w - chart->res_col_w - chart->rel_col_w - (double)(CHART_SPACING*4); + chart->graph_height = chart->h - chart->title_zh - chart->subtitle_zh -chart->header_zh; + + + DB( g_print("gfx_w = %.2f - %.2f - %.2f - %.2f - %.2f \n", + chart->w , chart->cat_col_w , chart->bud_col_w , chart->res_col_w , (double)(CHART_SPACING*4)) ); + + DB( g_print("gfx_w = %.2f\n", chart->graph_width) ); + + //if expand : we compute available space + //chart->barw = MAX(32, (chart->graph_width)/chart->nb_items); + //chart->barw = 32; // usr setted or defaut to BARW + + blkw = chart->barw + floor(chart->barw * 0.2); + chart->blkw = blkw; + + chart->visible = (chart->graph_height - chart->t) / blkw; + chart->visible = MIN(chart->visible, chart->nb_items); + + g_object_unref (layout); + + cairo_destroy(cr); + cairo_surface_destroy(surf); + +} + + +#if HELPDRAW == 1 +static void ui_chart_stack_draw_help(GtkWidget *widget, gpointer user_data) +{ +ChartStack *chart = GTK_CHARTSTACK(user_data); +cairo_t *cr; +double x, y, y2; +gint first = 0; +gint i; + + //cr = gdk_cairo_create (gtk_widget_get_window(widget)); + cr = cairo_create (chart->surface); + + cairo_set_line_width (cr, 1); + + + cairo_set_line_width(cr, 1.0); + cairo_set_source_rgb(cr, 0.0, 1.0, 0.0); //green + cairo_rectangle(cr, chart->l+0.5, chart->t+0.5, chart->w, chart->h); + cairo_stroke(cr); + + + //title rect + cairo_set_source_rgb(cr, .0, .0, 1.0); + cairo_rectangle(cr, chart->l+0.5, chart->t+0.5, chart->w, chart->title_zh); + cairo_stroke(cr); + + //period rect + cairo_set_source_rgb(cr, .0, 0, 1.0); + cairo_rectangle(cr, chart->l+0.5, chart->subtitle_y+0.5, chart->w, chart->subtitle_zh); + cairo_stroke(cr); + + //header rect + cairo_set_source_rgb(cr, .0, 1.0, 1.0); + cairo_rectangle(cr, chart->l+0.5, chart->header_y+0.5, chart->w, chart->header_zh); + cairo_stroke(cr); + + //category column + y = chart->t + chart->title_zh + chart->header_zh + chart->subtitle_zh; + cairo_set_source_rgb(cr, 0.0, 0.0, 1.0); //blue + cairo_rectangle(cr, chart->l+0.5, y+0.5, chart->cat_col_w, chart->h - y); + cairo_stroke(cr); + + //budget column + x = chart->l + chart->cat_col_w + chart->graph_width + CHART_SPACING ; + cairo_set_source_rgb(cr, 0.0, 0.0, 1.0); //blue + cairo_rectangle(cr, x+0.5, y+0.5, chart->bud_col_w, chart->h - y); + cairo_stroke(cr); + + //result column + x = chart->l + chart->cat_col_w + chart->graph_width + chart->bud_col_w + (CHART_SPACING*3); + cairo_set_source_rgb(cr, 0.0, 0.0, 1.0); //blue + cairo_rectangle(cr, x+0.5, y+0.5, chart->res_col_w, chart->h - y); + cairo_stroke(cr); + + + // draw item lines + y = chart->header_y + chart->header_zh; + + cairo_set_source_rgb(cr, 1.0, .0, .0); + cairo_rectangle(cr, chart->l+chart->cat_col_w+0.5, y+0.5, chart->graph_width+0.5, chart->graph_height+0.5); + cairo_stroke(cr); + + + + y2 = y+0.5; + cairo_set_line_width(cr, 1.0); + double dashlength; + dashlength = 4; + cairo_set_dash (cr, &dashlength, 1, 0); + cairo_set_source_rgb(cr, 1.0, 0.0, 1.0); // violet + for(i=first; i<=(first+chart->visible) ;i++) + { + cairo_move_to(cr, chart->l, y2); + cairo_line_to(cr, chart->r, y2); + + y2 += chart->blkw; + } + cairo_stroke(cr); + + cairo_destroy(cr); + +} +#endif + + +/* +** draw all visible bars +*/ +static void ui_chart_stack_draw_bars(ChartStack *chart, cairo_t *cr) +{ +double x, y, x2, y2, h; +gint first; +gint i, idx; +gchar *valstr; +PangoLayout *layout; +int tw, th; + + DB( g_print("\n[chartstack] bar draw bars\n") ); + + layout = pango_cairo_create_layout (cr); + + x = chart->l + chart->cat_col_w; + y = chart->t + chart->title_zh + chart->header_zh + chart->subtitle_zh; + first = (gint)gtk_adjustment_get_value(GTK_ADJUSTMENT(chart->adjustment)); + + // draw title + if(chart->title) + { + //pango_font_description_set_size(chart->pfd, CHART_FONT_SIZE_TITLE * PANGO_SCALE); + ui_chart_stack_set_font_size(chart, CHART_FONT_SIZE_TITLE); + pango_layout_set_font_description (layout, chart->pfd); + pango_layout_set_text (layout, chart->title, -1); + pango_layout_get_size (layout, &tw, &th); + + cairo_user_set_rgbcol(cr, &global_colors[THTEXT]); + cairo_move_to(cr, chart->l, chart->t); + pango_cairo_show_layout (cr, layout); + } + + // draw period + if(chart->subtitle) + { + //pango_font_description_set_size(chart->pfd, CHART_FONT_SIZE_PERIOD * PANGO_SCALE); + ui_chart_stack_set_font_size(chart, CHART_FONT_SIZE_SUBTITLE); + pango_layout_set_font_description (layout, chart->pfd); + pango_layout_set_text (layout, chart->subtitle, -1); + pango_layout_get_size (layout, &tw, &th); + + cairo_user_set_rgbcol(cr, &global_colors[THTEXT]); + cairo_move_to(cr, chart->l, chart->subtitle_y); + pango_cairo_show_layout (cr, layout); + } + + // draw column title + //cairo_user_set_rgbcol(cr, &global_colors[BLACK]); + cairo_user_set_rgbacol (cr, &global_colors[THTEXT], 0.78); + //pango_font_description_set_size(chart->pfd, CHART_FONT_SIZE_NORMAL * PANGO_SCALE); + ui_chart_stack_set_font_size(chart, CHART_FONT_SIZE_NORMAL); + pango_layout_set_font_description (layout, chart->pfd); + + pango_layout_set_text (layout, chart->budget_title, -1); + pango_layout_get_size (layout, &tw, &th); + cairo_move_to(cr, chart->l + chart->cat_col_w + chart->graph_width + chart->bud_col_w + CHART_SPACING - (tw /PANGO_SCALE), chart->header_y); + pango_cairo_show_layout (cr, layout); + + pango_layout_set_text (layout, chart->result_title, -1); + pango_layout_get_size (layout, &tw, &th); + cairo_move_to(cr, chart->l + chart->cat_col_w + chart->graph_width + chart->bud_col_w + chart->res_col_w - (tw /PANGO_SCALE) + (CHART_SPACING*3), chart->header_y); + pango_cairo_show_layout (cr, layout); + + + // draw items + //pango_font_description_set_size(chart->pfd, CHART_FONT_SIZE_NORMAL * PANGO_SCALE); + ui_chart_stack_set_font_size(chart, CHART_FONT_SIZE_NORMAL); + pango_layout_set_font_description (layout, chart->pfd); + + for(i=0; ivisible ;i++) + { + StackItem *item; + gint barw = chart->barw; + gint blkw = chart->blkw; + + idx = i + first; + + item = &g_array_index(chart->items, StackItem, idx); + + x2 = x; + y2 = y + (CHART_SPACING/2) + (blkw * i); + + DB( g_print("'%-32s' wrn=%d %.2f%% (%.2f%%) :: r=% 4.2f s=% 4.2f b=% 4.2f\n", + item->label, item->warn, item->rawrate, item->rate, item->result, item->spent, item->budget) ); + + valstr = item->label; + pango_layout_set_text (layout, valstr, -1); + pango_layout_get_size (layout, &tw, &th); + + double ytext = y2 + ((barw - (th / PANGO_SCALE))/2); + + //cairo_user_set_rgbcol(cr, &global_colors[BLACK]); + cairo_user_set_rgbacol (cr, &global_colors[THTEXT], 0.78); + cairo_move_to(cr, chart->l + chart->cat_col_w - (tw / PANGO_SCALE) - CHART_SPACING, ytext); + pango_cairo_show_layout (cr, layout); + + // bar background + cairo_user_set_rgbacol(cr, &global_colors[THTEXT], 0.15); + cairo_rectangle(cr, x2, y2, chart->graph_width, barw); + cairo_fill(cr); + + //bar with color :: todo migrate this + h = floor(item->rate * chart->graph_width); + if(item->warn) + { + cairo_user_set_rgbcol_over(cr, &chart->color_scheme.colors[chart->color_scheme.cs_red], idx == chart->active); + } + else + { + if(item->rate > 0.8 && item->rate < 1.0) + cairo_user_set_rgbcol_over(cr, &chart->color_scheme.colors[chart->color_scheme.cs_orange], idx == chart->active); + else + cairo_user_set_rgbcol_over(cr, &chart->color_scheme.colors[chart->color_scheme.cs_green], idx == chart->active); + } + + + cairo_rectangle(cr, x2, y2, h, barw); + cairo_fill(cr); + + // spent value + if( item->result != 0) + { + valstr = ui_chart_stack_print_int(chart, item->spent); + pango_layout_set_text (layout, valstr, -1); + pango_layout_get_size (layout, &tw, &th); + + if( h >= ( (tw / PANGO_SCALE) + (CHART_SPACING*2)) ) + { + // draw inside + cairo_user_set_rgbcol(cr, &global_colors[WHITE]); + //cairo_user_set_rgbcol (cr, &global_colors[THBASE]); + cairo_move_to(cr, x2 + h - (tw / PANGO_SCALE) - CHART_SPACING, ytext); + } + else + { + // draw outside + //cairo_user_set_rgbcol(cr, &global_colors[TEXT]); + cairo_user_set_rgbacol (cr, &global_colors[THTEXT], 0.78); + cairo_move_to(cr, x2 + h + CHART_SPACING, ytext); + } + + pango_cairo_show_layout (cr, layout); + } + + // budget value + valstr = ui_chart_stack_print_int(chart, item->budget); + pango_layout_set_text (layout, valstr, -1); + pango_layout_get_size (layout, &tw, &th); + //cairo_user_set_rgbcol(cr, &global_colors[BLACK]); + cairo_user_set_rgbacol (cr, &global_colors[THTEXT], 0.78); + cairo_move_to(cr, chart->l + chart->cat_col_w + chart->graph_width + chart->bud_col_w + CHART_SPACING - (tw / PANGO_SCALE), ytext); + pango_cairo_show_layout (cr, layout); + + // result value + + if( item->result != 0) + { + valstr = ui_chart_stack_print_int(chart, item->result); + + if(item->warn) + //cairo_set_source_rgb(cr, COLTOCAIRO(164), COLTOCAIRO(0), COLTOCAIRO(0)); + cairo_user_set_rgbcol(cr, &chart->color_scheme.colors[chart->color_scheme.cs_red]); + else + //cairo_user_set_rgbcol(cr, &global_colors[TEXT]); + cairo_user_set_rgbacol (cr, &global_colors[THTEXT], 0.78); + + pango_layout_set_text (layout, valstr, -1); + pango_layout_get_size (layout, &tw, &th); + cairo_move_to(cr, chart->l + chart->cat_col_w + chart->graph_width + chart->bud_col_w + chart->res_col_w - (tw / PANGO_SCALE) + (CHART_SPACING*3), ytext); + pango_cairo_show_layout (cr, layout); + + // status + pango_layout_set_text (layout, item->status, -1); + pango_layout_get_size (layout, &tw, &th); + cairo_move_to(cr, chart->l + chart->cat_col_w + chart->graph_width + chart->bud_col_w + chart->res_col_w + (CHART_SPACING*4), ytext); + pango_cairo_show_layout (cr, layout); + } + + //y += blkw; + + } + + g_object_unref (layout); + +} + + +/* +** get the bar under the mouse pointer +*/ +static gint ui_chart_stack_get_active(GtkWidget *widget, gint x, gint y, gpointer user_data) +{ +ChartStack *chart = GTK_CHARTSTACK(user_data); +gint retval, first, index, py; +gint blkw = chart->blkw; +double oy; + + retval = -1; + + oy = chart->t + chart->title_zh + chart->header_zh + chart->subtitle_zh; + + //DB( g_print(" y=%d, oy=%f, cb=%f\n", y, oy, chart->b) ); + + + if( (y <= chart->b && y >= oy) && (x >= chart->l && x <= chart->r) ) + { + first = gtk_adjustment_get_value(GTK_ADJUSTMENT(chart->adjustment)); + py = (y - oy); + index = first + (py / blkw); + + + if(index < chart->nb_items) + retval = index; + + //DB( g_print(" hover=%d\n", retval) ); + } + + return(retval); +} + + +static void ui_chart_stack_first_changed( GtkAdjustment *adj, gpointer user_data) +{ +ChartStack *chart = GTK_CHARTSTACK(user_data); +//gint first; + + DB( g_print("\n[chartstack] bar first changed\n") ); + + //first = gtk_adjustment_get_value(GTK_ADJUSTMENT(adj)); + + //DB( g_print(" first=%d\n", first) ); + +/* + DB( g_print("scrollbar\n adj=%8x, low=%.2f upp=%.2f val=%.2f step=%.2f page=%.2f size=%.2f\n", adj, + adj->lower, adj->upper, adj->value, adj->step_increment, adj->page_increment, adj->page_size) ); + */ + /* Set the number of decimal places to which adj->value is rounded */ + //gtk_scale_set_digits (GTK_SCALE (hscale), (gint) adj->value); + //gtk_scale_set_digits (GTK_SCALE (vscale), (gint) adj->value); + gtk_widget_queue_draw(chart->drawarea); + +} + +/* +** scrollbar set values for upper, page size, and also show/hide +*/ +static void ui_chart_stack_scrollbar_setvalues(ChartStack *chart) +{ +GtkAdjustment *adj = chart->adjustment; +gint first; + + g_return_if_fail (GTK_IS_ADJUSTMENT (adj)); + + DB( g_print("\n[chartstack] sb_set_values\n") ); + + //if(visible < entries) + //{ + first = gtk_adjustment_get_value(GTK_ADJUSTMENT(adj)); + + DB( g_print(" entries=%d, visible=%d\n", chart->nb_items, chart->visible) ); + DB( g_print(" first=%d, upper=%d, pagesize=%d\n", first, chart->nb_items, chart->visible) ); + + gtk_adjustment_set_upper(adj, (gdouble)chart->nb_items); + gtk_adjustment_set_page_size(adj, (gdouble)chart->visible); + gtk_adjustment_set_page_increment(adj, (gdouble)chart->visible); + + if(first+chart->visible > chart->nb_items) + { + gtk_adjustment_set_value(adj, (gdouble)chart->nb_items - chart->visible); + } + + gtk_adjustment_changed (adj); + + //gtk_widget_show(GTK_WIDGET(scrollbar)); + //} + //else + //gtk_widget_hide(GTK_WIDGET(scrollbar)); + + +} + + +static void ui_chart_stack_recompute(ChartStack *chart) +{ + + DB( g_print("\n[chartstack] recompute\n") ); + + ui_chart_stack_calculation(chart); + gtk_adjustment_set_value(chart->adjustment, 0); + ui_chart_stack_scrollbar_setvalues(chart); + gtk_widget_show(chart->scrollbar); + + gtk_widget_queue_draw( chart->drawarea ); +} + + +/* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ +static gboolean drawarea_full_redraw(GtkWidget *widget, gpointer user_data) +{ +ChartStack *chart = GTK_CHARTSTACK(user_data); +cairo_t *cr; + + DB( g_print("\n[gtkchart] drawarea full redraw\n") ); + + cr = cairo_create (chart->surface); + + /* fillin the back in white */ + //cairo_user_set_rgbcol(cr, &global_colors[WHITE]); + cairo_user_set_rgbcol(cr, &global_colors[THBASE]); + + cairo_paint(cr); + + if(chart->nb_items == 0) + { + cairo_destroy(cr); + return FALSE; + } + +#if HELPDRAW == 1 + ui_chart_stack_draw_help(widget, user_data); +#endif + + ui_chart_stack_draw_bars(chart, cr); + + cairo_destroy(cr); + + return TRUE; +} + + +static gboolean +drawarea_configure_event_callback (GtkWidget *widget, + GdkEventConfigure *event, + gpointer user_data) +{ +ChartStack *chart = GTK_CHARTSTACK(user_data); +GtkAllocation allocation; +GtkStyleContext *context; +PangoFontDescription *desc; +gboolean colfound; +GdkRGBA color; + + DB( g_print("\n[chartstack] drawarea configure \n") ); + + DB( g_print("w=%d h=%d\n", allocation.width, allocation.height) ); + + gtk_widget_get_allocation (widget, &allocation); + + if (chart->surface) + cairo_surface_destroy (chart->surface); + + chart->surface = gdk_window_create_similar_surface (gtk_widget_get_window (widget), + CAIRO_CONTENT_COLOR, + allocation.width, + allocation.height); + + context = gtk_widget_get_style_context (widget); + + chart_color_global_default(); + + // get base color + colfound = gtk_style_context_lookup_color(context, "theme_base_color", &color); + if(!colfound) + colfound = gtk_style_context_lookup_color(context, "base_color", &color); + + if( colfound ) + { + struct rgbcol *tcol = &global_colors[THBASE]; + tcol->r = color.red * 255; + tcol->g = color.green * 255; + tcol->b = color.blue * 255; + DB( g_print(" - theme base col: %x %x %x\n", tcol->r, tcol->g, tcol->b) ); + } + + //get text color + colfound = gtk_style_context_lookup_color(context, "theme_fg_color", &color); + if(!colfound) + gtk_style_context_lookup_color(context, "fg_color", &color); + + if( colfound ) + { + struct rgbcol *tcol = &global_colors[THTEXT]; + tcol->r = color.red * 255; + tcol->g = color.green * 255; + tcol->b = color.blue * 255; + DB( g_print(" - theme text (bg) col: %x %x %x\n", tcol->r, tcol->g, tcol->b) ); + } + + /* get and copy the font */ + gtk_style_context_get(context, GTK_STATE_FLAG_NORMAL, "font", &desc, NULL); + if(chart->pfd) + { + pango_font_description_free (chart->pfd); + chart->pfd = NULL; + } + chart->pfd = pango_font_description_copy(desc); + chart->pfd_size = pango_font_description_get_size (desc) / PANGO_SCALE; + chart->barw = (6 + chart->pfd_size) * PHI; + + DB( g_print("family: %s\n", pango_font_description_get_family(chart->pfd) ) ); + DB( g_print("size : %d (%d)\n", chart->pfd_size, chart->pfd_size/PANGO_SCALE ) ); + DB( g_print("isabs : %d\n", pango_font_description_get_size_is_absolute (chart->pfd) ) ); + + if( gtk_widget_get_realized(widget) ) + { + ui_chart_stack_recompute(chart); + drawarea_full_redraw(widget, user_data); + } + + /* We've handled the configure event, no need for further processing. */ + return TRUE; +} + + +static gboolean drawarea_draw_callback(GtkWidget *widget, cairo_t *cr, gpointer user_data) +{ +ChartStack *chart = GTK_CHARTSTACK(user_data); + + if( !gtk_widget_get_realized(widget) || chart->surface == NULL ) + return FALSE; + + //DB( g_print("\n[chartstack] drawarea draw cb\n") ); + + cairo_set_source_surface (cr, chart->surface, 0, 0); + cairo_paint (cr); + + /* always redraw directly the active block */ + gint first; + double ox, oy; + + first = (gint)gtk_adjustment_get_value(GTK_ADJUSTMENT(chart->adjustment)); + ox = chart->l + chart->cat_col_w; + oy = chart->t + chart->title_zh + chart->header_zh + chart->subtitle_zh; + + + if(chart->active != -1) + { + DB( g_print(" draw active\n") ); + + oy += CHART_SPACING/2 + (chart->active - first) * chart->blkw; + //cairo_user_set_rgbacol (cr, &global_colors[THTEXT], 0.78); + cairo_user_set_rgbacol(cr, &global_colors[WHITE], OVER_ALPHA); + //cairo_move_to(cr, chart->l, chart->t); + cairo_rectangle(cr, ox, oy, chart->graph_width, chart->barw); + cairo_fill(cr); + } + + return FALSE; +} + + +static gboolean drawarea_motionnotifyevent_callback(GtkWidget *widget, GdkEventMotion *event, gpointer user_data) +{ +ChartStack *chart = GTK_CHARTSTACK(user_data); +gint x, y; + + if(chart->nb_items == 0) + return FALSE; + + DB( g_print("\n[chartstack] drawarea motion cb\n") ); + x = event->x; + y = event->y; + + //todo see this + if(event->is_hint) + { + //DB( g_print(" is hint\n") ); + + gdk_window_get_device_position(event->window, event->device, &x, &y, NULL); + //gdk_window_get_pointer(event->window, &x, &y, NULL); + //return FALSE; + } + + chart->active = ui_chart_stack_get_active(widget, x, y, chart); + + + // rollover redraw ? + DB( g_print(" %d, %d :: active: last=%d, curr=%d\n", x, y, chart->lastactive, chart->active) ); + + if(chart->lastactive != chart->active) + { + GdkRectangle update_rect; + gint first; + double oy; + + DB( g_print(" motion rollover redraw :: active=%d\n", chart->active) ); + + first = (gint)gtk_adjustment_get_value(GTK_ADJUSTMENT(chart->adjustment)); + oy = chart->t + chart->title_zh + chart->header_zh + chart->subtitle_zh; + + if(chart->lastactive != -1) + { + update_rect.x = chart->l; + update_rect.y = oy + (chart->lastactive - first) * chart->blkw; + update_rect.width = chart->r; + update_rect.height = chart->blkw; + + /* Now invalidate the affected region of the drawing area. */ + gdk_window_invalidate_rect (gtk_widget_get_window (widget), + &update_rect, + FALSE); + } + + update_rect.x = chart->l; + update_rect.y = oy + (chart->active - first) * chart->blkw; + update_rect.width = chart->r; + update_rect.height = chart->blkw; + + /* Now invalidate the affected region of the drawing area. */ + gdk_window_invalidate_rect (gtk_widget_get_window (widget), + &update_rect, + FALSE); + + //gtk_widget_queue_draw( widget ); + //retval = FALSE; + } + + chart->lastactive = chart->active; + + return TRUE; +} + + + +static gboolean drawarea_scroll_event_callback( GtkWidget *widget, GdkEventScroll *event, gpointer user_data) +{ +ChartStack *chart = GTK_CHARTSTACK(user_data); +GtkAdjustment *adj = chart->adjustment; +gdouble first, upper, pagesize; + + DB( g_print("\n[chartstack] scroll\n") ); + + first = gtk_adjustment_get_value(GTK_ADJUSTMENT(adj)); + //lower = gtk_adjustment_get_lower(GTK_ADJUSTMENT(adj)); + upper = gtk_adjustment_get_upper(GTK_ADJUSTMENT(adj)); + pagesize = gtk_adjustment_get_page_size(GTK_ADJUSTMENT(adj)); + + DB( g_print("- pos is %.2f, [%.2f - %.2f]\n", first, 0.0, upper) ); + + switch(event->direction) + { + case GDK_SCROLL_UP: + gtk_adjustment_set_value(adj, first - 1); + break; + case GDK_SCROLL_DOWN: + gtk_adjustment_set_value(adj, CLAMP(first + 1, 0, upper - pagesize) ); + break; + default: + break; + } + + drawarea_full_redraw(widget, user_data); + + return TRUE; +} + + +/* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ +/* public functions */ +/* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ + +static void ui_chart_stack_queue_redraw(ChartStack *chart) +{ + + if( gtk_widget_get_realized(GTK_WIDGET(chart)) ) + { + ui_chart_stack_recompute(chart); + drawarea_full_redraw(chart->drawarea, chart); + //gtk_widget_queue_draw( chart->drawarea ); + } +} + +/* +** change the model and/or column +*/ +void ui_chart_stack_set_dualdatas(ChartStack *chart, GtkTreeModel *model, gchar *coltitle1, gchar *coltitle2, gchar *title, gchar *subtitle) +{ + g_return_if_fail (GTK_IS_CHARTSTACK (chart)); + + if( GTK_IS_TREE_MODEL(model) ) + { + ui_chart_stack_setup_with_model(chart, model, coltitle1, coltitle2 ); + if(title != NULL) + chart->title = g_strdup(title); + if(subtitle != NULL) + chart->subtitle = g_strdup(subtitle); + + ui_chart_stack_queue_redraw(chart); + } + else + { + ui_chart_stack_clear(chart, TRUE); + } +} + +/* +** change the tooltip title +*/ +void ui_chart_stack_set_title(ChartStack * chart, gchar *title) +{ + g_return_if_fail (GTK_IS_CHARTSTACK (chart)); + + chart->title = g_strdup(title); + + DB( g_print("\n[chartstack] set title = %s\n", chart->title) ); + + ui_chart_stack_recompute(chart); + +} + +void ui_chart_stack_set_subtitle(ChartStack * chart, gchar *subtitle) +{ + g_return_if_fail (GTK_IS_CHARTSTACK (chart)); + + chart->subtitle = g_strdup(subtitle); + + DB( g_print("\n[chartstack] set period = %s\n", chart->subtitle) ); + + ui_chart_stack_recompute(chart); + +} + + +/* +** change the minor visibility +*/ +void ui_chart_stack_show_minor(ChartStack * chart, gboolean minor) +{ + g_return_if_fail (GTK_IS_CHARTSTACK (chart)); + + chart->minor = minor; + + ui_chart_stack_queue_redraw(chart); + +} + +void ui_chart_stack_set_color_scheme(ChartStack * chart, gint index) +{ + colorscheme_init(&chart->color_scheme, index); +} + + +/* +** set the minor parameters +*/ +/*void ui_chart_stack_set_minor_prefs(ChartStack * chart, gdouble rate, gchar *symbol) +{ + g_return_if_fail (GTK_IS_CHARTSTACK (chart)); + + chart->minor_rate = rate; + chart->minor_symbol = symbol; +}*/ + +void ui_chart_stack_set_currency(ChartStack * chart, guint32 kcur) +{ + g_return_if_fail (GTK_IS_CHARTSTACK (chart)); + + chart->kcur = kcur; +} +