diff options
Diffstat (limited to 'baobab/src/baobab-chart.c')
-rw-r--r-- | baobab/src/baobab-chart.c | 1876 |
1 files changed, 1876 insertions, 0 deletions
diff --git a/baobab/src/baobab-chart.c b/baobab/src/baobab-chart.c new file mode 100644 index 00000000..dce8bfb6 --- /dev/null +++ b/baobab/src/baobab-chart.c @@ -0,0 +1,1876 @@ +/* + * baobab-chart.c + * + * Copyright (C) 2006, 2007, 2008 Igalia + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * Authors: + * Felipe Erias <[email protected]> + * Pablo Santamaria <[email protected]> + * Jacobo Aragunde <[email protected]> + * Eduardo Lima <[email protected]> + * Mario Sanchez <[email protected]> + * Miguel Gomez <[email protected]> + * Henrique Ferreiro <[email protected]> + * Alejandro Pinheiro <[email protected]> + * Carlos Sanmartin <[email protected]> + * Alejandro Garcia <[email protected]> + */ + +#include <config.h> +#include <gtk/gtk.h> +#include <glib/gi18n.h> + +#include "baobab-chart.h" + +#define BAOBAB_CHART_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \ + BAOBAB_CHART_TYPE, \ + BaobabChartPrivate)) + +#define SNAPSHOT_DEF_FILENAME_FORMAT "%s-disk-usage" + +G_DEFINE_ABSTRACT_TYPE (BaobabChart, baobab_chart, GTK_TYPE_WIDGET); + +#define BAOBAB_CHART_MAX_DEPTH 8 +#define BAOBAB_CHART_MIN_DEPTH 1 + +enum +{ + LEFT_BUTTON = 1, + MIDDLE_BUTTON = 2, + RIGHT_BUTTON = 3 +}; + +struct _BaobabChartPrivate +{ + guint name_column; + guint size_column; + guint info_column; + guint percentage_column; + guint valid_column; + gboolean button_pressed; + gboolean is_frozen; + cairo_surface_t *memento; + + guint max_depth; + gboolean model_changed; + + GtkTreeModel *model; + GtkTreeRowReference *root; + + GList *first_item; + GList *last_item; + GList *highlighted_item; +}; + +/* Signals */ +enum +{ + ITEM_ACTIVATED, + LAST_SIGNAL +}; + +static guint baobab_chart_signals [LAST_SIGNAL] = { 0 }; + +/* Properties */ +enum +{ + PROP_0, + PROP_MAX_DEPTH, + PROP_MODEL, + PROP_ROOT, +}; + +/* Colors */ +const BaobabChartColor baobab_chart_tango_colors[] = {{0.94, 0.16, 0.16}, /* tango: ef2929 */ + {0.68, 0.49, 0.66}, /* tango: ad7fa8 */ + {0.45, 0.62, 0.82}, /* tango: 729fcf */ + {0.54, 0.89, 0.20}, /* tango: 8ae234 */ + {0.91, 0.73, 0.43}, /* tango: e9b96e */ + {0.99, 0.68, 0.25}}; /* tango: fcaf3e */ + +static void baobab_chart_class_init (BaobabChartClass *class); +static void baobab_chart_init (BaobabChart *object); +static void baobab_chart_realize (GtkWidget *widget); +static void baobab_chart_dispose (GObject *object); +static void baobab_chart_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); +static void baobab_chart_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void baobab_chart_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static void baobab_chart_free_items (GtkWidget *chart); +static void baobab_chart_draw (GtkWidget *chart, + cairo_t *cr, + GdkRectangle area); +static void baobab_chart_update_draw (BaobabChart *chart, + GtkTreePath *path); +static void baobab_chart_row_changed (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data); +static void baobab_chart_row_inserted (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data); +static void baobab_chart_row_has_child_toggled (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data); +static void baobab_chart_row_deleted (GtkTreeModel *model, + GtkTreePath *path, + gpointer data); +static void baobab_chart_rows_reordered (GtkTreeModel *model, + GtkTreePath *parent, + GtkTreeIter *iter, + gint *new_order, + gpointer data); +static gboolean baobab_chart_expose (GtkWidget *chart, + GdkEventExpose *event); +static void baobab_chart_interpolate_colors (BaobabChartColor *color, + BaobabChartColor colora, + BaobabChartColor colorb, + gdouble percentage); +static gint baobab_chart_button_release (GtkWidget *widget, + GdkEventButton *event); +static gint baobab_chart_scroll (GtkWidget *widget, + GdkEventScroll *event); +static gint baobab_chart_motion_notify (GtkWidget *widget, + GdkEventMotion *event); +static gint baobab_chart_leave_notify (GtkWidget *widget, + GdkEventCrossing *event); +static inline void baobab_chart_disconnect_signals (GtkWidget *chart, + GtkTreeModel *model); +static inline void baobab_chart_connect_signals (GtkWidget *chart, + GtkTreeModel *model); +static void baobab_chart_get_items (GtkWidget *chart, GtkTreePath *root); +static gboolean baobab_chart_query_tooltip (GtkWidget *widget, + gint x, + gint y, + gboolean keyboard_mode, + GtkTooltip *tooltip, + gpointer user_data); + + +static void +baobab_chart_class_init (BaobabChartClass *class) +{ + GObjectClass *obj_class; + GtkWidgetClass *widget_class; + + obj_class = G_OBJECT_CLASS (class); + widget_class = GTK_WIDGET_CLASS (class); + + /* GtkObject signals */ + obj_class->set_property = baobab_chart_set_property; + obj_class->get_property = baobab_chart_get_property; + obj_class->dispose = baobab_chart_dispose; + + /* GtkWidget signals */ + widget_class->realize = baobab_chart_realize; + widget_class->expose_event = baobab_chart_expose; + widget_class->size_allocate = baobab_chart_size_allocate; + widget_class->scroll_event = baobab_chart_scroll; + + /* Baobab Chart abstract methods */ + class->draw_item = NULL; + class->pre_draw = NULL; + class->post_draw = NULL; + class->calculate_item_geometry = NULL; + class->is_point_over_item = NULL; + class->get_item_rectangle = NULL; + class->can_zoom_in = NULL; + class->can_zoom_out = NULL; + + g_object_class_install_property (obj_class, + PROP_MAX_DEPTH, + g_param_spec_int ("max-depth", + _("Maximum depth"), + _("The maximum depth drawn in the chart from the root"), + 1, + BAOBAB_CHART_MAX_DEPTH, + BAOBAB_CHART_MAX_DEPTH, + G_PARAM_READWRITE)); + + g_object_class_install_property (obj_class, + PROP_MODEL, + g_param_spec_object ("model", + _("Chart model"), + _("Set the model of the chart"), + GTK_TYPE_TREE_MODEL, + G_PARAM_READWRITE)); + + g_object_class_install_property (obj_class, + PROP_ROOT, + g_param_spec_boxed ("root", + _("Chart root node"), + _("Set the root node from the model"), + GTK_TYPE_TREE_ITER, + G_PARAM_READWRITE)); + + baobab_chart_signals[ITEM_ACTIVATED] = + g_signal_new ("item_activated", + G_TYPE_FROM_CLASS (obj_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (BaobabChartClass, item_activated), + NULL, NULL, + g_cclosure_marshal_VOID__BOXED, + G_TYPE_NONE, 1, + GTK_TYPE_TREE_ITER); + + g_type_class_add_private (obj_class, sizeof (BaobabChartPrivate)); +} + +static void +baobab_chart_init (BaobabChart *chart) +{ + BaobabChartPrivate *priv; + + priv = BAOBAB_CHART_GET_PRIVATE (chart); + chart->priv = priv; + + priv->model = NULL; + priv->max_depth = BAOBAB_CHART_MAX_DEPTH; + priv->name_column = 0; + priv->size_column = 0; + priv->info_column = 0; + priv->percentage_column = 0; + priv->valid_column = 0; + priv->button_pressed = FALSE; + priv->is_frozen = FALSE; + priv->memento = NULL; + priv->root = NULL; + + priv->first_item = NULL; + priv->last_item = NULL; + priv->highlighted_item = NULL; +} + +static void +baobab_chart_dispose (GObject *object) +{ + BaobabChartPrivate *priv; + + baobab_chart_free_items (GTK_WIDGET (object)); + + priv = BAOBAB_CHART (object)->priv; + + if (priv->model) + { + baobab_chart_disconnect_signals (GTK_WIDGET (object), + priv->model); + + g_object_unref (priv->model); + + priv->model = NULL; + } + + if (priv->root) + { + gtk_tree_row_reference_free (priv->root); + + priv->root = NULL; + } + + G_OBJECT_CLASS (baobab_chart_parent_class)->dispose (object); +} + +static void +baobab_chart_realize (GtkWidget *widget) +{ + BaobabChart *chart; + GdkWindowAttr attributes; + gint attributes_mask; + GtkAllocation allocation; + GdkWindow *window; + + g_return_if_fail (BAOBAB_IS_CHART (widget)); + + chart = BAOBAB_CHART (widget); + gtk_widget_set_realized (widget, TRUE); + + gtk_widget_get_allocation (widget, &allocation); + + attributes.window_type = GDK_WINDOW_CHILD; + attributes.x = allocation.x; + attributes.y = allocation.y; + attributes.width = allocation.width; + attributes.height = allocation.height; + attributes.wclass = GDK_INPUT_OUTPUT; + attributes.visual = gtk_widget_get_visual (widget); + attributes.colormap = gtk_widget_get_colormap (widget); + attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK; + + attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; + + window = gdk_window_new (gtk_widget_get_parent_window (widget), + &attributes, + attributes_mask); + gtk_widget_set_window (widget, window); + gdk_window_set_user_data (window, chart); + + gtk_widget_style_attach (widget); + gtk_style_set_background (gtk_widget_get_style (widget), + window, + GTK_STATE_NORMAL); + + gtk_widget_add_events (widget, + GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | + GDK_POINTER_MOTION_HINT_MASK | GDK_LEAVE_NOTIFY_MASK); +} + +static void +baobab_chart_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + BaobabChartPrivate *priv; + BaobabChartClass *class; + BaobabChartItem *item; + GList *node; + + g_return_if_fail (BAOBAB_IS_CHART (widget)); + g_return_if_fail (allocation != NULL); + + priv = BAOBAB_CHART (widget)->priv; + class = BAOBAB_CHART_GET_CLASS (widget); + + gtk_widget_set_allocation (widget, allocation); + + if (gtk_widget_get_realized (widget)) + { + gdk_window_move_resize (gtk_widget_get_window (widget), + allocation->x, allocation->y, + allocation->width, allocation->height); + + node = priv->first_item; + while (node != NULL) + { + item = (BaobabChartItem *) node->data; + item->has_visible_children = FALSE; + item->visible = FALSE; + class->calculate_item_geometry (widget, item); + + node = node->next; + } + } +} + +static void +baobab_chart_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + BaobabChart *chart; + + chart = BAOBAB_CHART (object); + + switch (prop_id) + { + case PROP_MAX_DEPTH: + baobab_chart_set_max_depth (GTK_WIDGET (chart), g_value_get_int (value)); + break; + case PROP_MODEL: + baobab_chart_set_model (GTK_WIDGET (chart), g_value_get_object (value)); + break; + case PROP_ROOT: + baobab_chart_set_root (GTK_WIDGET (chart), g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +baobab_chart_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + BaobabChartPrivate *priv; + + priv = BAOBAB_CHART (object)->priv; + + switch (prop_id) + { + case PROP_MAX_DEPTH: + g_value_set_int (value, priv->max_depth); + break; + case PROP_MODEL: + g_value_set_object (value, priv->model); + break; + case PROP_ROOT: + g_value_set_object (value, priv->root); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GList +*baobab_chart_add_item (GtkWidget *chart, + guint depth, + gdouble rel_start, + gdouble rel_size, + GtkTreeIter iter) +{ + BaobabChartPrivate *priv; + BaobabChartItem *item; + + gchar *name; + gchar *size; + + priv = BAOBAB_CHART_GET_PRIVATE (chart); + + gtk_tree_model_get (priv->model, &iter, + priv->name_column, &name, -1); + gtk_tree_model_get (priv->model, &iter, + priv->size_column, &size, -1); + + item = g_new (BaobabChartItem, 1); + item->name = name; + item->size = size; + item->depth = depth; + item->rel_start = rel_start; + item->rel_size = rel_size; + item->has_any_child = FALSE; + item->visible = FALSE; + item->has_visible_children = FALSE; + + item->iter = iter; + + item->parent = NULL; + item->data = NULL; + + priv->last_item = g_list_prepend (priv->last_item, item); + + return priv->last_item; +} + +static void +baobab_chart_free_items (GtkWidget *chart) +{ + BaobabChartPrivate *priv; + BaobabChartItem *item; + GList *node; + GList *next; + + priv = BAOBAB_CHART_GET_PRIVATE (chart); + + node = priv->first_item; + while (node != NULL) + { + next = node->next; + + item = (BaobabChartItem *) node->data; + + g_free (item->name); + g_free (item->size); + + g_free (item->data); + item->data = NULL; + + g_free (item); + g_list_free_1 (node); + + node = next; + } + + priv->first_item = NULL; + priv->last_item = NULL; + priv->highlighted_item = NULL; +} + +static void +baobab_chart_get_items (GtkWidget *chart, GtkTreePath *root) +{ + BaobabChartPrivate *priv; + BaobabChartItem *item; + + GList *node; + GtkTreeIter initial_iter = {0}; + gdouble size; + GtkTreePath *model_root_path; + GtkTreeIter model_root_iter; + + BaobabChartClass *class; + GtkTreeIter child_iter = {0}; + GList *child_node; + BaobabChartItem *child; + gdouble rel_start; + + priv = BAOBAB_CHART_GET_PRIVATE (chart); + + /* First we free current item list */ + baobab_chart_free_items (chart); + + /* Get the tree iteration corresponding to root */ + if (!gtk_tree_model_get_iter (priv->model, &initial_iter, root)) + { + priv->model_changed = FALSE; + return; + } + + + + model_root_path = gtk_tree_path_new_first (); + gtk_tree_model_get_iter (priv->model, &model_root_iter, model_root_path); + gtk_tree_path_free (model_root_path); + + gtk_tree_model_get (priv->model, &model_root_iter, + priv->percentage_column, &size, -1); + + /* Create first item */ + node = baobab_chart_add_item (chart, 0, 0, 100, initial_iter); + + /* Iterate through childs building the list */ + class = BAOBAB_CHART_GET_CLASS (chart); + + do + { + item = (BaobabChartItem *) node->data; + item->has_any_child = gtk_tree_model_iter_children (priv->model, + &child_iter, + &(item->iter)); + + /* Calculate item geometry */ + class->calculate_item_geometry (chart, item); + + if (! item->visible) + { + node = node->prev; + continue; + } + + /* Get item's children and add them to the list */ + if ((item->has_any_child) && (item->depth < priv->max_depth + 1)) + { + rel_start = 0; + + do + { + gtk_tree_model_get (priv->model, &child_iter, + priv->percentage_column, &size, -1); + + child_node = baobab_chart_add_item (chart, + item->depth + 1, + rel_start, + size, + child_iter); + child = (BaobabChartItem *) child_node->data; + child->parent = node; + rel_start += size; + } + while (gtk_tree_model_iter_next (priv->model, &child_iter)); + } + + node = node->prev; + } + while (node != NULL); + + /* Reverse the list, 'cause we created it from the tail, for efficiency reasons */ + priv->first_item = g_list_reverse (priv->last_item); + + priv->model_changed = FALSE; +} + +static void +baobab_chart_draw (GtkWidget *chart, + cairo_t *cr, + GdkRectangle area) +{ + BaobabChartPrivate *priv; + BaobabChartClass *class; + + GList *node; + BaobabChartItem *item; + gboolean highlighted; + + priv = BAOBAB_CHART_GET_PRIVATE (chart); + class = BAOBAB_CHART_GET_CLASS (chart); + + /* call pre-draw abstract method */ + if (class->pre_draw) + class->pre_draw (chart, cr); + + cairo_save (cr); + + node = priv->first_item; + while (node != NULL) + { + item = (BaobabChartItem *) node->data; + + if ((item->visible) && (gdk_rectangle_intersect (&area, &item->rect, NULL)) + && (item->depth <= priv->max_depth)) + { + highlighted = (node == priv->highlighted_item); + + class->draw_item (chart, cr, item, highlighted); + } + + node = node->next; + } + + cairo_restore (cr); + + /* call post-draw abstract method */ + if (class->post_draw) + class->post_draw (chart, cr); +} + +static void +baobab_chart_update_draw (BaobabChart* chart, + GtkTreePath *path) +{ + BaobabChartPrivate *priv; + GtkTreePath *root_path = NULL; + gint root_depth, node_depth; + + if (!gtk_widget_get_realized ( GTK_WIDGET (chart))) + return; + + priv = BAOBAB_CHART (chart)->priv; + + if (priv->root != NULL) + { + root_path = gtk_tree_row_reference_get_path (priv->root); + + if (root_path == NULL) + { + gtk_tree_row_reference_free (priv->root); + priv->root = NULL; + } + } + + if (priv->root == NULL) + root_path = gtk_tree_path_new_first (); + + + root_depth = gtk_tree_path_get_depth (root_path); + node_depth = gtk_tree_path_get_depth (path); + + if (((node_depth-root_depth)<=priv->max_depth)&& + ((gtk_tree_path_is_ancestor (root_path, path))|| + (gtk_tree_path_compare (root_path, path) == 0))) + { + gtk_widget_queue_draw (GTK_WIDGET (chart)); + } + + gtk_tree_path_free (root_path); +} + +static void +baobab_chart_row_changed (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + g_return_if_fail (BAOBAB_IS_CHART (data)); + g_return_if_fail (path != NULL || iter != NULL); + + BAOBAB_CHART_GET_PRIVATE (data)->model_changed = TRUE; + + baobab_chart_update_draw (BAOBAB_CHART (data), path); +} + +static void +baobab_chart_row_inserted (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + g_return_if_fail (BAOBAB_IS_CHART (data)); + g_return_if_fail (path != NULL || iter != NULL); + + BAOBAB_CHART_GET_PRIVATE (data)->model_changed = TRUE; + + baobab_chart_update_draw (BAOBAB_CHART (data), path); +} + +static void +baobab_chart_row_has_child_toggled (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + g_return_if_fail (BAOBAB_IS_CHART (data)); + g_return_if_fail (path != NULL || iter != NULL); + + BAOBAB_CHART_GET_PRIVATE (data)->model_changed = TRUE; + + baobab_chart_update_draw (BAOBAB_CHART (data), path); +} + +static void +baobab_chart_row_deleted (GtkTreeModel *model, + GtkTreePath *path, + gpointer data) +{ + g_return_if_fail (BAOBAB_IS_CHART (data)); + g_return_if_fail (path != NULL); + + BAOBAB_CHART_GET_PRIVATE (data)->model_changed = TRUE; + + baobab_chart_update_draw (BAOBAB_CHART (data), path); + +} + +static void +baobab_chart_rows_reordered (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gint *new_order, + gpointer data) +{ + g_return_if_fail (BAOBAB_IS_CHART (data)); + g_return_if_fail (path != NULL || iter != NULL); + + BAOBAB_CHART_GET_PRIVATE (data)->model_changed = TRUE; + + baobab_chart_update_draw (BAOBAB_CHART (data), path); + +} + +static gboolean +baobab_chart_expose (GtkWidget *chart, GdkEventExpose *event) +{ + cairo_t *cr; + BaobabChartPrivate *priv; + gint w, h; + gdouble p, sx, sy; + GtkTreePath *root_path = NULL; + GtkTreePath *current_path = NULL; + GtkAllocation allocation; + + priv = BAOBAB_CHART (chart)->priv; + + /* the columns are not set we paint nothing */ + if (priv->name_column == priv->percentage_column) + return FALSE; + + /* get a cairo_t */ + cr = gdk_cairo_create (gtk_widget_get_window (chart)); + + cairo_rectangle (cr, + event->area.x, event->area.y, + event->area.width, event->area.height); + + /* there is no model we can not paint */ + if ((priv->is_frozen) || (priv->model == NULL)) + { + if (priv->memento != NULL) + { + w = cairo_image_surface_get_width (priv->memento); + h = cairo_image_surface_get_height (priv->memento); + + cairo_clip (cr); + + gtk_widget_get_allocation (GTK_WIDGET (chart), &allocation); + if (w > 0 && h > 0 && + !(allocation.width == w && allocation.height == h)) + { + /* minimal available proportion */ + p = MIN (allocation.width / (1.0 * w), + allocation.height / (1.0 * h)); + + sx = (gdouble) (allocation.width - w * p) / 2.0; + sy = (gdouble) (allocation.height - h * p) / 2.0; + + cairo_translate (cr, sx, sy); + cairo_scale (cr, p, p); + } + + cairo_set_source_surface (cr, + priv->memento, + 0, 0); + cairo_paint (cr); + } + } + else + { + cairo_set_source_rgb (cr, 1, 1, 1); + cairo_fill_preserve (cr); + + cairo_clip (cr); + + if (priv->root != NULL) + root_path = gtk_tree_row_reference_get_path (priv->root); + + if (root_path == NULL) { + root_path = gtk_tree_path_new_first (); + priv->root = NULL; + } + + /* Check if tree model was modified in any way */ + if ((priv->model_changed) || + (priv->first_item == NULL)) + baobab_chart_get_items (chart, root_path); + else + { + /* Check if root was changed */ + current_path = gtk_tree_model_get_path (priv->model, + &((BaobabChartItem*) priv->first_item->data)->iter); + + if (gtk_tree_path_compare (root_path, current_path) != 0) + baobab_chart_get_items (chart, root_path); + + gtk_tree_path_free (current_path); + } + + gtk_tree_path_free (root_path); + + baobab_chart_draw (chart, cr, event->area); + } + + cairo_destroy (cr); + + return FALSE; +} + +static void +baobab_chart_interpolate_colors (BaobabChartColor *color, + BaobabChartColor colora, + BaobabChartColor colorb, + gdouble percentage) +{ + gdouble diff; + + diff = colora.red - colorb.red; + color->red = colora.red-diff*percentage; + + diff = colora.green - colorb.green; + color->green = colora.green-diff*percentage; + + diff = colora.blue - colorb.blue; + color->blue = colora.blue-diff*percentage; +} + +void +baobab_chart_get_item_color (BaobabChartColor *color, + gdouble rel_position, + gint depth, + gboolean highlighted) +{ + gdouble intensity; + gint color_number; + gint next_color_number; + gdouble maximum; + static const BaobabChartColor level_color = {0.83, 0.84, 0.82}; + static const BaobabChartColor level_color_hl = {0.88, 0.89, 0.87}; + + intensity = 1 - (((depth-1)*0.3) / BAOBAB_CHART_MAX_DEPTH); + + if (depth == 0) + *color = level_color; + else + { + color_number = rel_position / (100/3); + next_color_number = (color_number + 1) % 6; + + baobab_chart_interpolate_colors (color, + baobab_chart_tango_colors[color_number], + baobab_chart_tango_colors[next_color_number], + (rel_position - color_number * 100/3) / (100/3)); + color->red = color->red * intensity; + color->green = color->green * intensity; + color->blue = color->blue * intensity; + } + + if (highlighted) + { + if (depth == 0) + *color = level_color_hl; + else + { + maximum = MAX (color->red, + MAX (color->green, + color->blue)); + color->red /= maximum; + color->green /= maximum; + color->blue /= maximum; + } + } +} + +static gint +baobab_chart_button_release (GtkWidget *widget, + GdkEventButton *event) +{ + BaobabChartPrivate *priv; + + priv = BAOBAB_CHART (widget)->priv; + + if (priv->is_frozen) + return TRUE; + + switch (event->button) + { + case LEFT_BUTTON: + /* Enter into a subdir */ + if (priv->highlighted_item != NULL) + g_signal_emit (BAOBAB_CHART (widget), + baobab_chart_signals[ITEM_ACTIVATED], + 0, &((BaobabChartItem*) priv->highlighted_item->data)->iter); + + break; + + case MIDDLE_BUTTON: + /* Go back to the parent dir */ + baobab_chart_move_up_root (widget); + break; + } + + return FALSE; +} + +static gint +baobab_chart_scroll (GtkWidget *widget, + GdkEventScroll *event) +{ + switch (event->direction) + { + case GDK_SCROLL_LEFT : + case GDK_SCROLL_UP : + if (baobab_chart_can_zoom_out (widget)) + baobab_chart_zoom_out (widget); + /* change the selected item when zooming */ + baobab_chart_motion_notify (widget, (GdkEventMotion *)event); + break; + + case GDK_SCROLL_RIGHT : + case GDK_SCROLL_DOWN : + if (baobab_chart_can_zoom_in (widget)) + baobab_chart_zoom_in (widget); + break; + } + + return FALSE; +} + +static void +baobab_chart_set_item_highlight (GtkWidget *chart, + GList *node, + gboolean highlighted) +{ + BaobabChartItem *item; + BaobabChartPrivate *priv; + BaobabChartClass *class; + + if (node == NULL) + return; + + item = (BaobabChartItem *) node->data; + priv = BAOBAB_CHART_GET_PRIVATE (chart); + class = BAOBAB_CHART_GET_CLASS (chart); + + if (highlighted) + priv->highlighted_item = node; + else + priv->highlighted_item = NULL; + + gdk_window_invalidate_rect (gtk_widget_get_window ( GTK_WIDGET (chart)), + &item->rect, TRUE); +} + +static gint +baobab_chart_motion_notify (GtkWidget *widget, + GdkEventMotion *event) +{ + BaobabChartPrivate *priv; + BaobabChartClass *class; + GList *node; + BaobabChartItem *item; + gboolean found = FALSE; + + priv = BAOBAB_CHART_GET_PRIVATE (widget); + class = BAOBAB_CHART_GET_CLASS (widget); + + /* Check if the pointer is over an item */ + node = priv->last_item; + while (node != NULL) + { + item = (BaobabChartItem *) node->data; + + if ((item->visible) && (class->is_point_over_item (widget, item, event->x, event->y))) + { + if (priv->highlighted_item != node) + { + baobab_chart_set_item_highlight (widget, priv->highlighted_item, FALSE); + + gtk_widget_set_has_tooltip (widget, TRUE); + baobab_chart_set_item_highlight (widget, node, TRUE); + } + + found = TRUE; + break; + } + node = node->prev; + } + + /* If we never found a highlighted item, but there is an old highlighted item, + redraw it to turn it off */ + if (! found) + { + baobab_chart_set_item_highlight (widget, priv->highlighted_item, FALSE); + gtk_widget_set_has_tooltip (widget, FALSE); + } + + /* Continue receiving motion notifies */ + gdk_event_request_motions (event); + + return FALSE; +} + +static gint +baobab_chart_leave_notify (GtkWidget *widget, + GdkEventCrossing *event) +{ + BaobabChartPrivate *priv; + + priv = BAOBAB_CHART_GET_PRIVATE (widget); + baobab_chart_set_item_highlight (widget, priv->highlighted_item, FALSE); + + return FALSE; +} + +static inline void +baobab_chart_connect_signals (GtkWidget *chart, + GtkTreeModel *model) +{ + g_signal_connect (model, + "row_changed", + G_CALLBACK (baobab_chart_row_changed), + chart); + g_signal_connect (model, + "row_inserted", + G_CALLBACK (baobab_chart_row_inserted), + chart); + g_signal_connect (model, + "row_has_child_toggled", + G_CALLBACK (baobab_chart_row_has_child_toggled), + chart); + g_signal_connect (model, + "row_deleted", + G_CALLBACK (baobab_chart_row_deleted), + chart); + g_signal_connect (model, + "rows_reordered", + G_CALLBACK (baobab_chart_rows_reordered), + chart); + g_signal_connect (chart, + "query-tooltip", + G_CALLBACK (baobab_chart_query_tooltip), + chart); + g_signal_connect (chart, + "motion-notify-event", + G_CALLBACK (baobab_chart_motion_notify), + chart); + g_signal_connect (chart, + "leave-notify-event", + G_CALLBACK (baobab_chart_leave_notify), + chart); + g_signal_connect (chart, + "button-release-event", + G_CALLBACK (baobab_chart_button_release), + chart); +} + +static inline void +baobab_chart_disconnect_signals (GtkWidget *chart, + GtkTreeModel *model) +{ + g_signal_handlers_disconnect_by_func (model, + baobab_chart_row_changed, + chart); + g_signal_handlers_disconnect_by_func (model, + baobab_chart_row_inserted, + chart); + g_signal_handlers_disconnect_by_func (model, + baobab_chart_row_has_child_toggled, + chart); + g_signal_handlers_disconnect_by_func (model, + baobab_chart_row_deleted, + chart); + g_signal_handlers_disconnect_by_func (model, + baobab_chart_rows_reordered, + chart); + g_signal_handlers_disconnect_by_func (chart, + baobab_chart_query_tooltip, + chart); + g_signal_handlers_disconnect_by_func (chart, + baobab_chart_motion_notify, + chart); + g_signal_handlers_disconnect_by_func (chart, + baobab_chart_leave_notify, + chart); + g_signal_handlers_disconnect_by_func (chart, + baobab_chart_button_release, + chart); +} + +static gboolean +baobab_chart_query_tooltip (GtkWidget *widget, + gint x, + gint y, + gboolean keyboard_mode, + GtkTooltip *tooltip, + gpointer user_data) +{ + BaobabChartPrivate *priv; + BaobabChartItem *item; + char *markup; + + priv = BAOBAB_CHART_GET_PRIVATE (widget); + + if (priv->highlighted_item == NULL) + return FALSE; + + item = (BaobabChartItem *) priv->highlighted_item->data; + + if ( (item->name == NULL) || (item->size == NULL) ) + return FALSE; + + gtk_tooltip_set_tip_area (tooltip, &item->rect); + + markup = g_strconcat (item->name, + "\n", + item->size, + NULL); + gtk_tooltip_set_markup (tooltip, markup); + g_free (markup); + + return TRUE; +} + +GdkPixbuf* +baobab_chart_get_pixbuf (GtkWidget *widget) +{ + gint w, h; + GdkPixbuf *pixbuf; + + g_return_val_if_fail (BAOBAB_IS_CHART (widget), NULL); + + #if GTK_CHECK_VERSION(3, 0, 0) + w = gdk_window_get_width(gtk_widget_get_window(widget)); + h = gdk_window_get_height(gtk_widget_get_window(widget)); + #else + gdk_drawable_get_size(gtk_widget_get_window(widget), &w, &h); + #endif + + pixbuf = gdk_pixbuf_get_from_drawable (NULL, + gtk_widget_get_window (widget), + gdk_colormap_get_system (), + 0, 0, + 0, 0, + w, h); + + return pixbuf; +} + +/* Public functions start here */ + +/** + * baobab_chart_new: + * + * Constructor for the baobab_chart class + * + * Returns: a new #BaobabChart object + * + **/ +GtkWidget * +baobab_chart_new () +{ + return g_object_new (BAOBAB_CHART_TYPE, NULL); +} + +/** + * baobab_chart_set_model_with_columns: + * @chart: the #BaobabChart whose model is going to be set + * @model: the #GtkTreeModel which is going to set as the model of + * @chart + * @name_column: number of column inside @model where the file name is + * stored + * @size_column: number of column inside @model where the file size is + * stored + * @info_column: number of column inside @model where the percentage + * of disk usage is stored + * @percentage_column: number of column inside @model where the disk + * usage percentage is stored + * @valid_column: number of column inside @model where the flag indicating + * if the row data is right or not. + * @root: a #GtkTreePath indicating the node of @model which will be + * used as root. + * + * Sets @model as the #GtkTreeModel used by @chart. Indicates the + * columns inside @model where the values file name, file + * size, file information, disk usage percentage and data correction are stored, and + * the node which will be used as the root of @chart. Once + * the model has been successfully set, a redraw of the window is + * forced. + * This function is intended to be used the first time a #GtkTreeModel + * is assigned to @chart, or when the columns containing the needed data + * are going to change. In other cases, #baobab_chart_set_model should + * be used. + * This function does not change the state of the signals from the model, which + * is controlled by he #baobab_chart_freeze_updates and the + * #baobab_chart_thaw_updates functions. + * + * Fails if @chart is not a #BaobabChart or if @model is not a + * #GtkTreeModel. + **/ +void +baobab_chart_set_model_with_columns (GtkWidget *chart, + GtkTreeModel *model, + guint name_column, + guint size_column, + guint info_column, + guint percentage_column, + guint valid_column, + GtkTreePath *root) +{ + BaobabChartPrivate *priv; + + g_return_if_fail (BAOBAB_IS_CHART (chart)); + g_return_if_fail (GTK_IS_TREE_MODEL (model)); + + priv = BAOBAB_CHART (chart)->priv; + + baobab_chart_set_model (chart, model); + + if (root != NULL) + { + priv->root = gtk_tree_row_reference_new (model, root); + g_object_notify (G_OBJECT (chart), "root"); + } + + priv->name_column = name_column; + priv->size_column = size_column; + priv->info_column = info_column; + priv->percentage_column = percentage_column; + priv->valid_column = valid_column; +} + +/** + * baobab_chart_set_model: + * @chart: the #BaobabChart whose model is going to be set + * @model: the #GtkTreeModel which is going to set as the model of + * @chart + * + * Sets @model as the #GtkTreeModel used by @chart, and takes the needed + * data from the columns especified in the last call to + * #baobab_chart_set_model_with_colums. + * This function does not change the state of the signals from the model, which + * is controlled by he #baobab_chart_freeze_updates and the + * #baobab_chart_thaw_updates functions. + * + * Fails if @chart is not a #BaobabChart or if @model is not a + * #GtkTreeModel. + **/ +void +baobab_chart_set_model (GtkWidget *chart, + GtkTreeModel *model) +{ + BaobabChartPrivate *priv; + + g_return_if_fail (BAOBAB_IS_CHART (chart)); + g_return_if_fail (GTK_IS_TREE_MODEL (model)); + + priv = BAOBAB_CHART (chart)->priv; + + if (model == priv->model) + return; + + if (priv->model) + { + if (! priv->is_frozen) + baobab_chart_disconnect_signals (chart, + priv->model); + g_object_unref (priv->model); + } + + priv->model = model; + g_object_ref (priv->model); + + if (! priv->is_frozen) + baobab_chart_connect_signals (chart, + priv->model); + + if (priv->root) + gtk_tree_row_reference_free (priv->root); + + priv->root = NULL; + + g_object_notify (G_OBJECT (chart), "model"); + + gtk_widget_queue_draw (chart); +} + +/** + * baobab_chart_get_model: + * @chart: a #BaobabChart whose model will be returned. + * + * Returns the #GtkTreeModel which is the model used by @chart. + * + * Returns: %NULL if @chart is not a #BaobabChart. + **/ +GtkTreeModel * +baobab_chart_get_model (GtkWidget *chart) +{ + g_return_val_if_fail (BAOBAB_IS_CHART (chart), NULL); + + return BAOBAB_CHART (chart)->priv->model; +} + +/** + * baobab_chart_set_max_depth: + * @chart: a #BaobabChart + * @max_depth: the new maximum depth to show in the widget. + * + * Sets the maximum number of nested levels that are going to be show in the + * wigdet, and causes a redraw of the widget to show the new maximum + * depth. If max_depth is < 1 MAX_DRAWABLE_DEPTH is used. + * + * Fails if @chart is not a #BaobabChart. + **/ +void +baobab_chart_set_max_depth (GtkWidget *chart, + guint max_depth) +{ + BaobabChartPrivate *priv; + + g_return_if_fail (BAOBAB_IS_CHART (chart)); + + priv = BAOBAB_CHART_GET_PRIVATE (chart); + + max_depth = MIN (max_depth, BAOBAB_CHART_MAX_DEPTH); + max_depth = MAX (max_depth, BAOBAB_CHART_MIN_DEPTH); + + if (max_depth == priv->max_depth) + return; + + priv->max_depth = max_depth; + g_object_notify (G_OBJECT (chart), "max-depth"); + + priv->model_changed = TRUE; + + gtk_widget_queue_draw (chart); +} + +/** + * baobab_chart_get_max_depth: + * @chart: a #BaobabChart. + * + * Returns the maximum number of levels that will be show in the + * widget. + * + * Fails if @chart is not a #BaobabChart. + **/ +guint +baobab_chart_get_max_depth (GtkWidget *chart) +{ + g_return_val_if_fail (BAOBAB_IS_CHART (chart), 0); + + return BAOBAB_CHART (chart)->priv->max_depth; +} + +/** + * baobab_chart_set_root: + * @chart: a #BaobabChart + * @root: a #GtkTreePath indicating the node which will be used as + * the widget root. + * + * Sets the node pointed by @root as the new root of the widget + * @chart. + * + * Fails if @chart is not a #BaobabChart or if @chart has not + * a #GtkTreeModel set. + **/ +void +baobab_chart_set_root (GtkWidget *chart, + GtkTreePath *root) +{ + BaobabChartPrivate *priv; + GtkTreePath *current_root; + + g_return_if_fail (BAOBAB_IS_CHART (chart)); + + priv = BAOBAB_CHART (chart)->priv; + + g_return_if_fail (priv->model != NULL); + + if (priv->root) { + /* Check that given root is different from current */ + current_root = gtk_tree_row_reference_get_path (priv->root); + if ( (current_root) && (gtk_tree_path_compare (current_root, root) == 0) ) + return; + + /* Free current root */ + gtk_tree_row_reference_free (priv->root); + } + + priv->root = gtk_tree_row_reference_new (priv->model, root); + + g_object_notify (G_OBJECT (chart), "root"); + + gtk_widget_queue_draw (chart); +} + +/** + * baobab_chart_get_root: + * @chart: a #BaobabChart. + * + * Returns a #GtkTreePath pointing to the root of the widget. The + * programmer has the responsability to free the used memory once + * finished with the returned value. It returns NULL if there is no + * root node defined + * + * Fails if @chart is not a #BaobabChart. + **/ +GtkTreePath* +baobab_chart_get_root (GtkWidget *chart) +{ + g_return_val_if_fail (BAOBAB_IS_CHART (chart), NULL); + + if (BAOBAB_CHART (chart)->priv->root) + return gtk_tree_row_reference_get_path (BAOBAB_CHART (chart)->priv->root); + else + return NULL; +} + +/** + * baobab_chart_freeze_updates: + * @chart: the #BaobabChart whose model signals are going to be frozen. + * + * Disconnects @chart from the signals emitted by its model, and sets + * the window of @chart to a "processing" state, so that the window + * ignores changes in the chart's model and mouse events. + * In order to connect again the window to the model, a call to + * #baobab_chart_thaw_updates must be done. + * + * Fails if @chart is not a #BaobabChart. + **/ +void +baobab_chart_freeze_updates (GtkWidget *chart) +{ + BaobabChartPrivate *priv; + cairo_surface_t *surface = NULL; + cairo_t *cr = NULL; + GdkRectangle area; + GtkAllocation allocation; + + g_return_if_fail (BAOBAB_IS_CHART (chart)); + + priv = BAOBAB_CHART_GET_PRIVATE (chart); + + if (priv->is_frozen) + return; + + if (priv->model) + baobab_chart_disconnect_signals (chart, + priv->model); + + gtk_widget_get_allocation (GTK_WIDGET (chart), &allocation); + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, + allocation.width, + allocation.height); + + if (cairo_surface_status (surface) == CAIRO_STATUS_SUCCESS) + { + cr = cairo_create (surface); + + area.x = 0; + area.y = 0; + area.width = allocation.width; + area.height = allocation.height; + baobab_chart_draw (chart, cr, area); + + cairo_rectangle (cr, + 0, 0, + allocation.width, + allocation.height); + + cairo_set_source_rgba (cr, 0.93, 0.93, 0.93, 0.5); /* tango: eeeeec */ + cairo_fill_preserve (cr); + + cairo_clip (cr); + + priv->memento = surface; + + cairo_destroy (cr); + } + + priv->is_frozen = TRUE; + + gtk_widget_queue_draw (chart); +} + +/** + * baobab_chart_thaw_updates: + * @chart: the #BaobabChart whose model signals are frozen. + * + * Reconnects @chart to the signals emitted by its model, which + * were disconnected through a call to #baobab_chart_freeze_updates. + * Takes the window out of its "processing" state and forces a redraw + * of the widget. + * + * Fails if @chart is not a #BaobabChart. + **/ +void +baobab_chart_thaw_updates (GtkWidget *chart) +{ + BaobabChartPrivate *priv; + + g_return_if_fail (BAOBAB_IS_CHART (chart)); + + priv = BAOBAB_CHART_GET_PRIVATE (chart); + + if (priv->is_frozen) + { + if (priv->model) + baobab_chart_connect_signals (chart, + priv->model); + + if (priv->memento) + { + cairo_surface_destroy (priv->memento); + priv->memento = NULL; + } + + priv->is_frozen = FALSE; + + priv->model_changed = TRUE; + gtk_widget_queue_draw (chart); + } +} + +/** + * baobab_chart_zoom_in: + * @chart: the #BaobabChart requested to zoom in. + * + * Zooms in the chart by decreasing its maximun depth. + * + * Fails if @chart is not a #BaobabChart. + **/ +void +baobab_chart_zoom_in (GtkWidget *chart) +{ + BaobabChartPrivate *priv; + BaobabChartClass *class; + guint new_max_depth; + + g_return_if_fail (BAOBAB_IS_CHART (chart)); + + priv = BAOBAB_CHART (chart)->priv; + class = BAOBAB_CHART_GET_CLASS (chart); + + if (class->can_zoom_in != NULL) + new_max_depth = class->can_zoom_in (chart); + else + new_max_depth = priv->max_depth - 1; + + baobab_chart_set_max_depth (chart, new_max_depth); +} + +/** + * baobab_chart_zoom_out: + * @chart: the #BaobabChart requested to zoom out. + * + * Zooms out the chart by increasing its maximun depth. + * + * Fails if @chart is not a #BaobabChart. + **/ +void +baobab_chart_zoom_out (GtkWidget *chart) +{ + g_return_if_fail (BAOBAB_IS_CHART (chart)); + + baobab_chart_set_max_depth (chart, + baobab_chart_get_max_depth (chart) + 1); +} + +/** + * baobab_chart_move_up_root: + * @chart: the #BaobabChart whose root is requested to move up one level. + * + * Move root to the inmediate parent of the current root item of @chart. + * + * Fails if @chart is not a #BaobabChart. + **/ +void +baobab_chart_move_up_root (GtkWidget *chart) +{ + BaobabChartPrivate *priv; + + GtkTreeIter parent_iter; + GtkTreePath *path; + GtkTreeIter root_iter; + + gint valid; + GtkTreePath *parent_path; + + g_return_if_fail (BAOBAB_IS_CHART (chart)); + + priv = BAOBAB_CHART_GET_PRIVATE (chart); + + if (priv->root == NULL) + return; + + path = gtk_tree_row_reference_get_path (priv->root); + + if (path != NULL) + gtk_tree_model_get_iter (priv->model, &root_iter, path); + else + return; + + if (gtk_tree_model_iter_parent (priv->model, &parent_iter, &root_iter)) + { + gtk_tree_model_get (priv->model, &parent_iter, priv->valid_column, + &valid, -1); + + if (valid == -1) + return; + + gtk_tree_row_reference_free (priv->root); + parent_path = gtk_tree_model_get_path (priv->model, &parent_iter); + priv->root = gtk_tree_row_reference_new (priv->model, parent_path); + gtk_tree_path_free (parent_path); + + g_signal_emit (BAOBAB_CHART (chart), + baobab_chart_signals[ITEM_ACTIVATED], + 0, &parent_iter); + + gtk_widget_queue_draw (chart); + } + + gtk_tree_path_free (path); +} + +/** + * baobab_chart_save_snapshot: + * @chart: the #BaobabChart requested to be exported to image. + * + * Opens a dialog to allow saving the current chart's image as a PNG, JPEG or + * BMP image. + * + * Fails if @chart is not a #BaobabChart. + **/ +void +baobab_chart_save_snapshot (GtkWidget *chart) +{ + BaobabChartPrivate *priv; + + GdkPixbuf *pixbuf; + + GtkWidget *fs_dlg; + GtkWidget *vbox; + GtkWidget *hbox; + GtkWidget *label; + GtkWidget *opt_menu; + gchar *sel_type; + gchar *filename; + gchar *def_filename; + + BaobabChartItem *item; + + g_return_if_fail (BAOBAB_IS_CHART (chart)); + + while (gtk_events_pending ()) + gtk_main_iteration (); + + /* Get the chart's pixbuf */ + pixbuf = baobab_chart_get_pixbuf (chart); + if (pixbuf == NULL) + { + GtkWidget *dialog; + dialog = gtk_message_dialog_new (NULL, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + _("Cannot create pixbuf image!")); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + + return; + } + + priv = BAOBAB_CHART (chart)->priv; + + /* Popup the File chooser dialog */ + fs_dlg = gtk_file_chooser_dialog_new (_("Save Snapshot"), + NULL, + GTK_FILE_CHOOSER_ACTION_SAVE, + GTK_STOCK_CANCEL, + GTK_RESPONSE_CANCEL, + GTK_STOCK_SAVE, + GTK_RESPONSE_ACCEPT, NULL); + + item = (BaobabChartItem *) priv->first_item->data; + def_filename = g_strdup_printf (SNAPSHOT_DEF_FILENAME_FORMAT, item->name); + + gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (fs_dlg), def_filename); + g_free (def_filename); + + gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (fs_dlg), + g_get_home_dir ()); + +#if GTK_CHECK_VERSION(2,8,0) + gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (fs_dlg), TRUE); +#endif + + /* extra widget */ + vbox = gtk_vbox_new (FALSE, 0); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 0); + gtk_file_chooser_set_extra_widget (GTK_FILE_CHOOSER (fs_dlg), vbox); + + hbox = gtk_hbox_new (FALSE, 12); + gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 6); + + label = gtk_label_new_with_mnemonic (_("_Image type:")); + gtk_box_pack_start (GTK_BOX (hbox), + label, + FALSE, FALSE, 0); + + opt_menu = gtk_combo_box_new_text (); + gtk_combo_box_append_text (GTK_COMBO_BOX (opt_menu), "png"); + gtk_combo_box_append_text (GTK_COMBO_BOX (opt_menu), "jpeg"); + gtk_combo_box_append_text (GTK_COMBO_BOX (opt_menu), "bmp"); + gtk_combo_box_set_active (GTK_COMBO_BOX (opt_menu), 0); + gtk_box_pack_start (GTK_BOX (hbox), opt_menu, TRUE, TRUE, 0); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), opt_menu); + gtk_widget_show_all (vbox); + + if (gtk_dialog_run (GTK_DIALOG (fs_dlg)) == GTK_RESPONSE_ACCEPT) + { + filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (fs_dlg)); + sel_type = gtk_combo_box_get_active_text (GTK_COMBO_BOX (opt_menu)); + if (! g_str_has_suffix (filename, sel_type)) + { + gchar *tmp; + tmp = filename; + filename = g_strjoin (".", filename, sel_type, NULL); + g_free (tmp); + } + gdk_pixbuf_save (pixbuf, filename, sel_type, NULL, NULL); + + g_free (filename); + g_free (sel_type); + } + + gtk_widget_destroy (fs_dlg); + g_object_unref (pixbuf); +} + +/** + * baobab_chart_is_frozen: + * @chart: the #BaobabChart to ask if frozen. + * + * Returns a boolean telling whether the chart is in a frozen state, meanning + * that no actions should be taken uppon it. + * + * Fails if @chart is not a #BaobabChart. + **/ +gboolean +baobab_chart_is_frozen (GtkWidget *chart) +{ + BaobabChartPrivate *priv; + + g_return_val_if_fail (BAOBAB_IS_CHART (chart), FALSE); + + priv = BAOBAB_CHART_GET_PRIVATE (chart); + return priv->is_frozen; +} + +/** + * baobab_chart_is_frozen: + * @chart: the #BaobabChart to obtain the highlighted it from. + * + * Returns a BaobabChartItem corresponding to the item that currently has mouse + * pointer over, or NULL if no item is highlighted. + * + * Fails if @chart is not a #BaobabChart. + **/ +BaobabChartItem * +baobab_chart_get_highlighted_item (GtkWidget *chart) +{ + BaobabChartPrivate *priv; + + g_return_val_if_fail (BAOBAB_IS_CHART (chart), NULL); + + priv = BAOBAB_CHART_GET_PRIVATE (chart); + return (priv->highlighted_item ? + (BaobabChartItem *) priv->highlighted_item->data : NULL); +} + +/** + * baobab_chart_can_zoom_in: + * @chart: the #BaobabChart to ask if can be zoomed in. + * + * Returns a boolean telling whether the chart can be zoomed in, given its current + * visualization conditions. + * + * Fails if @chart is not a #BaobabChart. + **/ +gboolean +baobab_chart_can_zoom_in (GtkWidget *chart) +{ + BaobabChartPrivate *priv; + BaobabChartClass *class; + + g_return_val_if_fail (BAOBAB_IS_CHART (chart), FALSE); + + priv = BAOBAB_CHART (chart)->priv; + class = BAOBAB_CHART_GET_CLASS (chart); + + if (class->can_zoom_in != NULL) + return class->can_zoom_in (chart) > 0; + else + return priv->max_depth > 1; +} + +/** + * baobab_chart_can_zoom_out: + * @chart: the #BaobabChart to ask if can be zoomed out. + * + * Returns a boolean telling whether the chart can be zoomed out, given its current + * visualization conditions. + * + * Fails if @chart is not a #BaobabChart. + **/ +gboolean +baobab_chart_can_zoom_out (GtkWidget *chart) +{ + BaobabChartPrivate *priv; + BaobabChartClass *class; + + g_return_val_if_fail (BAOBAB_IS_CHART (chart), FALSE); + + priv = BAOBAB_CHART (chart)->priv; + class = BAOBAB_CHART_GET_CLASS (chart); + + if (class->can_zoom_out != NULL) + return class->can_zoom_out (chart) > 0; + else + return (priv->max_depth < BAOBAB_CHART_MAX_DEPTH); +} |