diff options
Diffstat (limited to 'baobab/src')
-rw-r--r-- | baobab/src/Makefile.am | 49 | ||||
-rw-r--r-- | baobab/src/baobab-cell-renderer-progress.c | 265 | ||||
-rw-r--r-- | baobab/src/baobab-cell-renderer-progress.h | 57 | ||||
-rw-r--r-- | baobab/src/baobab-chart.c | 1876 | ||||
-rw-r--r-- | baobab/src/baobab-chart.h | 159 | ||||
-rw-r--r-- | baobab/src/baobab-prefs.c | 338 | ||||
-rw-r--r-- | baobab/src/baobab-prefs.h | 28 | ||||
-rw-r--r-- | baobab/src/baobab-remote-connect-dialog.c | 803 | ||||
-rw-r--r-- | baobab/src/baobab-remote-connect-dialog.h | 59 | ||||
-rw-r--r-- | baobab/src/baobab-ringschart.c | 675 | ||||
-rw-r--r-- | baobab/src/baobab-ringschart.h | 72 | ||||
-rw-r--r-- | baobab/src/baobab-scan.c | 381 | ||||
-rw-r--r-- | baobab/src/baobab-scan.h | 30 | ||||
-rw-r--r-- | baobab/src/baobab-treemap.c | 342 | ||||
-rw-r--r-- | baobab/src/baobab-treemap.h | 64 | ||||
-rw-r--r-- | baobab/src/baobab-treeview.c | 336 | ||||
-rw-r--r-- | baobab/src/baobab-treeview.h | 47 | ||||
-rw-r--r-- | baobab/src/baobab-utils.c | 567 | ||||
-rw-r--r-- | baobab/src/baobab-utils.h | 49 | ||||
-rw-r--r-- | baobab/src/baobab.c | 1317 | ||||
-rw-r--r-- | baobab/src/baobab.h | 128 | ||||
-rw-r--r-- | baobab/src/callbacks.c | 394 | ||||
-rw-r--r-- | baobab/src/callbacks.h | 62 |
23 files changed, 8098 insertions, 0 deletions
diff --git a/baobab/src/Makefile.am b/baobab/src/Makefile.am new file mode 100644 index 00000000..224ca126 --- /dev/null +++ b/baobab/src/Makefile.am @@ -0,0 +1,49 @@ +NULL = + +INCLUDES = \ + -DPREFIX=\""$(prefix)"\" \ + -DSYSCONFDIR=\""$(sysconfdir)"\" \ + -DLIBDIR=\""$(libdir)"\" \ + -DDATADIR=\""$(datadir)"\" \ + -DPKGDATADIR=\""$(datadir)/mate-disk-usage-analyzer"\" \ + -DBAOBAB_PIX_DIR=\""$(datadir)/mate-disk-usage-analyzer/pixmaps/"\" \ + -DMATELOCALEDIR=\""$(prefix)/$(DATADIRNAME)/locale"\" \ + $(NULL) + +bin_PROGRAMS = mate-disk-usage-analyzer + +mate_disk_usage_analyzer_SOURCES = \ + baobab.c \ + baobab.h \ + baobab-cell-renderer-progress.c \ + baobab-cell-renderer-progress.h \ + baobab-ringschart.c \ + baobab-ringschart.h \ + baobab-scan.c \ + baobab-scan.h \ + baobab-treeview.c \ + baobab-treeview.h \ + baobab-utils.c \ + baobab-utils.h \ + callbacks.c \ + callbacks.h \ + baobab-prefs.c \ + baobab-prefs.h \ + baobab-remote-connect-dialog.c \ + baobab-remote-connect-dialog.h \ + baobab-chart.c \ + baobab-chart.h \ + baobab-treemap.c \ + baobab-treemap.h + +mate_disk_usage_analyzer_CFLAGS = \ + $(MATE_UTILS_CFLAGS) \ + $(LIBGTOP_CFLAGS) \ + $(NULL) + +mate_disk_usage_analyzer_LDFLAGS = -export-dynamic +mate_disk_usage_analyzer_LDADD = \ + -lm \ + $(MATE_UTILS_LIBS) \ + $(LIBGTOP_LIBS) \ + $(NULL) diff --git a/baobab/src/baobab-cell-renderer-progress.c b/baobab/src/baobab-cell-renderer-progress.c new file mode 100644 index 00000000..753d8b0a --- /dev/null +++ b/baobab/src/baobab-cell-renderer-progress.c @@ -0,0 +1,265 @@ +/* baobab-cell-renderer-progress.c + * + * Copyright (C) 2006 Paolo Borelli + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include <stdlib.h> + +#include "baobab-cell-renderer-progress.h" + +#define BAOBAB_CELL_RENDERER_PROGRESS_GET_PRIVATE(object) (G_TYPE_INSTANCE_GET_PRIVATE ((object), \ + BAOBAB_TYPE_CELL_RENDERER_PROGRESS, \ + BaobabCellRendererProgressPrivate)) + +enum +{ + PROP_0, + PROP_PERC +}; + +struct _BaobabCellRendererProgressPrivate +{ + double perc; +}; + +G_DEFINE_TYPE (BaobabCellRendererProgress, baobab_cell_renderer_progress, GTK_TYPE_CELL_RENDERER) + +static void +baobab_cell_renderer_progress_init (BaobabCellRendererProgress *cellprogress) +{ + cellprogress->priv = BAOBAB_CELL_RENDERER_PROGRESS_GET_PRIVATE (cellprogress); + cellprogress->priv->perc = 0; + + gtk_cell_renderer_set_padding (GTK_CELL_RENDERER (cellprogress), 4, 4); +} + +GtkCellRenderer* +baobab_cell_renderer_progress_new (void) +{ + return g_object_new (BAOBAB_TYPE_CELL_RENDERER_PROGRESS, NULL); +} + +static void +baobab_cell_renderer_progress_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + BaobabCellRendererProgress *cellprogress = BAOBAB_CELL_RENDERER_PROGRESS (object); + + switch (param_id) + { + case PROP_PERC: + g_value_set_double (value, cellprogress->priv->perc); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + } +} + +static void +baobab_cell_renderer_progress_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + BaobabCellRendererProgress *cellprogress = BAOBAB_CELL_RENDERER_PROGRESS (object); + + switch (param_id) + { + case PROP_PERC: + cellprogress->priv->perc = g_value_get_double (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + } +} + +/* we simply have a fixed size */ + +#define FIXED_WIDTH 70 +#define FIXED_HEIGHT 8 + +static void +baobab_cell_renderer_progress_get_size (GtkCellRenderer *cell, + GtkWidget *widget, + GdkRectangle *cell_area, + gint *x_offset, + gint *y_offset, + gint *width, + gint *height) +{ + gint calc_width; + gint calc_height; + gint xpad; + gint ypad; + gfloat xalign; + gfloat yalign; + + gtk_cell_renderer_get_padding (cell, &xpad, &ypad); + calc_width = (gint) xpad * 2 + FIXED_WIDTH; + calc_height = (gint) ypad * 2 + FIXED_HEIGHT; + + if (width) + *width = calc_width; + + if (height) + *height = calc_height; + + if (cell_area) + { + gtk_cell_renderer_get_alignment (cell, &xalign, &yalign); + if (x_offset) + { + *x_offset = xalign * (cell_area->width - calc_width); + *x_offset = MAX (*x_offset, 0); + } + + if (y_offset) + { + *y_offset = yalign * (cell_area->height - calc_height); + *y_offset = MAX (*y_offset, 0); + } + } +} + +static void +set_color_according_to_perc (cairo_t *cr, double value) +{ + static GdkColor red; + static GdkColor yellow; + static GdkColor green; + static gboolean colors_initialized = FALSE; + + if (!colors_initialized) + { + /* hardcoded tango colors */ + gdk_color_parse ("#cc0000", &red); + gdk_color_parse ("#edd400", &yellow); + gdk_color_parse ("#73d216", &green); + + colors_initialized = TRUE; + } + + if (value <= 0) + { + cairo_set_source_rgb (cr, 1, 1, 1); + return; + } + else if (value <= 33.33) + { + gdk_cairo_set_source_color (cr, &green); + return; + } + else if (value <= 66.66) + { + gdk_cairo_set_source_color (cr, &yellow); + return; + } + else if (value <= 100.0) + { + gdk_cairo_set_source_color (cr, &red); + return; + } + else + g_assert_not_reached (); +} + +static void +baobab_cell_renderer_progress_render (GtkCellRenderer *cell, + GdkWindow *window, + GtkWidget *widget, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GdkRectangle *expose_area, + guint flags) +{ + BaobabCellRendererProgress *cellprogress = BAOBAB_CELL_RENDERER_PROGRESS (cell); + gint x, y, w, h, perc_w; + gboolean is_rtl; + cairo_t *cr; + gint xpad; + gint ypad; + GtkStyle *style; + + is_rtl = gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL; + + cr = gdk_cairo_create (window); + + gtk_cell_renderer_get_padding (cell, &xpad, &ypad); + + x = cell_area->x + xpad; + y = cell_area->y + ypad; + + w = cell_area->width - xpad * 2; + h = cell_area->height - ypad * 2; + + /* + * we always use a white bar with black + * border and green/yellow/red progress... + * I know it's not theme friendly, but we don't + * want a plain progress bar + */ + + cairo_rectangle (cr, x, y, w, h); + cairo_set_source_rgb (cr, 0, 0, 0); + cairo_fill (cr); + + style = gtk_widget_get_style (widget); + x += style->xthickness; + y += style->ythickness; + w -= style->xthickness * 2; + h -= style->ythickness * 2; + + cairo_rectangle (cr, x, y, w, h); + cairo_set_source_rgb (cr, 1, 1, 1); + cairo_fill (cr); + + perc_w = w * MAX (0, cellprogress->priv->perc) / 100; + + cairo_rectangle (cr, is_rtl ? (x + w - perc_w) : x, y, perc_w, h); + set_color_according_to_perc (cr, cellprogress->priv->perc); + cairo_fill (cr); + + cairo_destroy (cr); +} + +static void +baobab_cell_renderer_progress_class_init (BaobabCellRendererProgressClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (klass); + + object_class->get_property = baobab_cell_renderer_progress_get_property; + object_class->set_property = baobab_cell_renderer_progress_set_property; + + cell_class->get_size = baobab_cell_renderer_progress_get_size; + cell_class->render = baobab_cell_renderer_progress_render; + + g_object_class_install_property (object_class, + PROP_PERC, + g_param_spec_double ("perc", + "percentage", + "precentage", + -1, 100, 0, + G_PARAM_READWRITE)); + + g_type_class_add_private (object_class, + sizeof (BaobabCellRendererProgressPrivate)); +} diff --git a/baobab/src/baobab-cell-renderer-progress.h b/baobab/src/baobab-cell-renderer-progress.h new file mode 100644 index 00000000..563e693a --- /dev/null +++ b/baobab/src/baobab-cell-renderer-progress.h @@ -0,0 +1,57 @@ +/* baobab-cell-renderer-progress.h + * + * Copyright (C) 2006 Paolo Borelli + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __BAOBAB_CELL_RENDERER_PROGRESS_H__ +#define __BAOBAB_CELL_RENDERER_PROGRESS_H__ + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define BAOBAB_TYPE_CELL_RENDERER_PROGRESS (baobab_cell_renderer_progress_get_type ()) +#define BAOBAB_CELL_RENDERER_PROGRESS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), BAOBAB_TYPE_CELL_RENDERER_PROGRESS, BaobabCellRendererProgress)) +#define BAOBAB_CELL_RENDERER_PROGRESS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), BAOBAB_TYPE_CELL_RENDERER_PROGRESS, BaobabCellRendererProgressClass)) +#define BAOBAB_IS_CELL_RENDERER_PROGRESS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), BAOBAB_TYPE_CELL_RENDERER_PROGRESS)) +#define BAOBAB_IS_CELL_RENDERER_PROGRESS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), BAOBAB_TYPE_CELL_RENDERER_PROGRESS)) +#define BAOBAB_CELL_RENDERER_PROGRESS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), BAOBAB_TYPE_CELL_RENDERER_PROGRESS, BaobabCellRendererProgressClass)) + +typedef struct _BaobabCellRendererProgress BaobabCellRendererProgress; +typedef struct _BaobabCellRendererProgressClass BaobabCellRendererProgressClass; +typedef struct _BaobabCellRendererProgressPrivate BaobabCellRendererProgressPrivate; + +struct _BaobabCellRendererProgress +{ + GtkCellRenderer parent_instance; + + /*< private >*/ + BaobabCellRendererProgressPrivate *priv; +}; + +struct _BaobabCellRendererProgressClass +{ + GtkCellRendererClass parent_class; +}; + +GType baobab_cell_renderer_progress_get_type (void) G_GNUC_CONST; +GtkCellRenderer* baobab_cell_renderer_progress_new (void); + +G_END_DECLS + +#endif /* __BAOBAB_CELL_RENDERER_PROGRESS_H__ */ 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); +} diff --git a/baobab/src/baobab-chart.h b/baobab/src/baobab-chart.h new file mode 100644 index 00000000..19773024 --- /dev/null +++ b/baobab/src/baobab-chart.h @@ -0,0 +1,159 @@ +/* + * baobab-chart.h + * + * 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]> + */ + +#ifndef __BAOBAB_CHART_H__ +#define __BAOBAB_CHART_H__ + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define BAOBAB_CHART_TYPE (baobab_chart_get_type ()) +#define BAOBAB_CHART(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), BAOBAB_CHART_TYPE, BaobabChart)) +#define BAOBAB_CHART_CLASS(obj) (G_TYPE_CHECK_CLASS_CAST ((obj), BAOBAB_CHART_TYPE, BaobabChartClass)) +#define BAOBAB_IS_CHART(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), BAOBAB_CHART_TYPE)) +#define BAOBAB_IS_CHART_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE ((obj), BAOBAB_CHART_TYPE)) +#define BAOBAB_CHART_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), BAOBAB_CHART_TYPE, BaobabChartClass)) + +typedef struct _BaobabChart BaobabChart; +typedef struct _BaobabChartClass BaobabChartClass; +typedef struct _BaobabChartPrivate BaobabChartPrivate; +typedef struct _BaobabChartColor BaobabChartColor; +typedef struct _BaobabChartItem BaobabChartItem; + +struct _BaobabChart +{ + GtkWidget parent; + + /* < private > */ + BaobabChartPrivate *priv; +}; + +struct _BaobabChartColor +{ + gdouble red; + gdouble green; + gdouble blue; +}; + +struct _BaobabChartItem +{ + gchar *name; + gchar *size; + guint depth; + gdouble rel_start; + gdouble rel_size; + GtkTreeIter iter; + gboolean visible; + gboolean has_any_child; + gboolean has_visible_children; + GdkRectangle rect; + + GList *parent; + + gpointer data; +}; + +struct _BaobabChartClass +{ + GtkWidgetClass parent_class; + + /* Signal prototypes */ + void (* item_activated) (BaobabChart *chart, + GtkTreeIter *iter); + + /* Abstract methods */ + void (* draw_item) (GtkWidget *chart, + cairo_t *cr, + BaobabChartItem *item, + gboolean highlighted); + + void (* pre_draw) (GtkWidget *chart, + cairo_t *cr); + + void (* post_draw) (GtkWidget *chart, + cairo_t *cr); + + void (* calculate_item_geometry) (GtkWidget *chart, + BaobabChartItem *item); + + gboolean (* is_point_over_item) (GtkWidget *chart, + BaobabChartItem *item, + gdouble x, + gdouble y); + + void (* get_item_rectangle) (GtkWidget *chart, + BaobabChartItem *item); + + guint (* can_zoom_in) (GtkWidget *chart); + guint (* can_zoom_out) (GtkWidget *chart); +}; + +GType baobab_chart_get_type (void) G_GNUC_CONST; +GtkWidget* baobab_chart_new (void); +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); +void baobab_chart_set_model (GtkWidget *chart, + GtkTreeModel *model); +GtkTreeModel* baobab_chart_get_model (GtkWidget *chart); +void baobab_chart_set_max_depth (GtkWidget *chart, + guint max_depth); +guint baobab_chart_get_max_depth (GtkWidget *chart); +void baobab_chart_set_root (GtkWidget *chart, + GtkTreePath *root); +GtkTreePath *baobab_chart_get_root (GtkWidget *chart); +void baobab_chart_freeze_updates (GtkWidget *chart); +void baobab_chart_thaw_updates (GtkWidget *chart); +void baobab_chart_get_item_color (BaobabChartColor *color, + gdouble position, + gint depth, + gboolean highlighted); +void baobab_chart_move_up_root (GtkWidget *chart); +void baobab_chart_zoom_in (GtkWidget *chart); +void baobab_chart_zoom_out (GtkWidget *chart); +void baobab_chart_save_snapshot (GtkWidget *chart); +gboolean baobab_chart_is_frozen (GtkWidget *chart); +BaobabChartItem *baobab_chart_get_highlighted_item (GtkWidget *chart); + +gboolean baobab_chart_can_zoom_in (GtkWidget *chart); +gboolean baobab_chart_can_zoom_out (GtkWidget *chart); + +G_END_DECLS + +#endif diff --git a/baobab/src/baobab-prefs.c b/baobab/src/baobab-prefs.c new file mode 100644 index 00000000..c440aa7f --- /dev/null +++ b/baobab/src/baobab-prefs.c @@ -0,0 +1,338 @@ +/* + * baobab-prefs.c + * This file is part of baobab + * + * Copyright (C) 2005-2006 Fabio Marzocca <[email protected]> + * + * 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 + */ + + +#include <config.h> + +#include <string.h> +#include <sys/stat.h> +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#include <glib/gstdio.h> +#include <mateconf/mateconf-client.h> +#include <glibtop/mountlist.h> +#include <glibtop/fsusage.h> +#include "baobab.h" +#include "baobab-utils.h" +#include "baobab-prefs.h" + +enum +{ + COL_CHECK, + COL_DEVICE, + COL_MOUNT_D, + COL_MOUNT, + COL_TYPE, + COL_FS_SIZE, + COL_FS_AVAIL, + TOT_COLUMNS +}; + +static gboolean +add_excluded_item (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + GSList **list) +{ + GSList *l; + gchar *mount; + gboolean check; + + l = *list; + + gtk_tree_model_get (model, + iter, + COL_MOUNT, &mount, + COL_CHECK, &check, + -1); + + if (!check) { + l = g_slist_prepend (l, mount); + } + + *list = l; + + return FALSE; +} + +static GSList * +get_excluded_locations (GtkTreeModel *model) +{ + GSList *l = NULL; + + gtk_tree_model_foreach (model, + (GtkTreeModelForeachFunc) add_excluded_item, + &l); + + return g_slist_reverse (l); +} + +static void +save_mateconf (GtkTreeModel *model) +{ + GSList *l; + + l = get_excluded_locations (model); + + mateconf_client_set_list (baobab.mateconf_client, + BAOBAB_EXCLUDED_DIRS_KEY, MATECONF_VALUE_STRING, + l, NULL); + + g_slist_foreach (l, (GFunc) g_free, NULL); + g_slist_free (l); +} + +static void +enable_home_cb (GtkToggleButton *togglebutton, gpointer user_data) +{ + gboolean enable; + + enable = gtk_toggle_button_get_active (togglebutton); + + mateconf_client_set_bool (baobab.mateconf_client, + BAOBAB_ENABLE_HOME_MONITOR_KEY, + enable, + NULL); + +} + +static void +filechooser_response_cb (GtkDialog *dialog, + gint response_id, + GtkTreeModel *model) +{ + switch (response_id) { + case GTK_RESPONSE_HELP: + baobab_help_display (GTK_WINDOW (baobab.window), + "baobab.xml", "baobab-preferences"); + break; + case GTK_RESPONSE_DELETE_EVENT: + case GTK_RESPONSE_CLOSE: + save_mateconf (model); + default: + gtk_widget_destroy (GTK_WIDGET (dialog)); + break; + } +} + +static void +check_toggled (GtkCellRendererToggle *cell, + gchar *path_str, + GtkTreeModel *model) +{ + GtkTreeIter iter; + GtkTreePath *path = gtk_tree_path_new_from_string (path_str); + gboolean toggle; + gchar *mountpoint; + + /* get toggled iter */ + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_model_get (model, + &iter, + COL_CHECK, &toggle, + COL_MOUNT_D, &mountpoint, + -1); + + /* check if root dir */ + if (strcmp ("/", mountpoint) == 0) + goto clean_up; + + /* set new value */ + gtk_list_store_set (GTK_LIST_STORE (model), + &iter, + COL_CHECK, !toggle, + -1); + + clean_up: + g_free (mountpoint); + gtk_tree_path_free (path); +} + +static void +create_tree_props (GtkBuilder *builder, GtkTreeModel *model) +{ + GtkCellRenderer *cell; + GtkTreeViewColumn *col; + GtkWidget *tvw; + + tvw = GTK_WIDGET (gtk_builder_get_object (builder , "tree_view_props")); + + /* checkbox column */ + cell = gtk_cell_renderer_toggle_new (); + g_signal_connect (cell, "toggled", + G_CALLBACK (check_toggled), model); + + col = gtk_tree_view_column_new_with_attributes (_("Scan"), cell, + "active", COL_CHECK, + NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (tvw), col); + + /* First text column */ + cell = gtk_cell_renderer_text_new (); + col = gtk_tree_view_column_new_with_attributes (_("Device"), cell, + "markup", COL_DEVICE, + "text", COL_DEVICE, + NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (tvw), col); + + /* second text column */ + cell = gtk_cell_renderer_text_new (); + col = gtk_tree_view_column_new_with_attributes (_("Mount Point"), + cell, "markup", + COL_MOUNT_D, "text", + COL_MOUNT_D, NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (tvw), col); + + /* third text column */ + cell = gtk_cell_renderer_text_new (); + col = gtk_tree_view_column_new_with_attributes (_("Filesystem Type"), + cell, "markup", + COL_TYPE, "text", + COL_TYPE, NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (tvw), col); + + /* fourth text column */ + cell = gtk_cell_renderer_text_new (); + col = gtk_tree_view_column_new_with_attributes (_("Total Size"), + cell, "markup", + COL_FS_SIZE, "text", + COL_FS_SIZE, NULL); + g_object_set (G_OBJECT (cell), "xalign", (gfloat) 1.0, NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (tvw), col); + + /* fifth text column */ + cell = gtk_cell_renderer_text_new (); + col = gtk_tree_view_column_new_with_attributes (_("Available"), + cell, "markup", + COL_FS_AVAIL, "text", + COL_FS_AVAIL, NULL); + g_object_set (G_OBJECT (cell), "xalign", (gfloat) 1.0, NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (tvw), col); + + gtk_tree_view_set_model (GTK_TREE_VIEW (tvw), model); + g_object_unref (model); +} + +static void +fill_props_model (GtkListStore *store) +{ + GtkTreeIter iter; + guint lo; + glibtop_mountlist mountlist; + glibtop_mountentry *mountentry, *mountentry_tofree; + guint64 fstotal, fsavail; + + mountentry_tofree = glibtop_get_mountlist (&mountlist, 0); + + for (lo = 0, mountentry = mountentry_tofree; + lo < mountlist.number; + lo++, mountentry++) { + glibtop_fsusage fsusage; + gchar * total, *avail; + GFile *file; + gchar *uri; + gboolean excluded; + + struct stat buf; + if (g_stat (mountentry->devname,&buf) == -1) + continue; + + glibtop_get_fsusage (&fsusage, mountentry->mountdir); + fstotal = fsusage.blocks * fsusage.block_size; + fsavail = fsusage.bfree * fsusage.block_size; + total = g_format_size_for_display(fstotal); + avail = g_format_size_for_display(fsavail); + file = g_file_new_for_path (mountentry->mountdir); + uri = g_file_get_uri (file); + excluded = baobab_is_excluded_location (file); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + COL_CHECK, !excluded, + COL_DEVICE, mountentry->devname, + COL_MOUNT_D, mountentry->mountdir, + COL_MOUNT, uri, + COL_TYPE, mountentry->type, + COL_FS_SIZE, total, + COL_FS_AVAIL, avail, + -1); + g_free(total); + g_free(avail); + g_free(uri); + g_object_unref(file); + } + + g_free (mountentry_tofree); +} + +void +baobab_prefs_dialog (void) +{ + GtkBuilder *builder; + GtkWidget *dlg; + GtkWidget *check_enablehome; + GtkListStore *model; + GError *error = NULL; + + builder = gtk_builder_new (); + gtk_builder_add_from_file (builder, BAOBAB_DIALOG_SCAN_UI_FILE, &error); + + if (error) { + g_critical ("Can't load user interface file for the scan properties dialog: %s", + error->message); + g_object_unref (builder); + g_error_free (error); + + return; + } + + dlg = GTK_WIDGET (gtk_builder_get_object (builder, "dialog_scan_props")); + + gtk_window_set_transient_for (GTK_WINDOW (dlg), + GTK_WINDOW (baobab.window)); + + model = gtk_list_store_new (TOT_COLUMNS, + G_TYPE_BOOLEAN, /* checkbox */ + G_TYPE_STRING, /* device */ + G_TYPE_STRING, /*mount point display */ + G_TYPE_STRING, /* mount point uri */ + G_TYPE_STRING, /* fs type */ + G_TYPE_STRING, /* fs size */ + G_TYPE_STRING /* fs avail */ + ); + + create_tree_props (builder, GTK_TREE_MODEL (model)); + fill_props_model (model); + + check_enablehome = GTK_WIDGET (gtk_builder_get_object (builder, "check_enable_home")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_enablehome), + baobab.monitor_home != NULL); + + g_signal_connect (check_enablehome, "toggled", + G_CALLBACK (enable_home_cb), NULL); + + g_signal_connect (dlg, "response", + G_CALLBACK (filechooser_response_cb), model); + + gtk_widget_show_all (dlg); +} + diff --git a/baobab/src/baobab-prefs.h b/baobab/src/baobab-prefs.h new file mode 100644 index 00000000..57cbb972 --- /dev/null +++ b/baobab/src/baobab-prefs.h @@ -0,0 +1,28 @@ +/* + * baobab-prefs.h + * This file is part of baobab + * + * Copyright (C) 2005-2006 Fabio Marzocca <[email protected]> + * + * 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 + */ + +#ifndef __BAOBAB_PROPS_H__ +#define __BAOBAB_PROPS_H__ + +void baobab_prefs_dialog (void); + +#endif /* __BAOBAB_PROPS_H__ */ diff --git a/baobab/src/baobab-remote-connect-dialog.c b/baobab/src/baobab-remote-connect-dialog.c new file mode 100644 index 00000000..5e53f27e --- /dev/null +++ b/baobab/src/baobab-remote-connect-dialog.c @@ -0,0 +1,803 @@ +/* Baobab - (C) 2005 Fabio Marzocca + + baobab-remote-connect-dialog.c + + Modified module from caja-connect-server-dialog.c + Released under same licence + */ +/* + * Caja + * + * Copyright (C) 2003 Red Hat, Inc. + * + * Caja 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. + * + * Caja 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; see the file COPYING. If not, + * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include <config.h> +#include "baobab-remote-connect-dialog.h" + +#include <string.h> +#include <glib/gi18n.h> +#include <gio/gio.h> +#include <gtk/gtk.h> + +#include "baobab.h" + + +/* Translators: all the strings in this file are meant to map the + similar strings inside caja-connect-server and should be + translated the same way +*/ + +struct _BaobabRemoteConnectDialogDetails { + + GtkWidget *table; + + GtkWidget *type_combo; + GtkWidget *uri_entry; + GtkWidget *server_entry; + GtkWidget *share_entry; + GtkWidget *port_entry; + GtkWidget *folder_entry; + GtkWidget *domain_entry; + GtkWidget *user_entry; +}; + +static void baobab_remote_connect_dialog_class_init (BaobabRemoteConnectDialogClass *class); +static void baobab_remote_connect_dialog_init (BaobabRemoteConnectDialog *dialog); + +G_DEFINE_TYPE(BaobabRemoteConnectDialog, baobab_remote_connect_dialog, GTK_TYPE_DIALOG) + +#define RESPONSE_CONNECT GTK_RESPONSE_OK + + +static void +display_error_dialog (GError *error, + GFile *location, + GtkWindow *parent) +{ + GtkWidget *dlg; + char *parse_name; + char *error_message; + + parse_name = g_file_get_parse_name (location); + error_message = g_strdup_printf (_("Cannot scan location \"%s\""), + parse_name); + g_free (parse_name); + + dlg = gtk_message_dialog_new (parent, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + error_message, NULL); + + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dlg), + "%s", error->message); + + g_free (error_message); + + gtk_dialog_run (GTK_DIALOG (dlg)); + gtk_widget_destroy (dlg); +} + +static void +mount_enclosing_ready_cb (GFile *location, + GAsyncResult *res, + GtkWindow *app) +{ + gboolean success; + GError *error = NULL; + + success = g_file_mount_enclosing_volume_finish (location, + res, &error); + + if (success || + g_error_matches (error, G_IO_ERROR, G_IO_ERROR_ALREADY_MOUNTED)) { + baobab_scan_location (location); + } + else { + display_error_dialog (error, location, app); + } + + if (error) + g_error_free (error); + + if (location) + g_object_unref (location); +} + +static void +connect_server_dialog_present_uri (GtkWindow *app, + GFile *location, + GtkWidget *widget) +{ + GMountOperation *op; + + op = gtk_mount_operation_new (GTK_WINDOW (widget)); + + g_file_mount_enclosing_volume (location, + 0, op, + NULL, + (GAsyncReadyCallback) mount_enclosing_ready_cb, + app); + g_object_unref (op); +} + +struct MethodInfo { + const char *scheme; + guint flags; +}; + +/* A collection of flags for MethodInfo.flags */ +enum { + DEFAULT_METHOD = 0x00000001, + + /* Widgets to display in setup_for_type */ + SHOW_SHARE = 0x00000010, + SHOW_PORT = 0x00000020, + SHOW_USER = 0x00000040, + SHOW_DOMAIN = 0x00000080, + + IS_ANONYMOUS = 0x00001000 +}; + +/* Remember to fill in descriptions below */ +static struct MethodInfo methods[] = { + /* FIXME: we need to alias ssh to sftp */ + { "sftp", SHOW_PORT | SHOW_USER }, + { "ftp", SHOW_PORT | SHOW_USER }, + { "ftp", DEFAULT_METHOD | IS_ANONYMOUS | SHOW_PORT}, + { "smb", SHOW_SHARE | SHOW_USER | SHOW_DOMAIN }, + { "dav", SHOW_PORT | SHOW_USER }, + /* FIXME: hrm, shouldn't it work? */ + { "davs", SHOW_PORT | SHOW_USER }, + { NULL, 0 }, /* Custom URI method */ +}; + +/* To get around non constant gettext strings */ +static const char* +get_method_description (struct MethodInfo *meth) +{ + if (!meth->scheme) { + return _("Custom Location"); + } else if (strcmp (meth->scheme, "sftp") == 0) { + return _("SSH"); + } else if (strcmp (meth->scheme, "ftp") == 0) { + if (meth->flags & IS_ANONYMOUS) { + return _("Public FTP"); + } else { + return _("FTP (with login)"); + } + } else if (strcmp (meth->scheme, "smb") == 0) { + return _("Windows share"); + } else if (strcmp (meth->scheme, "dav") == 0) { + return _("WebDAV (HTTP)"); + } else if (strcmp (meth->scheme, "davs") == 0) { + return _("Secure WebDAV (HTTPS)"); + + /* No descriptive text */ + } else { + return meth->scheme; + } +} + +static void +baobab_remote_connect_dialog_finalize (GObject *object) +{ + BaobabRemoteConnectDialog *dialog; + + dialog = BAOBAB_REMOTE_CONNECT_DIALOG(object); + + g_object_unref (dialog->details->uri_entry); + g_object_unref (dialog->details->server_entry); + g_object_unref (dialog->details->share_entry); + g_object_unref (dialog->details->port_entry); + g_object_unref (dialog->details->folder_entry); + g_object_unref (dialog->details->domain_entry); + g_object_unref (dialog->details->user_entry); + + g_free (dialog->details); + + G_OBJECT_CLASS (baobab_remote_connect_dialog_parent_class)->finalize (object); +} + +static void +connect_to_server (BaobabRemoteConnectDialog *dialog, GtkWindow *app) +{ + struct MethodInfo *meth; + char *uri; + GFile *location; + int index; + GtkTreeIter iter; + + /* Get our method info */ + gtk_combo_box_get_active_iter (GTK_COMBO_BOX (dialog->details->type_combo), &iter); + gtk_tree_model_get (gtk_combo_box_get_model (GTK_COMBO_BOX (dialog->details->type_combo)), + &iter, 0, &index, -1); + g_assert (index < G_N_ELEMENTS (methods) && index >= 0); + meth = &(methods[index]); + + if (meth->scheme == NULL) { + uri = gtk_editable_get_chars (GTK_EDITABLE (dialog->details->uri_entry), 0, -1); + /* FIXME: we should validate it in some way? */ + } else { + char *user, *port, *initial_path, *server, *folder ,*domain ; + char *t, *join; + gboolean free_initial_path, free_user, free_domain, free_port; + + server = gtk_editable_get_chars (GTK_EDITABLE (dialog->details->server_entry), 0, -1); + if (strlen (server) == 0) { + GtkWidget *dlg; + + dlg = gtk_message_dialog_new (GTK_WINDOW (dialog), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + _("Cannot Connect to Server. You must enter a name for the server.")); + + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dlg), + _("Please enter a name and try again.")); + + gtk_dialog_run (GTK_DIALOG (dlg)); + gtk_widget_destroy (dlg); + + g_free (server); + return; + } + + user = ""; + port = ""; + initial_path = ""; + domain = ""; + free_initial_path = FALSE; + free_user = FALSE; + free_domain = FALSE; + free_port = FALSE; + + /* FTP special case */ + if (meth->flags & IS_ANONYMOUS) { + user = "anonymous"; + + /* SMB special case */ + } else if (strcmp (meth->scheme, "smb") == 0) { + t = gtk_editable_get_chars (GTK_EDITABLE (dialog->details->share_entry), 0, -1); + initial_path = g_strconcat ("/", t, NULL); + free_initial_path = TRUE; + g_free (t); + } + + if (gtk_widget_get_parent (dialog->details->port_entry) != NULL) { + free_port = TRUE; + port = gtk_editable_get_chars (GTK_EDITABLE (dialog->details->port_entry), 0, -1); + } + folder = gtk_editable_get_chars (GTK_EDITABLE (dialog->details->folder_entry), 0, -1); + if (gtk_widget_get_parent (dialog->details->user_entry) != NULL) { + free_user = TRUE; + + t = gtk_editable_get_chars (GTK_EDITABLE (dialog->details->user_entry), 0, -1); + + user = g_uri_escape_string (t, G_URI_RESERVED_CHARS_ALLOWED_IN_USERINFO, FALSE); + + g_free (t); + } + if (gtk_widget_get_parent (dialog->details->domain_entry) != NULL) { + free_domain = TRUE; + + domain = gtk_editable_get_chars (GTK_EDITABLE (dialog->details->domain_entry), 0, -1); + + if (strlen (domain) != 0) { + t = user; + + user = g_strconcat (domain , ";" , t, NULL); + + if (free_user) { + g_free (t); + } + + free_user = TRUE; + } + } + + if (folder[0] != 0 && + folder[0] != '/') { + join = "/"; + } else { + join = ""; + } + + t = folder; + folder = g_strconcat (initial_path, join, t, NULL); + g_free (t); + + t = folder; + folder = g_uri_escape_string (t, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, FALSE); + g_free (t); + + uri = g_strdup_printf ("%s://%s%s%s%s%s%s", + meth->scheme, + user, (user[0] != 0) ? "@" : "", + server, + (port[0] != 0) ? ":" : "", port, + folder); + + if (free_initial_path) { + g_free (initial_path); + } + g_free (server); + if (free_port) { + g_free (port); + } + g_free (folder); + if (free_user) { + g_free (user); + } + if (free_domain) { + g_free (domain); + } + } + + gtk_widget_hide (GTK_WIDGET (dialog)); + + location = g_file_new_for_uri (uri); + g_free (uri); + + connect_server_dialog_present_uri (app, + location, + GTK_WIDGET (dialog)); +} + +static void +response_callback (BaobabRemoteConnectDialog *dialog, + int response_id, + GtkWindow *app) +{ + switch (response_id) { + case RESPONSE_CONNECT: + connect_to_server (dialog, app); + break; + case GTK_RESPONSE_NONE: + case GTK_RESPONSE_DELETE_EVENT: + case GTK_RESPONSE_CANCEL: + break; + default : + g_assert_not_reached (); + } +} + +static void +baobab_remote_connect_dialog_class_init (BaobabRemoteConnectDialogClass *class) +{ + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS (class); + gobject_class->finalize = baobab_remote_connect_dialog_finalize; +} + +static void +setup_for_type (BaobabRemoteConnectDialog *dialog) +{ + struct MethodInfo *meth; + int index, i; + GtkWidget *label, *table; + GtkTreeIter iter; + + /* Get our method info */ + gtk_combo_box_get_active_iter (GTK_COMBO_BOX (dialog->details->type_combo), &iter); + gtk_tree_model_get (gtk_combo_box_get_model (GTK_COMBO_BOX (dialog->details->type_combo)), + &iter, 0, &index, -1); + g_assert (index < G_N_ELEMENTS (methods) && index >= 0); + meth = &(methods[index]); + + if (gtk_widget_get_parent (dialog->details->uri_entry) != NULL) { + gtk_container_remove (GTK_CONTAINER (dialog->details->table), + dialog->details->uri_entry); + } + if (gtk_widget_get_parent (dialog->details->server_entry) != NULL) { + gtk_container_remove (GTK_CONTAINER (dialog->details->table), + dialog->details->server_entry); + } + if (gtk_widget_get_parent (dialog->details->share_entry) != NULL) { + gtk_container_remove (GTK_CONTAINER (dialog->details->table), + dialog->details->share_entry); + } + if (gtk_widget_get_parent (dialog->details->port_entry) != NULL) { + gtk_container_remove (GTK_CONTAINER (dialog->details->table), + dialog->details->port_entry); + } + if (gtk_widget_get_parent (dialog->details->folder_entry) != NULL) { + gtk_container_remove (GTK_CONTAINER (dialog->details->table), + dialog->details->folder_entry); + } + if (gtk_widget_get_parent (dialog->details->user_entry) != NULL) { + gtk_container_remove (GTK_CONTAINER (dialog->details->table), + dialog->details->user_entry); + } + if (gtk_widget_get_parent (dialog->details->domain_entry) != NULL) { + gtk_container_remove (GTK_CONTAINER (dialog->details->table), + dialog->details->domain_entry); + } + /* Destroy all labels */ + gtk_container_foreach (GTK_CONTAINER (dialog->details->table), + (GtkCallback) gtk_widget_destroy, NULL); + + + i = 1; + table = dialog->details->table; + + if (meth->scheme == NULL) { + label = gtk_label_new_with_mnemonic (_("_Location (URI):")); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, + 0, 1, + i, i+1, + GTK_FILL, GTK_FILL, + 0, 0); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), dialog->details->uri_entry); + gtk_widget_show (dialog->details->uri_entry); + gtk_table_attach (GTK_TABLE (table), dialog->details->uri_entry, + 1, 2, + i, i+1, + GTK_FILL | GTK_EXPAND, GTK_FILL, + 0, 0); + + i++; + + return; + } + + label = gtk_label_new_with_mnemonic (_("_Server:")); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, + 0, 1, + i, i+1, + GTK_FILL, GTK_FILL, + 0, 0); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), dialog->details->server_entry); + gtk_widget_show (dialog->details->server_entry); + gtk_table_attach (GTK_TABLE (table), dialog->details->server_entry, + 1, 2, + i, i+1, + GTK_FILL | GTK_EXPAND, GTK_FILL, + 0, 0); + + i++; + + label = gtk_label_new (_("Optional information:")); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, + 0, 2, + i, i+1, + GTK_FILL, GTK_FILL, + 0, 0); + + i++; + + if (meth->flags & SHOW_SHARE) { + label = gtk_label_new_with_mnemonic (_("_Share:")); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, + 0, 1, + i, i+1, + GTK_FILL, GTK_FILL, + 0, 0); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), dialog->details->share_entry); + gtk_widget_show (dialog->details->share_entry); + gtk_table_attach (GTK_TABLE (table), dialog->details->share_entry, + 1, 2, + i, i+1, + GTK_FILL | GTK_EXPAND, GTK_FILL, + 0, 0); + + i++; + } + + if (meth->flags & SHOW_PORT) { + label = gtk_label_new_with_mnemonic (_("_Port:")); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, + 0, 1, + i, i+1, + GTK_FILL, GTK_FILL, + 0, 0); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), dialog->details->port_entry); + gtk_widget_show (dialog->details->port_entry); + gtk_table_attach (GTK_TABLE (table), dialog->details->port_entry, + 1, 2, + i, i+1, + GTK_FILL | GTK_EXPAND, GTK_FILL, + 0, 0); + + i++; + } + + label = gtk_label_new_with_mnemonic (_("_Folder:")); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, + 0, 1, + i, i+1, + GTK_FILL, GTK_FILL, + 0, 0); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), dialog->details->folder_entry); + gtk_widget_show (dialog->details->folder_entry); + gtk_table_attach (GTK_TABLE (table), dialog->details->folder_entry, + 1, 2, + i, i+1, + GTK_FILL | GTK_EXPAND, GTK_FILL, + 0, 0); + + i++; + + if (meth->flags & SHOW_USER) { + label = gtk_label_new_with_mnemonic (_("_User Name:")); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, + 0, 1, + i, i+1, + GTK_FILL, GTK_FILL, + 0, 0); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), dialog->details->user_entry); + gtk_widget_show (dialog->details->user_entry); + gtk_table_attach (GTK_TABLE (table), dialog->details->user_entry, + 1, 2, + i, i+1, + GTK_FILL | GTK_EXPAND, GTK_FILL, + 0, 0); + + i++; + } + + if (meth->flags & SHOW_DOMAIN) { + label = gtk_label_new_with_mnemonic (_("_Domain Name:")); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, + 0, 1, + i, i+1, + GTK_FILL, GTK_FILL, + 0, 0); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), dialog->details->domain_entry); + gtk_widget_show (dialog->details->domain_entry); + gtk_table_attach (GTK_TABLE (table), dialog->details->domain_entry, + 1, 2, + i, i+1, + GTK_FILL | GTK_EXPAND, GTK_FILL, + 0, 0); + + i++; + } +} + +static void +combo_changed_callback (GtkComboBox *combo_box, + BaobabRemoteConnectDialog *dialog) +{ + setup_for_type (dialog); +} + +static void +port_insert_text (GtkEditable *editable, + const gchar *new_text, + gint new_text_length, + gint *position) +{ + int pos; + + if (new_text_length < 0) { + new_text_length = strlen (new_text); + } + + /* Only allow digits to be inserted as port number */ + for (pos = 0; pos < new_text_length; pos++) { + if (!g_ascii_isdigit (new_text[pos])) { + GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (editable)); + if (toplevel != NULL) { + gdk_window_beep (gtk_widget_get_window (toplevel)); + } + g_signal_stop_emission_by_name (editable, "insert_text"); + return; + } + } +} + +static void +baobab_remote_connect_dialog_init (BaobabRemoteConnectDialog *dialog) +{ + GtkWidget *label; + GtkWidget *table; + GtkWidget *combo; + GtkWidget *hbox; + GtkWidget *vbox; + GtkListStore *store; + GtkCellRenderer *renderer; + int i; + + dialog->details = g_new0 (BaobabRemoteConnectDialogDetails, 1); + + gtk_window_set_title (GTK_WINDOW (dialog), _("Connect to Server")); + gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE); + gtk_container_set_border_width (GTK_CONTAINER (dialog), 5); + gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), 2); + gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); + + vbox = gtk_vbox_new (FALSE, 6); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 5); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + vbox, FALSE, TRUE, 0); + gtk_widget_show (vbox); + + hbox = gtk_hbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (vbox), + hbox, FALSE, TRUE, 0); + gtk_widget_show (hbox); + + label = gtk_label_new_with_mnemonic (_("Service _type:")); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (hbox), + label, FALSE, FALSE, 0); + + dialog->details->type_combo = combo = gtk_combo_box_new (); + + /* each row contains: method index, textual description */ + store = gtk_list_store_new (2, G_TYPE_INT, G_TYPE_STRING); + gtk_combo_box_set_model (GTK_COMBO_BOX (combo), GTK_TREE_MODEL (store)); + g_object_unref (G_OBJECT (store)); + + renderer = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), renderer, TRUE); + gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (combo), renderer, "text", 1); + + for (i = 0; i < G_N_ELEMENTS (methods); i++) { + GtkTreeIter iter; + const gchar * const *supported; + int j; + + /* skip methods that don't have corresponding MateVFSMethods */ + supported = g_vfs_get_supported_uri_schemes (g_vfs_get_default ()); + + if (methods[i].scheme != NULL) { + gboolean found; + + found = FALSE; + for (j = 0; supported[j] != NULL; j++) { + if (strcmp (methods[i].scheme, supported[j]) == 0) { + found = TRUE; + break; + } + } + + if (!found) { + continue; + } + } + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + 0, i, + 1, get_method_description (&(methods[i])), + -1); + + + if (methods[i].flags & DEFAULT_METHOD) { + gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo), &iter); + } + } + + if (gtk_combo_box_get_active (GTK_COMBO_BOX (combo)) < 0) { + /* default method not available, use any other */ + gtk_combo_box_set_active (GTK_COMBO_BOX (combo), 0); + } + + gtk_widget_show (combo); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo); + gtk_box_pack_start (GTK_BOX (hbox), + combo, TRUE, TRUE, 0); + g_signal_connect (combo, "changed", + G_CALLBACK (combo_changed_callback), + dialog); + + + hbox = gtk_hbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (vbox), + hbox, FALSE, TRUE, 0); + gtk_widget_show (hbox); + + label = gtk_label_new_with_mnemonic (" "); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (hbox), + label, FALSE, FALSE, 0); + + + dialog->details->table = table = gtk_table_new (5, 2, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_table_set_col_spacings (GTK_TABLE (table), 12); + gtk_widget_show (table); + gtk_box_pack_start (GTK_BOX (hbox), + table, TRUE, TRUE, 0); + + //dialog->details->uri_entry = caja_location_entry_new (); + dialog->details->uri_entry = gtk_entry_new(); + dialog->details->server_entry = gtk_entry_new (); + dialog->details->share_entry = gtk_entry_new (); + dialog->details->port_entry = gtk_entry_new (); + g_signal_connect (dialog->details->port_entry, "insert_text", G_CALLBACK (port_insert_text), + NULL); + dialog->details->folder_entry = gtk_entry_new (); + dialog->details->domain_entry = gtk_entry_new (); + dialog->details->user_entry = gtk_entry_new (); + + gtk_entry_set_activates_default (GTK_ENTRY (dialog->details->uri_entry), TRUE); + gtk_entry_set_activates_default (GTK_ENTRY (dialog->details->server_entry), TRUE); + gtk_entry_set_activates_default (GTK_ENTRY (dialog->details->share_entry), TRUE); + gtk_entry_set_activates_default (GTK_ENTRY (dialog->details->port_entry), TRUE); + gtk_entry_set_activates_default (GTK_ENTRY (dialog->details->folder_entry), TRUE); + gtk_entry_set_activates_default (GTK_ENTRY (dialog->details->domain_entry), TRUE); + gtk_entry_set_activates_default (GTK_ENTRY (dialog->details->user_entry), TRUE); + + /* We need an extra ref so we can remove them from the table */ + g_object_ref (dialog->details->uri_entry); + g_object_ref (dialog->details->server_entry); + g_object_ref (dialog->details->share_entry); + g_object_ref (dialog->details->port_entry); + g_object_ref (dialog->details->folder_entry); + g_object_ref (dialog->details->domain_entry); + g_object_ref (dialog->details->user_entry); + + setup_for_type (dialog); + + gtk_dialog_add_button (GTK_DIALOG (dialog), + GTK_STOCK_CANCEL, + GTK_RESPONSE_CANCEL); + gtk_dialog_add_button (GTK_DIALOG (dialog), + _("_Scan"), + RESPONSE_CONNECT); + gtk_dialog_set_default_response (GTK_DIALOG (dialog), + RESPONSE_CONNECT); +} + +GtkWidget * +baobab_remote_connect_dialog_new (GtkWindow *window, GFile *location) +{ + GtkWidget *dialog; + + dialog = gtk_widget_new (BAOBAB_TYPE_REMOTE_CONNECT_DIALOG, NULL); + + g_signal_connect (dialog, "response", + G_CALLBACK (response_callback), + window); + + if (window) { + gtk_window_set_screen (GTK_WINDOW (dialog), + gtk_window_get_screen (GTK_WINDOW (window))); + } + + return dialog; +} + diff --git a/baobab/src/baobab-remote-connect-dialog.h b/baobab/src/baobab-remote-connect-dialog.h new file mode 100644 index 00000000..17095e5a --- /dev/null +++ b/baobab/src/baobab-remote-connect-dialog.h @@ -0,0 +1,59 @@ +/* Baobab - (C) 2005 Fabio Marzocca + + baobab-remote-connect-dialog.h + + Modified module from BaobabRemoteConnectDialog.h + Released under same licence + */ + +/* + * Caja + * + * Copyright (C) 2003 Red Hat, Inc. + * + * Caja 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. + * + * Caja 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; see the file COPYING. If not, + * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef BAOBAB_REMOTE_CONNECT_DIALOG_H +#define BAOBAB_REMOTE_CONNECT_DIALOG_H + +#include <gtk/gtk.h> +#include <gio/gio.h> + + +#define BAOBAB_TYPE_REMOTE_CONNECT_DIALOG (baobab_remote_connect_dialog_get_type()) +#define BAOBAB_REMOTE_CONNECT_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), BAOBAB_TYPE_REMOTE_CONNECT_DIALOG, BaobabRemoteConnectDialog)) +#define BAOBAB_REMOTE_CONNECT_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), BAOBAB_TYPE_REMOTE_CONNECT_DIALOG, BaobabRemoteConnectDialogClass)) +#define BAOBAB_IS_REMOTE_CONNECT_DIALOG(obj) (G_TYPE_INSTANCE_CHECK_TYPE ((obj), BAOBAB_TYPE_REMOTE_CONNECT_DIALOG) + +typedef struct _BaobabRemoteConnectDialog BaobabRemoteConnectDialog; +typedef struct _BaobabRemoteConnectDialogClass BaobabRemoteConnectDialogClass; +typedef struct _BaobabRemoteConnectDialogDetails BaobabRemoteConnectDialogDetails; + +struct _BaobabRemoteConnectDialog { + GtkDialog parent; + BaobabRemoteConnectDialogDetails *details; +}; + +struct _BaobabRemoteConnectDialogClass { + GtkDialogClass parent_class; +}; + +GType baobab_remote_connect_dialog_get_type (void); +GtkWidget* baobab_remote_connect_dialog_new (GtkWindow *window, + GFile *location); + +#endif /* BAOBAB_REMOTE_CONNECT_DIALOG_H */ diff --git a/baobab/src/baobab-ringschart.c b/baobab/src/baobab-ringschart.c new file mode 100644 index 00000000..4e51c01e --- /dev/null +++ b/baobab/src/baobab-ringschart.c @@ -0,0 +1,675 @@ +/* + * baobab-ringschart.c + * + * Copyright (C) 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 <math.h> +#include <string.h> + +#include <gtk/gtk.h> +#include <pango/pangocairo.h> + +#include "baobab-chart.h" +#include "baobab-ringschart.h" + +#define BAOBAB_RINGSCHART_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \ + BAOBAB_RINGSCHART_TYPE, \ + BaobabRingschartPrivate)) + +#define ITEM_BORDER_WIDTH 1 +#define CHART_PADDING 13 +#define ITEM_MIN_ANGLE 0.03 +#define EDGE_ANGLE 0.004 + +#define SUBFOLDER_TIP_PADDING 3 + +G_DEFINE_TYPE (BaobabRingschart, baobab_ringschart, BAOBAB_CHART_TYPE); + +typedef struct _BaobabRingschartItem BaobabRingschartItem; + +struct _BaobabRingschartItem +{ + gdouble min_radius; + gdouble max_radius; + gdouble start_angle; + gdouble angle; + gboolean continued; +}; + +struct _BaobabRingschartPrivate +{ + gboolean subfoldertips_enabled; + BaobabChartItem *highlighted_item; + guint tips_timeout_event; + GList *subtip_items; + gboolean drawing_subtips; + gint subtip_timeout; +}; + +static void baobab_ringschart_class_init (BaobabRingschartClass *class); +static void baobab_ringschart_init (BaobabRingschart *object); +static void baobab_ringschart_draw_sector (cairo_t *cr, + gdouble center_x, gdouble center_y, + gdouble radius, gdouble thickness, + gdouble init_angle, gdouble final_angle, + BaobabChartColor fill_color, + gboolean continued, guint border); +static void baobab_ringschart_draw_item (GtkWidget *chart, + cairo_t *cr, + BaobabChartItem *item, + gboolean highlighted); +static void baobab_ringschart_calculate_item_geometry (GtkWidget *chart, + BaobabChartItem *item); +static gboolean baobab_ringschart_is_point_over_item (GtkWidget *chart, + BaobabChartItem *item, + gdouble x, + gdouble y); +static void baobab_ringschart_get_point_min_rect (gdouble cx, gdouble cy, + gdouble radius, gdouble angle, + GdkRectangle *rect); +static void baobab_ringschart_get_item_rectangle (GtkWidget *chart, + BaobabChartItem *item); +static void baobab_ringschart_pre_draw (GtkWidget *chart, cairo_t *cr); +static void baobab_ringschart_post_draw (GtkWidget *chart, cairo_t *cr); + + +static void +baobab_ringschart_class_init (BaobabRingschartClass *class) +{ + GObjectClass *obj_class; + BaobabChartClass *chart_class; + + obj_class = G_OBJECT_CLASS (class); + chart_class = BAOBAB_CHART_CLASS (class); + + /* BaobabChart abstract methods */ + chart_class->draw_item = baobab_ringschart_draw_item; + chart_class->calculate_item_geometry = baobab_ringschart_calculate_item_geometry; + chart_class->is_point_over_item = baobab_ringschart_is_point_over_item; + chart_class->get_item_rectangle = baobab_ringschart_get_item_rectangle; + chart_class->pre_draw = baobab_ringschart_pre_draw; + chart_class->post_draw = baobab_ringschart_post_draw; + + g_type_class_add_private (obj_class, sizeof (BaobabRingschartPrivate)); +} + +static void +baobab_ringschart_init (BaobabRingschart *chart) +{ + BaobabRingschartPrivate *priv; + GtkSettings* settings; + gint timeout; + + priv = BAOBAB_RINGSCHART_GET_PRIVATE (chart); + + priv->subfoldertips_enabled = FALSE; + priv->highlighted_item = NULL; + priv->tips_timeout_event = 0; + priv->subtip_items = NULL; + priv->drawing_subtips = FALSE; + + settings = gtk_settings_get_default (); + g_object_get (G_OBJECT (settings), "gtk-tooltip-timeout", &timeout, NULL); + priv->subtip_timeout = 2 * timeout; +} + +static void +baobab_ringschart_draw_sector (cairo_t *cr, + gdouble center_x, + gdouble center_y, + gdouble radius, + gdouble thickness, + gdouble init_angle, + gdouble final_angle, + BaobabChartColor fill_color, + gboolean continued, + guint border) +{ + cairo_set_line_width (cr, border); + if (radius > 0) + cairo_arc (cr, center_x, center_y, radius, init_angle, final_angle); + cairo_arc_negative (cr, center_x, center_y, radius+thickness, + final_angle, init_angle); + cairo_close_path(cr); + + cairo_set_source_rgb (cr, fill_color.red, fill_color.green, fill_color.blue); + cairo_fill_preserve (cr); + cairo_set_source_rgb (cr, 0, 0, 0); + cairo_stroke (cr); + + if (continued) + { + cairo_set_line_width (cr, 3); + cairo_arc (cr, center_x, center_y, radius+thickness + 4, + init_angle+EDGE_ANGLE, final_angle-EDGE_ANGLE); + cairo_stroke (cr); + } +} + +static void +baobab_ringschart_draw_item (GtkWidget *chart, + cairo_t *cr, + BaobabChartItem *item, + gboolean highlighted) +{ + BaobabRingschartPrivate *priv; + BaobabRingschartItem *data; + BaobabChartColor fill_color; + GtkAllocation allocation; + + priv = BAOBAB_RINGSCHART_GET_PRIVATE (chart); + + data = (BaobabRingschartItem *) item->data; + + if (priv->drawing_subtips) + if ( (priv->highlighted_item) && (item->parent) && + (((BaobabChartItem *) item->parent->data) == priv->highlighted_item) ) + { + GList *node; + node = g_new0 (GList, 1); + node->data = (gpointer) item; + + node->next = priv->subtip_items; + if (priv->subtip_items) + priv->subtip_items->prev = node; + priv->subtip_items = node; + } + + baobab_chart_get_item_color (&fill_color, + data->start_angle / M_PI * 99, + item->depth, + highlighted); + + gtk_widget_get_allocation (chart, &allocation); + baobab_ringschart_draw_sector (cr, + allocation.width / 2, + allocation.height / 2, + data->min_radius, + data->max_radius - data->min_radius, + data->start_angle, + data->start_angle + data->angle, + fill_color, + data->continued, + ITEM_BORDER_WIDTH); +} + +static void +baobab_ringschart_calculate_item_geometry (GtkWidget *chart, + BaobabChartItem *item) +{ + BaobabRingschartItem *data; + BaobabRingschartItem p_data; + BaobabChartItem *parent = NULL; + GtkAllocation allocation; + + gdouble max_radius; + gdouble thickness; + guint max_depth; + + max_depth = baobab_chart_get_max_depth (chart); + + if (item->data == NULL) + item->data = g_new (BaobabRingschartItem, 1); + + data = (BaobabRingschartItem *) item->data; + + data->continued = FALSE; + item->visible = FALSE; + + gtk_widget_get_allocation (chart, &allocation); + max_radius = MIN (allocation.width / 2, allocation.height / 2) - CHART_PADDING; + thickness = max_radius / (max_depth + 1); + + if (item->parent == NULL) + { + data->min_radius = 0; + data->max_radius = thickness; + data->start_angle = 0; + data->angle = 2 * M_PI; + } + else + { + parent = (BaobabChartItem *) item->parent->data; + g_memmove (&p_data, parent->data, sizeof (BaobabRingschartItem)); + + data->min_radius = (item->depth) * thickness; + + if (data->min_radius + thickness > max_radius) + return; + else + data->max_radius = data->min_radius + thickness; + + data->angle = p_data.angle * item->rel_size / 100; + if (data->angle < ITEM_MIN_ANGLE) + return; + + data->start_angle = p_data.start_angle + p_data.angle * item->rel_start / 100; + + data->continued = (item->has_any_child) && (item->depth == max_depth); + + parent->has_visible_children = TRUE; + } + + item->visible = TRUE; + baobab_ringschart_get_item_rectangle (chart, item); +} + +static gboolean +baobab_ringschart_is_point_over_item (GtkWidget *chart, + BaobabChartItem *item, + gdouble x, + gdouble y) +{ + BaobabRingschartItem *data; + gdouble radius, angle; + GtkAllocation allocation; + + data = (BaobabRingschartItem *) item->data; + gtk_widget_get_allocation (chart, &allocation); + x = x - allocation.width / 2; + y = y - allocation.height / 2; + + radius = sqrt (x*x + y*y); + angle = atan2 (y, x); + angle = (angle > 0) ? angle : angle + 2*G_PI; + + return (radius >= data->min_radius) && + (radius <= data->max_radius) && + (angle >= data->start_angle) && + (angle <= data->start_angle + data->angle); +} + +static void +baobab_ringschart_get_point_min_rect (gdouble cx, + gdouble cy, + gdouble radius, + gdouble angle, + GdkRectangle *rect) +{ + gdouble x, y; + + x = cx + cos (angle) * radius; + y = cy + sin (angle) * radius; + + rect->x = MIN (rect->x, x); + rect->y = MIN (rect->y, y); + rect->width = MAX (rect->width, x); + rect->height = MAX (rect->height, y); +} + +static void +baobab_ringschart_get_item_rectangle (GtkWidget *chart, + BaobabChartItem *item) +{ + BaobabRingschartItem *data; + GdkRectangle rect; + gdouble cx, cy, r1, r2, a1, a2; + GtkAllocation allocation; + + data = (BaobabRingschartItem *) item->data; + + gtk_widget_get_allocation (chart, &allocation); + cx = allocation.width / 2; + cy = allocation.height / 2; + r1 = data->min_radius; + r2 = data->max_radius; + a1 = data->start_angle; + a2 = data->start_angle + data->angle; + + rect.x = allocation.width; + rect.y = allocation.height; + rect.width = 0; + rect.height = 0; + + baobab_ringschart_get_point_min_rect (cx, cy, r1, a1, &rect); + baobab_ringschart_get_point_min_rect (cx, cy, r2, a1, &rect); + baobab_ringschart_get_point_min_rect (cx, cy, r1, a2, &rect); + baobab_ringschart_get_point_min_rect (cx, cy, r2, a2, &rect); + + if ( (a1 <= M_PI/2) && (a2 >= M_PI/2) ) + rect.height = MAX (rect.height, cy + sin (M_PI/2) * r2); + + if ( (a1 <= M_PI) && (a2 >= M_PI) ) + rect.x = MIN (rect.x, cx + cos (M_PI) * r2); + + if ( (a1 <= M_PI*1.5) && (a2 >= M_PI*1.5) ) + rect.y = MIN (rect.y, cy + sin (M_PI*1.5) * r2); + + if ( (a1 <= M_PI*2) && (a2 >= M_PI*2) ) + rect.width = MAX (rect.width, cx + cos (M_PI*2) * r2); + + rect.width -= rect.x; + rect.height -= rect.y; + + item->rect = rect; +} + +gboolean +baobab_ringschart_subfolder_tips_timeout (gpointer data) +{ + BaobabRingschartPrivate *priv; + + priv = BAOBAB_RINGSCHART_GET_PRIVATE (data); + + priv->drawing_subtips = TRUE; + + gtk_widget_queue_draw (GTK_WIDGET (data)); + + return FALSE; +} + +void +baobab_ringschart_clean_subforlder_tips_state (GtkWidget *chart) +{ + BaobabRingschartPrivate *priv; + GList *node; + + priv = BAOBAB_RINGSCHART_GET_PRIVATE (chart); + + if (priv->drawing_subtips) + gtk_widget_queue_draw (chart); + + priv->drawing_subtips = FALSE; + + if (priv->highlighted_item == NULL) + return; + + if (priv->tips_timeout_event) + { + g_source_remove (priv->tips_timeout_event); + priv->tips_timeout_event = 0; + } + + priv->highlighted_item = NULL; + + /* Free subtip_items GList */ + node = priv->subtip_items; + while (node != NULL) + { + priv->subtip_items = node->next; + g_free (node); + + node = priv->subtip_items; + } + priv->subtip_items = NULL; +} + +static void +baobab_ringschart_draw_subfolder_tips (GtkWidget *chart, cairo_t *cr) +{ + BaobabRingschartPrivate *priv; + GList *node; + BaobabChartItem *item; + BaobabRingschartItem *data; + + gdouble q_angle, q_width, q_height; + + gdouble tip_x, tip_y; + gdouble middle_angle, middle_angle_n, middle_radius; + gdouble sector_center_x, sector_center_y; + gdouble a; + guint i; + GtkAllocation allocation; + + PangoLayout *layout; + PangoRectangle layout_rect; + gchar *markup = NULL; + + cairo_rectangle_t tooltip_rect; + GdkRectangle _rect, last_rect; + + priv = BAOBAB_RINGSCHART_GET_PRIVATE (chart); + + gtk_widget_get_allocation (chart, &allocation); + q_width = allocation.width / 2; + q_height = allocation.height / 2; + q_angle = atan2 (q_height, q_width); + + memset (&last_rect, 0, sizeof (GdkRectangle)); + + cairo_save (cr); + + node = priv->subtip_items; + while (node != NULL) + { + item = (BaobabChartItem *) node->data; + data = (BaobabRingschartItem *) item->data; + + /* get the middle angle */ + middle_angle = data->start_angle + data->angle / 2; + + /* normalize the middle angle (take it to the first quadrant) */ + middle_angle_n = middle_angle; + while (middle_angle_n > M_PI/2) + middle_angle_n -= M_PI; + middle_angle_n = ABS (middle_angle_n); + + /* get the pango layout and its enclosing rectangle */ + layout = gtk_widget_create_pango_layout (chart, NULL); + markup = g_strconcat ("<span size=\"small\">", item->name, "</span>", NULL); + pango_layout_set_markup (layout, markup, -1); + g_free (markup); + pango_layout_set_indent (layout, 0); + pango_layout_set_spacing (layout, 0); + pango_layout_get_pixel_extents (layout, NULL, &layout_rect); + + /* get the center point of the tooltip rectangle */ + if (middle_angle_n < q_angle) + { + tip_x = q_width - layout_rect.width/2 - SUBFOLDER_TIP_PADDING * 2; + tip_y = tan (middle_angle_n) * tip_x; + } + else + { + tip_y = q_height - layout_rect.height/2 - SUBFOLDER_TIP_PADDING * 2; + tip_x = tip_y / tan (middle_angle_n); + } + + /* get the tooltip rectangle */ + tooltip_rect.x = q_width + tip_x - layout_rect.width/2 - SUBFOLDER_TIP_PADDING; + tooltip_rect.y = q_height + tip_y - layout_rect.height/2 - SUBFOLDER_TIP_PADDING; + tooltip_rect.width = layout_rect.width + SUBFOLDER_TIP_PADDING * 2; + tooltip_rect.height = layout_rect.height + SUBFOLDER_TIP_PADDING * 2; + + /* Check tooltip's width is not greater than half of the widget */ + if (tooltip_rect.width > q_width) + { + g_object_unref (layout); + node = node->next; + continue; + } + + /* translate tooltip rectangle and edge angles to the original quadrant */ + a = middle_angle; + i = 0; + while (a > M_PI/2) + { + if (i % 2 == 0) + tooltip_rect.x = allocation.width - tooltip_rect.x + - tooltip_rect.width; + else + tooltip_rect.y = allocation.height - tooltip_rect.y + - tooltip_rect.height; + + i++; + a -= M_PI/2; + } + + /* get the GdkRectangle of the tooltip (with a little padding) */ + _rect.x = tooltip_rect.x - 1; + _rect.y = tooltip_rect.y - 1; + _rect.width = tooltip_rect.width + 2; + _rect.height = tooltip_rect.height + 2; + + /* Check if tooltip overlaps */ + if (! gdk_rectangle_intersect (&_rect, &last_rect, NULL)) + { + g_memmove (&last_rect, &_rect, sizeof (GdkRectangle)); + + /* Finally draw the tooltip to cairo! */ + + /* TODO: Do not hardcode colors */ + + /* avoid blurred lines */ + tooltip_rect.x = floor (tooltip_rect.x) + 0.5; + tooltip_rect.y = floor (tooltip_rect.y) + 0.5; + + middle_radius = data->min_radius + (data->max_radius - data->min_radius) / 2; + sector_center_x = q_width + middle_radius * cos (middle_angle); + sector_center_y = q_height + middle_radius * sin (middle_angle); + + /* draw line from sector center to tooltip center */ + cairo_set_line_width (cr, 1); + cairo_move_to (cr, sector_center_x, sector_center_y); + cairo_set_source_rgb (cr, 0.8275, 0.8431, 0.8118); /* tango: #d3d7cf */ + cairo_line_to (cr, tooltip_rect.x + tooltip_rect.width / 2, + tooltip_rect.y + tooltip_rect.height / 2); + cairo_stroke (cr); + + /* draw a tiny circle in sector center */ + cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.6666 ); + cairo_arc (cr, sector_center_x, sector_center_y, 1.0, 0, 2 * G_PI ); + cairo_stroke (cr); + + /* draw tooltip box */ + cairo_set_line_width (cr, 0.5); + cairo_rectangle (cr, tooltip_rect.x, tooltip_rect.y, + tooltip_rect.width, tooltip_rect.height); + cairo_set_source_rgb (cr, 0.8275, 0.8431, 0.8118); /* tango: #d3d7cf */ + cairo_fill_preserve(cr); + cairo_set_source_rgb (cr, 0.5333, 0.5412, 0.5216); /* tango: #888a85 */ + cairo_stroke (cr); + + /* draw the text inside the box */ + cairo_set_source_rgb (cr, 0, 0, 0); + cairo_move_to (cr, tooltip_rect.x + SUBFOLDER_TIP_PADDING, + tooltip_rect.y + SUBFOLDER_TIP_PADDING); + pango_cairo_show_layout (cr, layout); + cairo_set_line_width (cr, 1); + cairo_stroke (cr); + } + + /* Free stuff */ + g_object_unref (layout); + + node = node->next; + } + + cairo_restore (cr); +} + +static void +baobab_ringschart_pre_draw (GtkWidget *chart, cairo_t *cr) +{ + BaobabRingschartPrivate *priv; + BaobabChartItem *hl_item; + + priv = BAOBAB_RINGSCHART_GET_PRIVATE (chart); + + hl_item = baobab_chart_get_highlighted_item (chart); + + if ( (hl_item == NULL) || (! hl_item->has_visible_children) ) + { + baobab_ringschart_clean_subforlder_tips_state (chart); + + return; + } + + if (hl_item != priv->highlighted_item) + { + baobab_ringschart_clean_subforlder_tips_state (chart); + + priv->highlighted_item = hl_item; + + /* Launch timer to show subfolder tooltips */ + priv->tips_timeout_event = g_timeout_add (priv->subtip_timeout, + (GSourceFunc) baobab_ringschart_subfolder_tips_timeout, + (gpointer) chart); + } +} + +static void +baobab_ringschart_post_draw (GtkWidget *chart, cairo_t *cr) +{ + BaobabRingschartPrivate *priv; + + priv = BAOBAB_RINGSCHART_GET_PRIVATE (chart); + + if (priv->drawing_subtips) + { + /* Reverse the glist, which was created from the tail */ + priv->subtip_items = g_list_reverse (priv->subtip_items); + + baobab_ringschart_draw_subfolder_tips (chart, cr); + } +} + +/* Public functions start here */ + +/** + * baobab_ringschart_new: + * + * Constructor for the baobab_ringschart class + * + * Returns: a new #BaobabRingschart object + * + **/ +GtkWidget * +baobab_ringschart_new (void) +{ + return g_object_new (BAOBAB_RINGSCHART_TYPE, NULL); +} + +/** + * baobab_ringschart_set_subfoldertips_enabled: + * @chart: the #BaobabRingschart to set the + * @enabled: boolean to determine whether to show sub-folder tips. + * + * Enables/disables drawing tooltips of sub-forlders of the highlighted sector. + * + * Fails if @chart is not a #BaobabRingschart. + * + **/ +void +baobab_ringschart_set_subfoldertips_enabled (GtkWidget *chart, gboolean enabled) +{ + BaobabRingschartPrivate *priv; + + priv = BAOBAB_RINGSCHART_GET_PRIVATE (chart); + + priv->subfoldertips_enabled = enabled; + + if ( (! enabled) && (priv->drawing_subtips) ) + { + /* Turn off currently drawn tips */ + baobab_ringschart_clean_subforlder_tips_state (chart); + } +} diff --git a/baobab/src/baobab-ringschart.h b/baobab/src/baobab-ringschart.h new file mode 100644 index 00000000..276bd7fe --- /dev/null +++ b/baobab/src/baobab-ringschart.h @@ -0,0 +1,72 @@ +/* + * baobab-ringschart.h + * + * Copyright (C) 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]> + */ + +#ifndef __BAOBAB_RINGSCHART_H__ +#define __BAOBAB_RINGSCHART_H__ + +#include <gtk/gtk.h> +#include "baobab-chart.h" + +G_BEGIN_DECLS + +#define BAOBAB_RINGSCHART_TYPE (baobab_ringschart_get_type ()) +#define BAOBAB_RINGSCHART(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), BAOBAB_RINGSCHART_TYPE, BaobabRingschart)) +#define BAOBAB_RINGSCHART_CLASS(obj) (G_TYPE_CHECK_CLASS_CAST ((obj), BAOBAB_RINGSCHART, BaobabRingschartClass)) +#define BAOBAB_IS_RINGSCHART(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), BAOBAB_RINGSCHART_TYPE)) +#define BAOBAB_IS_RINGSCHART_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE ((obj), BAOBAB_RINGSCHART_TYPE)) +#define BAOBAB_RINGSCHART_GET_CLASS (G_TYPE_INSTANCE_GET_CLASS ((obj), BAOBAB_RINGSCHART_TYPE, BaobabRingschartClass)) + +typedef struct _BaobabRingschart BaobabRingschart; +typedef struct _BaobabRingschartClass BaobabRingschartClass; +typedef struct _BaobabRingschartPrivate BaobabRingschartPrivate; + +struct _BaobabRingschart +{ + BaobabChart parent; + + BaobabRingschartPrivate *priv; +}; + +struct _BaobabRingschartClass +{ + BaobabChartClass parent_class; +}; + +GType baobab_ringschart_get_type (void) G_GNUC_CONST; +GtkWidget *baobab_ringschart_new (void); +void baobab_ringschart_set_subfoldertips_enabled (GtkWidget *chart, + gboolean enabled); + +G_END_DECLS + +#endif diff --git a/baobab/src/baobab-scan.c b/baobab/src/baobab-scan.c new file mode 100644 index 00000000..8c85d859 --- /dev/null +++ b/baobab/src/baobab-scan.c @@ -0,0 +1,381 @@ +/* + * baobab-scan.c + * This file is part of baobab + * + * Copyright (C) 2005-2006 Fabio Marzocca <[email protected]> + * + * 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 + */ + + +#include <config.h> + +#include <string.h> + +#include <glib.h> +#include <gtk/gtk.h> +#include <gio/gio.h> + +#include "baobab.h" +#include "baobab-utils.h" + + +/* + Hardlinks handling. + + As long as we're optimistic about hardlinks count + over the whole system (250 files with st_nlink > 1 here), + we keep linear search. If it turns out to be an bottleneck + we can switch to an hash table or tree. + + TODO: get real timings about this code. find out the average + number of files with st_nlink > 1 on average computer. + + To save memory, we store only { inode, dev } instead of full + GFileInfo. + + EDIT: /me stupid. I realize that this code was not called that often + 1 call per file with st_nlink > 1. BUT, i'm using pdumpfs to backup + my /etc. pdumpfs massively uses hard links. So there are more than + 5000 files with st_nlink > 1. I believe this is the worst case. +*/ + +typedef struct { + guint64 inode; + dev_t device; +} BaobabHardLink; + +typedef GArray BaobabHardLinkArray; + +static BaobabHardLinkArray * +baobab_hardlinks_array_create (void) +{ + return g_array_new (FALSE, FALSE, sizeof(BaobabHardLink)); +} + +static gboolean +baobab_hardlinks_array_has (BaobabHardLinkArray *a, + BaobabHardLink *s) +{ + guint i; + + for (i = 0; i < a->len; ++i) { + BaobabHardLink *cur = &g_array_index (a, BaobabHardLink, i); + + /* + * cur->st_dev == s->st_dev is the common case and may be more + * expansive than cur->st_ino == s->st_ino + * so keep this order */ + if (cur->inode == s->inode && cur->device == s->device) + return TRUE; + } + + return FALSE; +} + +/* return FALSE if the element was already in the array */ +static gboolean +baobab_hardlinks_array_add (BaobabHardLinkArray *a, + GFileInfo *s) +{ + + if (g_file_info_has_attribute (s, G_FILE_ATTRIBUTE_UNIX_INODE) && + g_file_info_has_attribute (s, G_FILE_ATTRIBUTE_UNIX_DEVICE)) + { + BaobabHardLink hl; + + hl.inode = g_file_info_get_attribute_uint64 (s, + G_FILE_ATTRIBUTE_UNIX_INODE); + hl.device = g_file_info_get_attribute_uint32 (s, + G_FILE_ATTRIBUTE_UNIX_DEVICE); + + if (baobab_hardlinks_array_has (a, &hl)) + return FALSE; + + g_array_append_val (a, hl); + + return TRUE; + } + else + { + g_warning ("Could not obtain inode and device for hardlink"); + } + + return FALSE; +} + +static void +baobab_hardlinks_array_free (BaobabHardLinkArray *a) +{ +/* g_print ("HL len was %d\n", a->len); */ + + g_array_free (a, TRUE); +} + +#define BLOCK_SIZE 512 + +struct allsizes { + goffset size; + goffset alloc_size; + gint depth; +}; + +static const char *dir_attributes = \ + G_FILE_ATTRIBUTE_STANDARD_NAME "," \ + G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME "," \ + G_FILE_ATTRIBUTE_STANDARD_TYPE "," \ + G_FILE_ATTRIBUTE_STANDARD_SIZE "," \ + G_FILE_ATTRIBUTE_UNIX_BLOCKS "," \ + G_FILE_ATTRIBUTE_UNIX_NLINK "," \ + G_FILE_ATTRIBUTE_UNIX_INODE "," \ + G_FILE_ATTRIBUTE_UNIX_DEVICE "," \ + G_FILE_ATTRIBUTE_ACCESS_CAN_READ; + + +static gboolean +is_in_dot_gvfs (GFile *file) +{ + static GFile *dot_gvfs_dir = NULL; + GFile *parent; + gboolean res = FALSE; + + if (dot_gvfs_dir == NULL) + { + gchar *dot_gvfs; + + dot_gvfs = g_build_filename (g_get_home_dir (), ".gvfs", NULL); + + dot_gvfs_dir = g_file_new_for_path (dot_gvfs); + + g_free (dot_gvfs); + } + + parent = g_file_get_parent (file); + + if (parent != NULL) + { + res = g_file_equal (parent, dot_gvfs_dir); + g_object_unref (parent); + } + + return res; +} + +static struct allsizes +loopdir (GFile *file, + GFileInfo *info, + guint count, + BaobabHardLinkArray *hla, + gint current_depth) +{ + guint64 tempHLsize = 0; + gint elements = 0; + struct chan_data data; + struct allsizes retloop, temp; + GFileInfo *temp_info; + GFileEnumerator *file_enum; + gchar *dir_uri = NULL; + gchar *display_name = NULL; + gchar *parse_name = NULL; + GError *err = NULL; + + count++; + retloop.size = 0; + retloop.alloc_size = 0; + retloop.depth = 0; + + /* Skip the user excluded folders */ + if (baobab_is_excluded_location (file)) + goto exit; + + /* Skip the virtual file systems */ + if (is_virtual_filesystem (file)) + goto exit; + + /* FIXME: skip dirs in ~/.gvfs. It would be better to have a way + * to check if a file is a FUSE mountpoint instead of just + * hardcoding .gvfs */ + if (is_in_dot_gvfs (file)) + goto exit; + + parse_name = g_file_get_parse_name (file); + + if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_STANDARD_SIZE)) + retloop.size = g_file_info_get_size (info); + + if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_BLOCKS)) + retloop.alloc_size = BLOCK_SIZE * + g_file_info_get_attribute_uint64 (info, + G_FILE_ATTRIBUTE_UNIX_BLOCKS); + + if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME)) + display_name = g_strdup (g_file_info_get_display_name (info)); + else + /* paranoid fallback */ + display_name = g_filename_display_basename (g_file_info_get_name (info)); + + /* load up the file enumerator */ + file_enum = g_file_enumerate_children (file, + dir_attributes, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + NULL, + &err); + + if (file_enum == NULL) { + if (!g_error_matches (err, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED)) { + g_warning ("couldn't get dir enum for dir %s: %s\n", + parse_name, err->message); + } + goto exit; + } + + /* All skipped folders (i.e. bad type, excluded, /proc) must be + skept *before* this point. Everything passes the prefill-model + will be part of the GUI. */ + + /* prefill the model */ + data.size = 1; + data.alloc_size = 1; + data.depth = count - 1; + data.elements = -1; + data.display_name = display_name; + data.parse_name = parse_name; + data.tempHLsize = tempHLsize; + baobab_fill_model (&data); + + g_clear_error (&err); + while ((temp_info = g_file_enumerator_next_file (file_enum, + NULL, + &err)) != NULL) { + GFileType temp_type = g_file_info_get_file_type (temp_info); + if (baobab.STOP_SCANNING) { + g_object_unref (temp_info); + g_object_unref (file_enum); + goto exit; + } + + /* is a directory? */ + if (temp_type == G_FILE_TYPE_DIRECTORY) { + GFile *child_dir = g_file_get_child (file, + g_file_info_get_name (temp_info)); + temp = loopdir (child_dir, temp_info, count, hla, current_depth+1); + retloop.size += temp.size; + retloop.alloc_size += temp.alloc_size; + retloop.depth = ((temp.depth + 1) > retloop.depth) ? temp.depth + 1 : retloop.depth; + elements++; + g_object_unref (child_dir); + } + + /* is it a regular file? */ + else if (temp_type == G_FILE_TYPE_REGULAR) { + + /* check for hard links only on local files */ + if (g_file_info_has_attribute (temp_info, + G_FILE_ATTRIBUTE_UNIX_NLINK) && + g_file_info_get_attribute_uint32 (temp_info, + G_FILE_ATTRIBUTE_UNIX_NLINK) > 1) { + + if (!baobab_hardlinks_array_add (hla, temp_info)) { + + /* we already acconted for it */ + tempHLsize += g_file_info_get_size (temp_info); + g_object_unref (temp_info); + continue; + } + } + + if (g_file_info_has_attribute (temp_info, G_FILE_ATTRIBUTE_UNIX_BLOCKS)) { + retloop.alloc_size += BLOCK_SIZE * + g_file_info_get_attribute_uint64 (temp_info, + G_FILE_ATTRIBUTE_UNIX_BLOCKS); + } + retloop.size += g_file_info_get_size (temp_info); + elements++; + } + + /* ignore other types (symlinks, sockets, devices, etc) */ + + g_object_unref (temp_info); + } + + /* won't be an error if we've finished normally */ + if (err != NULL) { + g_warning ("error in dir %s: %s\n", + parse_name, err->message); + } + + data.display_name = display_name; + data.parse_name = parse_name; + data.size = retloop.size; + data.alloc_size = retloop.alloc_size; + data.depth = count - 1; + data.elements = elements; + data.tempHLsize = tempHLsize; + baobab_fill_model (&data); + g_object_unref (file_enum); + + exit: + g_free (dir_uri); + g_free (display_name); + g_free (parse_name); + if (err) + g_error_free (err); + + return retloop; +} + +void +baobab_scan_execute (GFile *location) +{ + BaobabHardLinkArray *hla; + GFileInfo *info; + GError *err = NULL; + GFileType ftype; + struct allsizes sizes; + + g_return_if_fail (location != NULL); + + /* NOTE: for the root of the scan we follow symlinks */ + info = g_file_query_info (location, + dir_attributes, + G_FILE_QUERY_INFO_NONE, + NULL, + &err); + + if (info == NULL) { + char *parse_name = g_file_get_parse_name (location); + g_warning ("couldn't get info for dir %s: %s\n", + parse_name, err->message); + g_free (parse_name); + g_error_free (err); + + return; + } + + ftype = g_file_info_get_file_type (info); + + if (ftype == G_FILE_TYPE_DIRECTORY) { + hla = baobab_hardlinks_array_create (); + + sizes = loopdir (location, info, 0, hla, 0); + baobab.model_max_depth = sizes.depth; + + baobab_hardlinks_array_free (hla); + } + + g_object_unref (info); +} + diff --git a/baobab/src/baobab-scan.h b/baobab/src/baobab-scan.h new file mode 100644 index 00000000..c6400d56 --- /dev/null +++ b/baobab/src/baobab-scan.h @@ -0,0 +1,30 @@ +/* + * baobab-scan.h + * This file is part of baobab + * + * Copyright (C) 2005-2006 Fabio Marzocca <[email protected]> + * + * 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 + */ + +#ifndef __BAOBAB_SCAN_H__ +#define __BAOBAB_SCAN_H__ + +#include <gio/gio.h> + +void baobab_scan_execute (GFile *location); + +#endif /* __BAOBAB_SCAN_H__ */ diff --git a/baobab/src/baobab-treemap.c b/baobab/src/baobab-treemap.c new file mode 100644 index 00000000..7262b819 --- /dev/null +++ b/baobab/src/baobab-treemap.c @@ -0,0 +1,342 @@ +/* + * baobab-treemap.c + * + * Copyright (C) 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: + * Fabio Marzocca <[email protected]> + * Paolo Borelli <[email protected]> + * Miguel Gomez <[email protected]> + * Eduardo Lima Mitev <[email protected]> + */ + +#include <math.h> +#include <string.h> +#include <gtk/gtk.h> + +#include "baobab-chart.h" +#include "baobab-treemap.h" + +#define BAOBAB_TREEMAP_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \ + BAOBAB_TREEMAP_TYPE, \ + BaobabTreemapPrivate)) + +#define ITEM_TEXT_PADDING 3 +#define ITEM_BORDER_WIDTH 1 +#define ITEM_PADDING 6 + +#define ITEM_MIN_WIDTH 3 +#define ITEM_MIN_HEIGHT 3 + +#define ITEM_SHOW_LABEL TRUE + +G_DEFINE_TYPE (BaobabTreemap, baobab_treemap, BAOBAB_CHART_TYPE); + +struct _BaobabTreemapPrivate +{ + guint max_visible_depth; + gboolean more_visible_childs; +}; + +static void baobab_treemap_class_init (BaobabTreemapClass *class); +static void baobab_treemap_init (BaobabTreemap *object); +static void baobab_treemap_draw_rectangle (GtkWidget *chart, + cairo_t *cr, + gdouble x, gdouble y, gdouble width, gdouble height, + BaobabChartColor fill_color, + const char *text, + gboolean show_text); +static void baobab_treemap_draw_item (GtkWidget *chart, + cairo_t *cr, + BaobabChartItem *item, + gboolean highlighted); +static void baobab_treemap_calculate_item_geometry (GtkWidget *chart, + BaobabChartItem *item); +static gboolean baobab_treemap_is_point_over_item (GtkWidget *chart, + BaobabChartItem *item, + gdouble x, + gdouble y); +static void baobab_treemap_get_item_rectangle (GtkWidget *chart, + BaobabChartItem *item); +guint baobab_treemap_can_zoom_in (GtkWidget *chart); +guint baobab_treemap_can_zoom_out (GtkWidget *chart); + +static void +baobab_treemap_class_init (BaobabTreemapClass *class) +{ + GObjectClass *obj_class; + BaobabChartClass *chart_class; + + obj_class = G_OBJECT_CLASS (class); + chart_class = BAOBAB_CHART_CLASS (class); + + /* BaobabChart abstract methods */ + chart_class->draw_item = baobab_treemap_draw_item; + chart_class->calculate_item_geometry = baobab_treemap_calculate_item_geometry; + chart_class->is_point_over_item = baobab_treemap_is_point_over_item; + chart_class->get_item_rectangle = baobab_treemap_get_item_rectangle; + chart_class->can_zoom_in = baobab_treemap_can_zoom_in; + chart_class->can_zoom_out = baobab_treemap_can_zoom_out; + + g_type_class_add_private (obj_class, sizeof (BaobabTreemapPrivate)); +} + +static void +baobab_treemap_init (BaobabTreemap *chart) +{ + BaobabTreemapPrivate *priv; + + priv = BAOBAB_TREEMAP_GET_PRIVATE (chart); + + chart->priv = priv; +} + +static void +baobab_treemap_draw_rectangle (GtkWidget *chart, + cairo_t *cr, + gdouble x, gdouble y, gdouble width, gdouble height, + BaobabChartColor fill_color, + const char *text, + gboolean show_text) +{ + guint border = ITEM_BORDER_WIDTH; + PangoRectangle rect; + PangoLayout *layout; + + cairo_stroke (cr); + + cairo_set_line_width (cr, border); + cairo_rectangle (cr, x + border, y + border, + width - border*2, + height - border*2); + cairo_set_source_rgb (cr, fill_color.red, fill_color.green, fill_color.blue); + cairo_fill_preserve (cr); + cairo_set_source_rgb (cr, 0, 0, 0); + cairo_stroke (cr); + + if ((show_text) && (ITEM_SHOW_LABEL)) + { + layout = gtk_widget_create_pango_layout (chart, NULL); + pango_layout_set_markup (layout, text, -1); + pango_layout_get_pixel_extents (layout, NULL, &rect); + + if ((rect.width + ITEM_TEXT_PADDING*2 <= width) && + (rect.height + ITEM_TEXT_PADDING*2 <= height)) + { + cairo_move_to (cr, x + width/2 - rect.width/2, + y + height/2 - rect.height/2); + pango_cairo_show_layout (cr, layout); + } + + g_object_unref (layout); + } +} + +static void +baobab_treemap_draw_item (GtkWidget *chart, + cairo_t *cr, + BaobabChartItem *item, + gboolean highlighted) +{ + cairo_rectangle_t * rect; + BaobabChartColor fill_color; + GtkAllocation allocation; + gdouble width, height; + + rect = (cairo_rectangle_t *) item->data; + + gtk_widget_get_allocation (chart, &allocation); + + if (item->depth % 2 != 0) + { + baobab_chart_get_item_color (&fill_color, rect->x/allocation.width*200, + item->depth, highlighted); + width = rect->width - ITEM_PADDING; + height = rect->height; + } + else + { + baobab_chart_get_item_color (&fill_color, rect->y/allocation.height*200, + item->depth, highlighted); + width = rect->width; + height = rect->height - ITEM_PADDING; + } + + baobab_treemap_draw_rectangle (chart, + cr, + rect->x, + rect->y, + width, + height, + fill_color, + item->name, + (! item->has_visible_children) ); +} + +static void +baobab_treemap_calculate_item_geometry (GtkWidget *chart, + BaobabChartItem *item) +{ + BaobabTreemapPrivate *priv; + cairo_rectangle_t p_area; + static cairo_rectangle_t *rect; + gdouble width, height; + BaobabChartItem *parent = NULL; + GtkAllocation allocation; + + priv = BAOBAB_TREEMAP (chart)->priv; + + if (item->depth == 0) + { + priv->max_visible_depth = 0; + priv->more_visible_childs = FALSE; + } + + item->visible = FALSE; + + if (item->parent == NULL) + { + gtk_widget_get_allocation (chart, &allocation); + p_area.x = 0 - ITEM_PADDING/2; + p_area.y = 0 - ITEM_PADDING/2; + p_area.width = allocation.width + ITEM_PADDING * 2; + p_area.height = allocation.height + ITEM_PADDING; + } + else + { + parent = (BaobabChartItem *) item->parent->data; + g_memmove (&p_area, parent->data, sizeof (cairo_rectangle_t)); + } + + if (item->data == NULL) + item->data = g_new (cairo_rectangle_t, 1); + + rect = (cairo_rectangle_t *) item->data; + + if (item->depth % 2 != 0) + { + width = p_area.width - ITEM_PADDING; + + rect->x = p_area.x + (item->rel_start * width / 100) + ITEM_PADDING; + rect->y = p_area.y + ITEM_PADDING; + rect->width = width * item->rel_size / 100; + rect->height = p_area.height - ITEM_PADDING * 3; + } + else + { + height = p_area.height - ITEM_PADDING; + + rect->x = p_area.x + ITEM_PADDING; + rect->y = p_area.y + (item->rel_start * height / 100) + ITEM_PADDING; + rect->width = p_area.width - ITEM_PADDING * 3; + rect->height = height * item->rel_size / 100; + } + + if ((rect->width - ITEM_PADDING < ITEM_MIN_WIDTH) || + (rect->height - ITEM_PADDING < ITEM_MIN_HEIGHT)) + return; + + rect->x = floor (rect->x) + 0.5; + rect->y = floor (rect->y) + 0.5; + rect->width = floor (rect->width); + rect->height = floor (rect->height); + + item->visible = TRUE; + + if (parent != NULL) + parent->has_visible_children = TRUE; + + baobab_treemap_get_item_rectangle (chart, item); + + if (item->depth == baobab_chart_get_max_depth (chart) + 1) + priv->more_visible_childs = TRUE; + else + priv->max_visible_depth = MAX (priv->max_visible_depth, item->depth); +} + +static gboolean +baobab_treemap_is_point_over_item (GtkWidget *chart, + BaobabChartItem *item, + gdouble x, + gdouble y) +{ + GdkRectangle *rect; + + rect = &item->rect; + return ((x >= rect->x) && (x <= rect->x + rect->width) && + (y >= rect->y) && (y <= rect->y + rect->height)); +} + +static void +baobab_treemap_get_item_rectangle (GtkWidget *chart, + BaobabChartItem *item) +{ + cairo_rectangle_t *_rect; + + _rect = (cairo_rectangle_t *) item->data; + + item->rect.x = _rect->x; + item->rect.y = _rect->y; + if (item->depth % 2 != 0) + { + item->rect.width = _rect->width - ITEM_PADDING; + item->rect.height = _rect->height; + } + else + { + item->rect.width = _rect->width; + item->rect.height = _rect->height - ITEM_PADDING; + } + +} + +guint +baobab_treemap_can_zoom_in (GtkWidget *chart) +{ + BaobabTreemapPrivate *priv; + + priv = BAOBAB_TREEMAP (chart)->priv; + + return MAX (0, (gint) (priv->max_visible_depth - 1)); +} + +guint +baobab_treemap_can_zoom_out (GtkWidget *chart) +{ + BaobabTreemapPrivate *priv; + + priv = BAOBAB_TREEMAP (chart)->priv; + + return priv->more_visible_childs ? 1 : 0; +} + +/* Public functions start here */ + +/** + * baobab_treemap_new: + * + * Constructor for the baobab_treemap class + * + * Returns: a new #BaobabTreemap object + * + **/ +GtkWidget* +baobab_treemap_new (void) +{ + return g_object_new (BAOBAB_TREEMAP_TYPE, NULL); +} diff --git a/baobab/src/baobab-treemap.h b/baobab/src/baobab-treemap.h new file mode 100644 index 00000000..734bc272 --- /dev/null +++ b/baobab/src/baobab-treemap.h @@ -0,0 +1,64 @@ +/* + * baobab-treemap.h + * + * Copyright (C) 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: + * Fabio Marzocca <[email protected]> + * Paolo Borelli <[email protected]> + * Miguel Gomez <[email protected]> + * Eduardo Lima Mitev <[email protected]> + */ + +#ifndef __BAOBAB_TREEMAP_H__ +#define __BAOBAB_TREEMAP_H__ + +#include <gtk/gtk.h> +#include "baobab-chart.h" + +G_BEGIN_DECLS + +#define BAOBAB_TREEMAP_TYPE (baobab_treemap_get_type ()) +#define BAOBAB_TREEMAP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), BAOBAB_TREEMAP_TYPE, BaobabTreemap)) +#define BAOBAB_TREEMAP_CLASS(obj) (G_TYPE_CHECK_CLASS_CAST ((obj), BAOBAB_TREEMAP, BaobabTreemapClass)) +#define BAOBAB_IS_TREEMAP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), BAOBAB_TREEMAP_TYPE)) +#define BAOBAB_IS_TREEMAP_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE ((obj), BAOBAB_TREEMAP_TYPE)) +#define BAOBAB_TREEMAP_GET_CLASS (G_TYPE_INSTANCE_GET_CLASS ((obj), BAOBAB_TREEMAP_TYPE, BaobabTreemapClass)) + +typedef struct _BaobabTreemap BaobabTreemap; +typedef struct _BaobabTreemapClass BaobabTreemapClass; +typedef struct _BaobabTreemapPrivate BaobabTreemapPrivate; + +struct _BaobabTreemap +{ + BaobabChart parent; + + BaobabTreemapPrivate *priv; +}; + +struct _BaobabTreemapClass +{ + BaobabChartClass parent_class; +}; + +GType baobab_treemap_get_type (void) G_GNUC_CONST; +GtkWidget* baobab_treemap_new (void); + +G_END_DECLS + +#endif diff --git a/baobab/src/baobab-treeview.c b/baobab/src/baobab-treeview.c new file mode 100644 index 00000000..2a3aad3f --- /dev/null +++ b/baobab/src/baobab-treeview.c @@ -0,0 +1,336 @@ +/* + * baobab-treeview.c + * This file is part of baobab + * + * Copyright (C) 2005-2006 Fabio Marzocca <[email protected]> + * + * 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 + */ + +#include <config.h> + +#include <gtk/gtk.h> +#include <glib.h> +#include <glib/gprintf.h> +#include <glib/gi18n.h> +#include <string.h> + +#include "baobab.h" +#include "baobab-treeview.h" +#include "baobab-cell-renderer-progress.h" +#include "baobab-utils.h" +#include "callbacks.h" + +static GtkTreeStore * +create_model (void) +{ + GtkTreeStore *mdl = gtk_tree_store_new (NUM_TREE_COLUMNS, + G_TYPE_STRING, /* COL_DIR_NAME */ + G_TYPE_STRING, /* COL_H_PARSENAME */ + G_TYPE_DOUBLE, /* COL_H_PERC */ + G_TYPE_STRING, /* COL_DIR_SIZE */ + G_TYPE_UINT64, /* COL_H_SIZE */ + G_TYPE_UINT64, /* COL_H_ALLOCSIZE */ + G_TYPE_STRING, /* COL_ELEMENTS */ + G_TYPE_INT, /* COL_H_ELEMENTS */ + G_TYPE_STRING, /* COL_HARDLINK */ + G_TYPE_UINT64 /* COL_H_HARDLINK */ + ); + + return mdl; +} + +static void +on_tv_row_expanded (GtkTreeView *treeview, + GtkTreeIter *arg1, + GtkTreePath *arg2, + gpointer data) +{ + gtk_tree_view_columns_autosize (treeview); +} + +static void +on_tv_cur_changed (GtkTreeView *treeview, gpointer data) +{ + GtkTreeIter iter; + gchar *parsename = NULL; + + gtk_tree_selection_get_selected (gtk_tree_view_get_selection (treeview), NULL, &iter); + + if (gtk_tree_store_iter_is_valid (baobab.model, &iter)) { + gtk_tree_model_get (GTK_TREE_MODEL (baobab.model), &iter, + COL_H_PARSENAME, &parsename, -1); + } +} + +static void +contents_changed (void) +{ + if (messageyesno (_("Rescan your home folder?"), + _("The content of your home folder has changed. Select rescan to update the disk usage details."), + GTK_MESSAGE_QUESTION, _("_Rescan"), baobab.window) == GTK_RESPONSE_OK) { + baobab_rescan_current_dir (); + } + else { + /* Just update the total */ + baobab_update_filesystem (); + } +} + +static gboolean +on_tv_button_press (GtkWidget *widget, + GdkEventButton *event, + gpointer data) +{ + GtkTreePath *path; + GtkTreeIter iter; + GFile *file; + + gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget), + event->x, event->y, + &path, NULL, NULL, NULL); + if (!path) + return TRUE; + + /* get the selected path */ + g_free (baobab.selected_path); + gtk_tree_model_get_iter (GTK_TREE_MODEL (baobab.model), &iter, + path); + gtk_tree_model_get (GTK_TREE_MODEL (baobab.model), &iter, + COL_H_PARSENAME, &baobab.selected_path, -1); + + file = g_file_parse_name (baobab.selected_path); + + if (baobab.CONTENTS_CHANGED_DELAYED) { + GFile *home_file; + + home_file = g_file_new_for_path (g_get_home_dir ()); + if (g_file_has_prefix (file, home_file)) { + baobab.CONTENTS_CHANGED_DELAYED = FALSE; + if (baobab.STOP_SCANNING) { + contents_changed (); + } + } + g_object_unref (home_file); + } + + /* right-click */ + if (event->button == 3) { + + if (g_file_query_exists (file, NULL)) { + popupmenu_list (path, event, can_trash_file (file)); + } + } + + gtk_tree_path_free (path); + g_object_unref (file); + + return FALSE; +} + +static gboolean +baobab_treeview_equal_func (GtkTreeModel *model, + gint column, + const gchar *key, + GtkTreeIter *iter, + gpointer data) +{ + gboolean results = TRUE; + gchar *name; + + gtk_tree_model_get (model, iter, 1, &name, -1); + + if (name != NULL) { + gchar * casefold_key; + gchar * casefold_name; + + casefold_key = g_utf8_casefold (key, -1); + casefold_name = g_utf8_casefold (name, -1); + + if ((casefold_key != NULL) && + (casefold_name != NULL) && + (strstr (casefold_name, casefold_key) != NULL)) { + results = FALSE; + } + g_free (casefold_key); + g_free (casefold_name); + g_free (name); + } + return results; +} + +static void +perc_cell_data_func (GtkTreeViewColumn *col, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer user_data) +{ + gdouble perc; + gchar textperc[10]; + + gtk_tree_model_get (model, iter, COL_H_PERC, &perc, -1); + + if (perc < 0) + strcpy (textperc, "-.- %"); + else if (perc == 100.0) + strcpy (textperc, "100 %"); + else + g_sprintf (textperc, " %.1f %%", perc); + + g_object_set (renderer, "text", textperc, NULL); +} + +GtkWidget * +create_directory_treeview (void) +{ + GtkCellRenderer *cell; + GtkTreeViewColumn *col; + GtkWidget *scrolled; + + GtkWidget *tvw = GTK_WIDGET (gtk_builder_get_object (baobab.main_ui, "treeview1")); + + g_signal_connect (tvw, "row-expanded", + G_CALLBACK (on_tv_row_expanded), NULL); + g_signal_connect (tvw, "cursor-changed", + G_CALLBACK (on_tv_cur_changed), NULL); + g_signal_connect (tvw, "button-press-event", + G_CALLBACK (on_tv_button_press), NULL); + + /* dir name column */ + g_signal_connect (gtk_tree_view_get_selection (GTK_TREE_VIEW (tvw)), "changed", + G_CALLBACK (on_tv_selection_changed), NULL); + cell = gtk_cell_renderer_text_new (); + col = gtk_tree_view_column_new_with_attributes (NULL, cell, "markup", + COL_DIR_NAME, "text", + COL_DIR_NAME, NULL); + gtk_tree_view_column_set_sort_column_id (col, COL_DIR_NAME); + gtk_tree_view_column_set_reorderable (col, TRUE); + gtk_tree_view_column_set_title (col, _("Folder")); + gtk_tree_view_column_set_sizing (col, GTK_TREE_VIEW_COLUMN_AUTOSIZE); + gtk_tree_view_column_set_resizable (col, TRUE); + gtk_tree_view_append_column (GTK_TREE_VIEW (tvw), col); + + /* percentage bar & text column */ + col = gtk_tree_view_column_new (); + + cell = baobab_cell_renderer_progress_new (); + gtk_tree_view_column_pack_start (col, cell, FALSE); + gtk_tree_view_column_set_attributes (col, cell, "perc", + COL_H_PERC, NULL); + + cell = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (col, cell, TRUE); + gtk_tree_view_column_set_cell_data_func (col, cell, + perc_cell_data_func, + NULL, NULL); + + g_object_set (G_OBJECT (cell), "xalign", (gfloat) 1.0, NULL); + gtk_tree_view_column_set_sort_column_id (col, COL_H_PERC); + gtk_tree_view_column_set_reorderable (col, TRUE); + gtk_tree_view_column_set_title (col, _("Usage")); + gtk_tree_view_column_set_sizing (col, GTK_TREE_VIEW_COLUMN_AUTOSIZE); + gtk_tree_view_column_set_resizable (col, FALSE); + gtk_tree_view_append_column (GTK_TREE_VIEW (tvw), col); + + /* directory size column */ + cell = gtk_cell_renderer_text_new (); + col = gtk_tree_view_column_new_with_attributes (NULL, cell, "markup", + COL_DIR_SIZE, "text", + COL_DIR_SIZE, NULL); + g_object_set (G_OBJECT (cell), "xalign", (gfloat) 1.0, NULL); + gtk_tree_view_column_set_sort_column_id (col, + baobab.show_allocated ? COL_H_ALLOCSIZE : COL_H_SIZE); + gtk_tree_view_column_set_reorderable (col, TRUE); + gtk_tree_view_column_set_title (col, _("Size")); + gtk_tree_view_column_set_sizing (col, GTK_TREE_VIEW_COLUMN_AUTOSIZE); + gtk_tree_view_column_set_resizable (col, TRUE); + gtk_tree_view_append_column (GTK_TREE_VIEW (tvw), col); + + /* store this column, we need it when toggling 'allocated' */ + g_object_set_data (G_OBJECT (tvw), "baobab_size_col", col); + + /* objects column */ + cell = gtk_cell_renderer_text_new (); + col = gtk_tree_view_column_new_with_attributes (NULL, cell, "markup", + COL_ELEMENTS, "text", + COL_ELEMENTS, NULL); + g_object_set (G_OBJECT (cell), "xalign", (gfloat) 1.0, NULL); + gtk_tree_view_column_set_sort_column_id (col, COL_H_ELEMENTS); + gtk_tree_view_column_set_reorderable (col, TRUE); + gtk_tree_view_column_set_title (col, _("Contents")); + gtk_tree_view_column_set_sizing (col, GTK_TREE_VIEW_COLUMN_AUTOSIZE); + gtk_tree_view_column_set_resizable (col, TRUE); + gtk_tree_view_append_column (GTK_TREE_VIEW (tvw), col); + + /* hardlink column */ + cell = gtk_cell_renderer_text_new (); + col = gtk_tree_view_column_new_with_attributes (NULL, cell, "markup", + COL_HARDLINK, "text", + COL_HARDLINK, NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (tvw), col); + + gtk_tree_view_collapse_all (GTK_TREE_VIEW (tvw)); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (tvw), FALSE); + scrolled = GTK_WIDGET (gtk_builder_get_object (baobab.main_ui, "scrolledwindow1")); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + + gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (tvw), + baobab_treeview_equal_func, + NULL, NULL); + + baobab.model = create_model (); + + /* By default, sort by size */ + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (baobab.model), + baobab.show_allocated ? COL_H_ALLOCSIZE : COL_H_SIZE, + GTK_SORT_DESCENDING); + + gtk_tree_view_set_model (GTK_TREE_VIEW (tvw), + GTK_TREE_MODEL (baobab.model)); + g_object_unref (baobab.model); + + return tvw; +} + +void +baobab_treeview_show_allocated_size (GtkWidget *tv, + gboolean show_allocated) +{ + gint sort_id; + gint new_sort_id; + GtkSortType order; + GtkTreeViewColumn *size_col; + + g_return_if_fail (GTK_IS_TREE_VIEW (tv)); + + gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (baobab.model), + &sort_id, &order); + + /* set the sort id for the size column */ + size_col = g_object_get_data (G_OBJECT (tv), "baobab_size_col"); + new_sort_id = show_allocated ? COL_H_ALLOCSIZE : COL_H_SIZE; + gtk_tree_view_column_set_sort_column_id (size_col, new_sort_id); + + /* if we are currently sorted on size or allocated size, + * then trigger a resort (with the same order) */ + if (sort_id == COL_H_SIZE || sort_id == COL_H_ALLOCSIZE) { + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (baobab.model), + new_sort_id, order); + } +} diff --git a/baobab/src/baobab-treeview.h b/baobab/src/baobab-treeview.h new file mode 100644 index 00000000..38aca412 --- /dev/null +++ b/baobab/src/baobab-treeview.h @@ -0,0 +1,47 @@ +/* + * baobab-treeview.h + * This file is part of baobab + * + * Copyright (C) 2005-2006 Fabio Marzocca <[email protected]> + * + * 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 + */ + +#ifndef __BAOBAB_TV_H__ +#define __BAOBAB_TV_H__ + +#include <gtk/gtk.h> + +/* tree model columns (_H_ are hidden) */ +enum +{ + COL_DIR_NAME, + COL_H_PARSENAME, + COL_H_PERC, + COL_DIR_SIZE, + COL_H_SIZE, + COL_H_ALLOCSIZE, + COL_ELEMENTS, + COL_H_ELEMENTS, + COL_HARDLINK, + COL_H_HARDLINK, + NUM_TREE_COLUMNS +}; + +GtkWidget *create_directory_treeview (void); +void baobab_treeview_show_allocated_size (GtkWidget *tv, gboolean show_allocated); + +#endif /* __BAOBAB_TV_H__ */ diff --git a/baobab/src/baobab-utils.c b/baobab/src/baobab-utils.c new file mode 100644 index 00000000..12fbcbfb --- /dev/null +++ b/baobab/src/baobab-utils.c @@ -0,0 +1,567 @@ +/* + * baobab-utils.c + * This file is part of baobab + * + * Copyright (C) 2005-2006 Fabio Marzocca <[email protected]> + * + * 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 + */ + +#include <config.h> + +#include <glib.h> +#include <glib/gstdio.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <string.h> +#include <glib/gi18n.h> +#include <glib/gprintf.h> +#include <gtk/gtk.h> +#include <gio/gio.h> +#include <glibtop/mountlist.h> +#include <glibtop/fsusage.h> + +#include "baobab.h" +#include "baobab-treeview.h" +#include "baobab-utils.h" +#include "callbacks.h" + +void +baobab_get_filesystem (BaobabFS *fs) +{ + size_t i; + glibtop_mountlist mountlist; + glibtop_mountentry *mountentries; + + memset (fs, 0, sizeof *fs); + + mountentries = glibtop_get_mountlist (&mountlist, FALSE); + + for (i = 0; i < mountlist.number; ++i) { + GFile *file; + glibtop_fsusage fsusage; + + file = g_file_new_for_path (mountentries[i].mountdir); + + if (!baobab_is_excluded_location (file)) { + + glibtop_get_fsusage (&fsusage, mountentries[i].mountdir); + + fs->total += fsusage.blocks * fsusage.block_size; + fs->avail += fsusage.bfree * fsusage.block_size; + fs->used += (fsusage.blocks - fsusage.bfree) * fsusage.block_size; + } + + g_object_unref (file); + } + + g_free (mountentries); +} + +void +filechooser_cb (GtkWidget *chooser, + gint response, + gpointer data) +{ + if (response == GTK_RESPONSE_OK) { + gchar *filename; + GFile *file; + + filename = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (chooser)); + gtk_widget_hide (chooser); + + file = g_file_new_for_uri (filename); + baobab_scan_location (file); + g_free (filename); + g_object_unref (file); + } + else { + gtk_widget_hide (chooser); + } +} + +/* + * GtkFileChooser to select a directory to scan + */ +gchar * +dir_select (gboolean SEARCH, GtkWidget *parent) +{ + static GtkWidget *file_chooser = NULL; + GtkWidget *toggle; + + if (file_chooser == NULL) { + file_chooser = gtk_file_chooser_dialog_new (_("Select Folder"), + GTK_WINDOW (parent), + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, + GTK_STOCK_CANCEL, + GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, + GTK_RESPONSE_OK, NULL); + + gtk_file_chooser_set_show_hidden (GTK_FILE_CHOOSER (file_chooser), FALSE); + gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (file_chooser), + g_get_home_dir ()); + /* add extra widget */ + toggle = gtk_check_button_new_with_mnemonic (_("_Show hidden folders")); + gtk_widget_show (toggle); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), FALSE); + g_signal_connect ((gpointer) toggle, "toggled", + G_CALLBACK (on_toggled), file_chooser); + gtk_file_chooser_set_extra_widget (GTK_FILE_CHOOSER (file_chooser), + toggle); + + g_signal_connect (file_chooser, "response", + G_CALLBACK (filechooser_cb), NULL); + g_signal_connect (file_chooser, "destroy", + G_CALLBACK (gtk_widget_destroyed), &file_chooser); + + gtk_window_set_modal (GTK_WINDOW (file_chooser), TRUE); + gtk_window_set_position (GTK_WINDOW (file_chooser), GTK_WIN_POS_CENTER_ON_PARENT); + } + + gtk_widget_show (GTK_WIDGET (file_chooser)); + + return NULL; +} + +void +on_toggled (GtkToggleButton *togglebutton, gpointer dialog) +{ + gtk_file_chooser_set_show_hidden (GTK_FILE_CHOOSER (dialog), + !gtk_file_chooser_get_show_hidden + (GTK_FILE_CHOOSER (dialog))); +} + +void +set_ui_action_sens (const gchar *name, gboolean sens) +{ + GtkAction *a; + + a = GTK_ACTION (gtk_builder_get_object (baobab.main_ui, name)); + gtk_action_set_sensitive (a, sens); +} + +void +set_ui_widget_sens (const gchar *name, gboolean sens) +{ + GtkWidget *w; + + w = GTK_WIDGET (gtk_builder_get_object (baobab.main_ui, name)); + gtk_widget_set_sensitive (w, sens); +} + +gboolean +show_bars (GtkTreeModel *mdl, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + GtkTreeIter parent; + gdouble perc; + gint readelements, size_col; + guint64 refsize, size; + char *sizecstr = NULL; + + if (baobab.show_allocated) + size_col = (gint) COL_H_ALLOCSIZE; + else + size_col = (gint) COL_H_SIZE; + + if (gtk_tree_model_iter_parent (mdl, &parent, iter)) { + gtk_tree_model_get (mdl, iter, COL_H_ELEMENTS, + &readelements, -1); + + if (readelements == -1) { + gtk_tree_store_set (GTK_TREE_STORE (mdl), iter, + COL_DIR_SIZE, "--", + COL_ELEMENTS, "--", -1); + return FALSE; + } + + gtk_tree_model_get (mdl, &parent, COL_H_ELEMENTS, + &readelements, -1); + + gtk_tree_model_get (mdl, iter, size_col, &size, -1); + sizecstr = g_format_size_for_display (size); + + if (readelements == -1) { + gtk_tree_store_set (GTK_TREE_STORE (mdl), iter, + COL_DIR_SIZE, sizecstr, -1); + + g_free (sizecstr); + return FALSE; + } + + gtk_tree_model_get (mdl, &parent, size_col, &refsize, -1); + perc = (refsize != 0) ? ((gdouble) size * 100) / (gdouble) refsize : 0.0; + + gtk_tree_store_set (GTK_TREE_STORE (mdl), iter, + COL_DIR_SIZE, sizecstr, + COL_H_PERC, perc, -1); + + g_free (sizecstr); + } else { + gtk_tree_model_get (mdl, iter, COL_H_ELEMENTS, + &readelements, -1); + + if (readelements != -1) { + gtk_tree_model_get (mdl, iter, size_col, &size, + -1); + sizecstr = g_format_size_for_display (size); + + gtk_tree_store_set (GTK_TREE_STORE (mdl), iter, + COL_H_PERC, 100.0, + COL_DIR_SIZE, sizecstr, -1); + + g_free (sizecstr); + } + else { + gtk_tree_store_set (GTK_TREE_STORE (mdl), iter, + COL_DIR_SIZE, "--", + COL_ELEMENTS, "--", -1); + } + } + + return FALSE; +} + +void +message (const gchar *primary_msg, + const gchar *secondary_msg, + GtkMessageType type, + GtkWidget *parent) +{ + GtkWidget *dialog; + dialog = gtk_message_dialog_new (GTK_WINDOW (parent), + GTK_DIALOG_DESTROY_WITH_PARENT, + type, + GTK_BUTTONS_OK, "%s", primary_msg); + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), + "%s", secondary_msg); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); +} + +gint +messageyesno (const gchar *primary_msg, + const gchar *secondary_msg, + GtkMessageType type, + gchar *ok_button, + GtkWidget *parent) +{ + GtkWidget *dialog; + GtkWidget *button; + gint response; + + dialog = gtk_message_dialog_new (GTK_WINDOW (parent), + GTK_DIALOG_DESTROY_WITH_PARENT, + type, + GTK_BUTTONS_CANCEL, + "%s", primary_msg); + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), + "%s", secondary_msg); + + button = gtk_button_new_with_mnemonic (ok_button); + gtk_widget_set_can_default (button, TRUE); + gtk_widget_show (button); + + gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button, GTK_RESPONSE_OK); + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); + response = gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + + return response; +} + +gboolean +baobab_check_dir (GFile *file) +{ + GFileInfo *info; + GError *error = NULL; + gboolean ret = TRUE; + + if (baobab_is_excluded_location (file)) { + message("", _("Cannot check an excluded folder!"), GTK_MESSAGE_INFO, baobab.window); + return FALSE; + } + + info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_TYPE, + G_FILE_QUERY_INFO_NONE, + NULL, + &error); + + if (!info) { + message("", error->message, GTK_MESSAGE_INFO, baobab.window); + g_error_free (error); + + return FALSE; + } + + if ((g_file_info_get_file_type (info) != G_FILE_TYPE_DIRECTORY) || + is_virtual_filesystem(file)) { + + char *error_msg = NULL; + gchar *name = NULL; + + name = g_file_get_parse_name (file); + error_msg = g_strdup_printf (_("\"%s\" is not a valid folder"), + name); + + message (error_msg, _("Could not analyze disk usage."), + GTK_MESSAGE_ERROR, baobab.window); + + g_free (error_msg); + g_free (name); + ret = FALSE; + } + + g_object_unref(info); + + return ret; +} + +static void +add_popupmenu_item (GtkMenu *pmenu, const gchar *label, const gchar *stock, GCallback item_cb) +{ + GtkWidget *item; + GtkWidget *image; + + item = gtk_image_menu_item_new_with_mnemonic (label); + image = gtk_image_new_from_stock (stock, GTK_ICON_SIZE_MENU); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image); + + g_signal_connect (item, "activate", item_cb, NULL); + + gtk_container_add (GTK_CONTAINER (pmenu), item); +} + +void +popupmenu_list (GtkTreePath *path, GdkEventButton *event, gboolean can_trash) +{ + GtkWidget *pmenu; + gchar *path_to_string; + + /* path_to_string is freed in callback function */ + path_to_string = gtk_tree_path_to_string (path); + + pmenu = gtk_menu_new (); + + add_popupmenu_item (GTK_MENU (pmenu), + _("_Open Folder"), + "gtk-open", + G_CALLBACK (open_file_cb)); + + if (baobab.is_local && can_trash) { + add_popupmenu_item (GTK_MENU (pmenu), + _("Mo_ve to Trash"), + "gtk-delete", + G_CALLBACK (trash_dir_cb)); + } + + gtk_widget_show_all (pmenu); + gtk_menu_popup (GTK_MENU (pmenu), NULL, NULL, NULL, NULL, + event->button, event->time); +} + +void +open_file_with_application (GFile *file) +{ + GAppInfo *application; + gchar *primary; + GFileInfo *info; + gchar *uri_scheme; + const char *content; + gboolean local = FALSE; + + info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, + G_FILE_QUERY_INFO_NONE, + NULL, + NULL); + if (!info) return; + + uri_scheme = g_file_get_uri_scheme (file); + if (g_ascii_strcasecmp(uri_scheme,"file") == 0) local = TRUE; + + content = g_file_info_get_content_type (info); + application = g_app_info_get_default_for_type (content, TRUE); + + if (!application) { + primary = g_strdup_printf (_("Could not open folder \"%s\""), + g_file_get_basename (file)); + message (primary, + _("There is no installed viewer capable " + "of displaying the folder."), + GTK_MESSAGE_ERROR, + baobab.window); + g_free (primary); + } + else { + GList *uris = NULL; + gchar *uri; + + uri = g_file_get_uri (file); + uris = g_list_append (uris, uri); + g_app_info_launch_uris (application, uris, NULL, NULL); + + g_list_free (uris); + g_free (uri); + } + + g_free (uri_scheme); + + if (application) + g_object_unref (application); + + g_object_unref (info); +} + +gboolean +can_trash_file (GFile *file) +{ + GFileInfo *info; + gboolean can_trash = FALSE; + + info = g_file_query_info (file, + G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, + G_FILE_QUERY_INFO_NONE, + NULL, + NULL); + + if (info) { + if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH)) { + can_trash = g_file_info_get_attribute_boolean (info, + G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH); + } + + g_object_unref (info); + } + + return can_trash; +} + +gboolean +trash_file (GFile *file) +{ + GError *error = NULL; + + if (!g_file_trash (file, NULL, &error)) { + GFileInfo *info; + char *str = NULL; + char *mess; + + info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME, + G_FILE_QUERY_INFO_NONE, + NULL, + NULL); + + if (info) { + const char *displayname = g_file_info_get_display_name (info); + if (displayname) + str = g_strdup_printf (_("Could not move \"%s\" to the Trash"), + displayname); + + g_object_unref (info); + } + + /* fallback */ + if (str == NULL) + str = g_strdup (_("Could not move file to the Trash")); + + mess = g_strdup_printf (_("Details: %s"), error->message); + message (str, mess, GTK_MESSAGE_ERROR, baobab.window); + g_free (str); + g_free (mess); + g_error_free (error); + + return FALSE; + } + + return TRUE; +} + +gchar * +baobab_mateconf_get_string_with_default (MateConfClient *client, + const gchar *key, + const gchar *def) +{ + gchar *val; + + val = mateconf_client_get_string (client, key, NULL); + return val ? val : g_strdup (def); +} + +gboolean +baobab_help_display (GtkWindow *parent, + const gchar *file_name, + const gchar *link_id) +{ + GError *error = NULL; + char *uri; + gboolean ret; + + uri = (link_id) ? + g_strdup_printf ("ghelp:%s#%s", file_name, link_id) : + g_strdup_printf ("ghelp:%s", file_name); + + ret = gtk_show_uri (gtk_window_get_screen (parent), + uri, gtk_get_current_event_time (), &error); + g_free (uri); + + if (error != NULL) { + GtkWidget *dialog; + + dialog = gtk_message_dialog_new (parent, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + _("There was an error displaying help.")); + + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), + "%s", error->message); + + g_signal_connect (G_OBJECT (dialog), "response", + G_CALLBACK (gtk_widget_destroy), NULL); + + gtk_widget_show (dialog); + + g_error_free (error); + } + + return ret; +} + +gboolean +is_virtual_filesystem (GFile *file) +{ + gboolean ret = FALSE; + char *path; + + path = g_file_get_path (file); + + /* FIXME: we need a better way to check virtual FS */ + if (path != NULL) { + if ((strcmp (path, "/proc") == 0) || + (strcmp (path, "/sys") == 0)) + ret = TRUE; + } + + g_free (path); + + return ret; +} diff --git a/baobab/src/baobab-utils.h b/baobab/src/baobab-utils.h new file mode 100644 index 00000000..37330240 --- /dev/null +++ b/baobab/src/baobab-utils.h @@ -0,0 +1,49 @@ +/* + * baobab-utils.h + * This file is part of baobab + * + * Copyright (C) 2005-2006 Fabio Marzocca <[email protected]> + * + * 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 + */ + +#ifndef __BAOBAB_UTILS_H__ +#define __BAOBAB_UTILS_H__ + +#include "baobab.h" + +void baobab_get_filesystem (BaobabFS *fs); +gchar* dir_select (gboolean, GtkWidget *); +void on_toggled (GtkToggleButton *, gpointer); +void stop_scan (void); +gboolean show_bars (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data); +void message (const gchar *primary_msg, const gchar *secondary_msg, GtkMessageType type, GtkWidget *parent); +gint messageyesno (const gchar *primary_msg, const gchar *secondary_msg, GtkMessageType type, gchar * ok_button, GtkWidget *parent); +gboolean baobab_check_dir (GFile *); +void popupmenu_list (GtkTreePath *path, GdkEventButton *event, gboolean can_trash); +void open_file_with_application (GFile *file); +gboolean can_trash_file (GFile *file); +gboolean trash_file (GFile *file); +void set_ui_action_sens (const gchar *name, gboolean sens); +void set_ui_widget_sens (const gchar *name, gboolean sens); +gchar *baobab_mateconf_get_string_with_default (MateConfClient *client, const gchar *key, const gchar *def); +gboolean baobab_help_display (GtkWindow *parent, const gchar *file_name, const gchar *link_id); +gboolean is_virtual_filesystem (GFile *file); + +#endif /* __BAOBAB_UTILS_H__ */ diff --git a/baobab/src/baobab.c b/baobab/src/baobab.c new file mode 100644 index 00000000..063731d2 --- /dev/null +++ b/baobab/src/baobab.c @@ -0,0 +1,1317 @@ +/* + * baobab.c + * This file is part of baobab + * + * Copyright (C) 2005-2006 Fabio Marzocca <[email protected]> + * + * 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 + */ + +#include <config.h> + +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#include <gio/gio.h> +#include <mateconf/mateconf-client.h> +#include <glibtop.h> + +#include "baobab.h" +#include "baobab-scan.h" +#include "baobab-treeview.h" +#include "baobab-utils.h" +#include "callbacks.h" +#include "baobab-prefs.h" + +#include "baobab-treemap.h" +#include "baobab-ringschart.h" + +static void push_iter_in_stack (GtkTreeIter *); +static GtkTreeIter pop_iter_from_stack (void); + +static gint currentdepth = 0; +static GtkTreeIter currentiter; +static GtkTreeIter firstiter; +static GQueue *iterstack = NULL; + +enum { + DND_TARGET_URI_LIST +}; + +static GtkTargetEntry dnd_target_list[] = { + { "text/uri-list", 0, DND_TARGET_URI_LIST }, +}; + +static gboolean +scan_is_local (GFile *file) +{ + gchar *uri_scheme; + gboolean ret = FALSE; + + uri_scheme = g_file_get_uri_scheme (file); + if (g_ascii_strcasecmp(uri_scheme,"file") == 0) + ret = TRUE; + + g_free (uri_scheme); + + return ret; +} + +void +baobab_set_busy (gboolean busy) +{ + static GdkCursor *busy_cursor = NULL; + GdkCursor *cursor = NULL; + GdkWindow *window; + + if (busy == TRUE) { + if (!busy_cursor) { + busy_cursor = gdk_cursor_new (GDK_WATCH); + } + cursor = busy_cursor; + + gtk_widget_show (baobab.spinner); + gtk_spinner_start (GTK_SPINNER (baobab.spinner)); + + baobab_chart_freeze_updates (baobab.rings_chart); + + baobab_chart_freeze_updates (baobab.treemap_chart); + + gtk_widget_set_sensitive (baobab.chart_type_combo, FALSE); + } + else { + gtk_widget_hide (baobab.spinner); + gtk_spinner_stop (GTK_SPINNER (baobab.spinner)); + + baobab_chart_thaw_updates (baobab.rings_chart); + baobab_chart_thaw_updates (baobab.treemap_chart); + + gtk_widget_set_sensitive (baobab.chart_type_combo, TRUE); + } + + /* change the cursor */ + window = gtk_widget_get_window (baobab.window); + if (window) { + gdk_window_set_cursor (window, cursor); + } +} + +static void +set_drop_target (GtkWidget *target, gboolean active) { + if (active) { + gtk_drag_dest_set (GTK_WIDGET (target), + GTK_DEST_DEFAULT_DROP | GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT, + dnd_target_list, + G_N_ELEMENTS (dnd_target_list), + GDK_ACTION_COPY); + } else { + gtk_drag_dest_unset (target); + } +} + +/* menu & toolbar sensitivity */ +static void +check_menu_sens (gboolean scanning) +{ + gboolean has_current_location; + + if (scanning) { + while (gtk_events_pending ()) + gtk_main_iteration (); + + baobab_set_statusbar (_("Scanning...")); + set_ui_action_sens ("expand_all", TRUE); + set_ui_action_sens ("collapse_all", TRUE); + } + + has_current_location = baobab.current_location != NULL; + + set_ui_action_sens ("menuscanhome", !scanning); + set_ui_action_sens ("menuallfs", !scanning); + set_ui_action_sens ("menuscandir", !scanning); + set_ui_action_sens ("menustop", scanning); + set_ui_action_sens ("menurescan", !scanning && has_current_location); + set_ui_action_sens ("preferenze1", !scanning); + set_ui_action_sens ("menu_scan_rem", !scanning); + set_ui_action_sens ("ck_allocated", !scanning && baobab.is_local); + + set_ui_widget_sens ("tbscanhome", !scanning); + set_ui_widget_sens ("tbscanall", !scanning); + set_ui_widget_sens ("tbscandir", !scanning); + set_ui_widget_sens ("tbstop", scanning); + set_ui_widget_sens ("tbrescan", !scanning && has_current_location); + set_ui_widget_sens ("tb_scan_remote", !scanning); +} + +static void +check_drop_targets (gboolean scanning) +{ + set_drop_target (baobab.rings_chart, !scanning); + set_drop_target (baobab.treemap_chart, !scanning); +} + +static void +update_scan_label (void) +{ + gchar *markup; + gchar *total; + gchar *used; + gchar *available; + GtkWidget *label; + + total = g_format_size_for_display (baobab.fs.total); + used = g_format_size_for_display (baobab.fs.used); + available = g_format_size_for_display (baobab.fs.avail); + + /* Translators: these are labels for disk space */ + markup = g_markup_printf_escaped ("<small>%s <b>%s</b> (%s %s %s %s )</small>", + _("Total filesystem capacity:"), total, + _("used:"), used, + _("available:"), available); + + g_free (total); + g_free (used); + g_free (available); + + label = GTK_WIDGET (gtk_builder_get_object (baobab.main_ui, "label1")); + + gtk_label_set_markup (GTK_LABEL (label), markup); +} + +void +baobab_update_filesystem (void) +{ + baobab_get_filesystem (&baobab.fs); + update_scan_label (); +} + +void +baobab_scan_location (GFile *file) +{ + GtkToggleAction *ck_allocated; + + if (!baobab_check_dir (file)) + return; + + if (iterstack !=NULL) + return; + + if (baobab.current_location) + g_object_unref (baobab.current_location); + baobab.current_location = g_object_ref (file); + + baobab.STOP_SCANNING = FALSE; + baobab_set_busy (TRUE); + check_menu_sens (TRUE); + check_drop_targets (TRUE); + gtk_tree_store_clear (baobab.model); + currentdepth = -1; /* flag */ + iterstack = g_queue_new (); + + /* check if the file system is local or remote */ + baobab.is_local = scan_is_local (file); + ck_allocated = GTK_TOGGLE_ACTION (gtk_builder_get_object (baobab.main_ui, "ck_allocated")); + if (!baobab.is_local) { + gtk_toggle_action_set_active (ck_allocated, FALSE); + gtk_action_set_sensitive (GTK_ACTION (ck_allocated), FALSE); + baobab.show_allocated = FALSE; + } + else { + gtk_action_set_sensitive (GTK_ACTION (ck_allocated), TRUE); + } + + baobab_scan_execute (file); + + /* set statusbar, percentage and allocated/normal size */ + baobab_set_statusbar (_("Calculating percentage bars...")); + gtk_tree_model_foreach (GTK_TREE_MODEL (baobab.model), + show_bars, + NULL); + + baobab_chart_set_max_depth (baobab.rings_chart, baobab.model_max_depth); + baobab_chart_set_max_depth (baobab.treemap_chart, baobab.model_max_depth); + + baobab_set_busy (FALSE); + check_menu_sens (FALSE); + check_drop_targets (FALSE); + baobab_set_statusbar (_("Ready")); + + gtk_tree_view_columns_autosize (GTK_TREE_VIEW (baobab.tree_view)); + baobab.STOP_SCANNING = TRUE; + g_queue_free (iterstack); + iterstack = NULL; + baobab.CONTENTS_CHANGED_DELAYED = FALSE; +} + +void +baobab_scan_home (void) +{ + GFile *file; + + file = g_file_new_for_path (g_get_home_dir ()); + baobab_scan_location (file); + g_object_unref (file); +} + +void +baobab_scan_root (void) +{ + GFile *file; + + file = g_file_new_for_uri ("file:///"); + baobab_scan_location (file); + g_object_unref (file); +} + +void +baobab_rescan_current_dir (void) +{ + g_return_if_fail (baobab.current_location != NULL); + + baobab_update_filesystem (); + + g_object_ref (baobab.current_location); + baobab_scan_location (baobab.current_location); + g_object_unref (baobab.current_location); +} + +void +baobab_stop_scan (void) +{ + baobab.STOP_SCANNING = TRUE; + + baobab_set_statusbar (_("Calculating percentage bars...")); + gtk_tree_model_foreach (GTK_TREE_MODEL (baobab.model), + show_bars, NULL); + gtk_tree_view_columns_autosize (GTK_TREE_VIEW (baobab.tree_view)); +} + +/* + * pre-fills model during scanning + */ +static void +prefill_model (struct chan_data *data) +{ + GtkTreeIter iter, iterparent; + char *name; + char *str; + + if (currentdepth == -1) { + gtk_tree_store_append (baobab.model, &iter, NULL); + firstiter = iter; + } + else if (data->depth == 1) { + GtkTreePath *path; + + gtk_tree_store_append (baobab.model, &iter, &firstiter); + path = gtk_tree_model_get_path (GTK_TREE_MODEL (baobab.model), + &firstiter); + gtk_tree_view_expand_row (GTK_TREE_VIEW (baobab.tree_view), + path, FALSE); + gtk_tree_path_free (path); + } + else if (data->depth > currentdepth) { + gtk_tree_store_append (baobab.model, &iter, ¤titer); + } + else if (data->depth == currentdepth) { + gtk_tree_model_iter_parent ((GtkTreeModel *) baobab.model, + &iterparent, ¤titer); + gtk_tree_store_append (baobab.model, &iter, &iterparent); + } + else if (data->depth < currentdepth) { + GtkTreeIter tempiter; + gint i; + iter = currentiter; + for (i = 0; i <= (currentdepth - data->depth); i++) { + gtk_tree_model_iter_parent ((GtkTreeModel *) + baobab.model, + &tempiter, &iter); + iter = tempiter; + } + gtk_tree_store_append (baobab.model, &iter, &tempiter); + } + + currentdepth = data->depth; + push_iter_in_stack (&iter); + currentiter = iter; + + /* in case filenames contains gmarkup */ + name = g_markup_escape_text (data->display_name, -1); + + str = g_strdup_printf ("<small><i>%s</i></small>", _("Scanning...")); + + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (baobab.tree_view), TRUE); + gtk_tree_store_set (baobab.model, &iter, + COL_DIR_NAME, name, + COL_H_PARSENAME, "", + COL_H_ELEMENTS, -1, + COL_H_PERC, -1.0, + COL_DIR_SIZE, str, + COL_ELEMENTS, str, -1); + + g_free (name); + g_free (str); + + while (gtk_events_pending ()) { + gtk_main_iteration (); + } +} + +static void +first_row (void) +{ + char *size; + gdouble perc; + char *label; + + GtkTreeIter root_iter; + + gchar *capacity_label, *capacity_size; + + gtk_tree_store_append (baobab.model, &root_iter, NULL); + capacity_size = g_format_size_for_display (baobab.fs.total); + + capacity_label = g_strdup (_("Total filesystem capacity")); + gtk_tree_store_set (baobab.model, &root_iter, + COL_DIR_NAME, capacity_label, + COL_H_PARSENAME, "", + COL_H_PERC, 100.0, + COL_DIR_SIZE, capacity_size, + COL_H_SIZE, baobab.fs.total, + COL_H_ALLOCSIZE, baobab.fs.total, + COL_H_ELEMENTS, -1, -1); + g_free (capacity_label); + g_free (capacity_size); + + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (baobab.tree_view), FALSE); + gtk_tree_store_append (baobab.model, &firstiter, &root_iter); + size = g_format_size_for_display (baobab.fs.used); + if (baobab.fs.total == 0 && baobab.fs.used == 0) { + perc = 100.0; + } else { + g_assert (baobab.fs.total != 0); + perc = ((gdouble) baobab.fs.used * 100) / (gdouble) baobab.fs.total; + } + + label = g_strdup (_("Total filesystem usage")); + gtk_tree_store_set (baobab.model, &firstiter, + COL_DIR_NAME, label, + COL_H_PARSENAME, "", + COL_H_PERC, perc, + COL_DIR_SIZE, size, + COL_H_SIZE, baobab.fs.used, + COL_H_ALLOCSIZE, baobab.fs.used, + COL_H_ELEMENTS, -1, -1); + + g_free (size); + g_free (label); + + gtk_tree_view_expand_all (GTK_TREE_VIEW (baobab.tree_view)); +} + +/* fills model during scanning */ +void +baobab_fill_model (struct chan_data *data) +{ + GtkTreeIter iter; + GString *hardlinks; + GString *elements; + char *name; + char *size; + char *alloc_size; + + if (data->elements == -1) { + prefill_model (data); + return; + } + + iter = pop_iter_from_stack (); + + /* in case filenames contains gmarkup */ + name = g_markup_escape_text (data->display_name, -1); + + hardlinks = g_string_new (""); + if (data->tempHLsize > 0) { + size = g_format_size_for_display (data->tempHLsize); + g_string_assign (hardlinks, "<i>("); + g_string_append (hardlinks, _("contains hardlinks for:")); + g_string_append (hardlinks, " "); + g_string_append (hardlinks, size); + g_string_append (hardlinks, ")</i>"); + g_free (size); + } + + elements = g_string_new (""); + g_string_printf (elements, + ngettext ("%5d item", "%5d items", + data->elements), data->elements); + + size = g_format_size_for_display (data->size); + alloc_size = g_format_size_for_display (data->alloc_size); + + gtk_tree_store_set (baobab.model, &iter, + COL_DIR_NAME, name, + COL_H_PARSENAME, data->parse_name, + COL_H_PERC, -1.0, + COL_DIR_SIZE, + baobab.show_allocated ? alloc_size : size, + COL_H_SIZE, data->size, + COL_ELEMENTS, elements->str, + COL_H_ELEMENTS, data->elements, + COL_HARDLINK, hardlinks->str, + COL_H_HARDLINK, data->tempHLsize, + COL_H_ALLOCSIZE, data->alloc_size, -1); + + while (gtk_events_pending ()) { + gtk_main_iteration (); + } + + g_string_free (hardlinks, TRUE); + g_string_free (elements, TRUE); + g_free (name); + g_free (size); + g_free (alloc_size); +} + +void +push_iter_in_stack (GtkTreeIter *iter) +{ + g_queue_push_head (iterstack, iter->user_data3); + g_queue_push_head (iterstack, iter->user_data2); + g_queue_push_head (iterstack, iter->user_data); + g_queue_push_head (iterstack, GINT_TO_POINTER (iter->stamp)); +} + +GtkTreeIter +pop_iter_from_stack (void) +{ + GtkTreeIter iter; + + iter.stamp = GPOINTER_TO_INT (g_queue_pop_head (iterstack)); + iter.user_data = g_queue_pop_head (iterstack); + iter.user_data2 = g_queue_pop_head (iterstack); + iter.user_data3 = g_queue_pop_head (iterstack); + + return iter; +} + +gboolean +baobab_is_excluded_location (GFile *file) +{ + gboolean ret = FALSE; + GSList *l; + + g_return_val_if_fail (file != NULL, FALSE); + + for (l = baobab.excluded_locations; l != NULL; l = l->next) { + if (g_file_equal (l->data, file)) { + ret = TRUE; + break; + } + } + + return ret; +} + +static void +volume_changed (GVolumeMonitor *volume_monitor, + GVolume *volume, + gpointer user_data) +{ + /* filesystem has changed (mounted or unmounted device) */ + baobab_update_filesystem (); +} + +static void +home_contents_changed (GFileMonitor *file_monitor, + GFile *child, + GFile *other_file, + GFileMonitorEvent event_type, + gpointer user_data) + +{ + gchar *excluding; + + if (baobab.CONTENTS_CHANGED_DELAYED) + return; + + excluding = g_file_get_basename (child); + if (strcmp (excluding, ".recently-used") == 0 || + strcmp (excluding, ".mate2_private") == 0 || + strcmp (excluding, ".xsession-errors") == 0 || + strcmp (excluding, ".bash_history") == 0 || + strcmp (excluding, ".mateconfd") == 0) { + g_free (excluding); + return; + } + g_free (excluding); + + baobab.CONTENTS_CHANGED_DELAYED = TRUE; +} + +static void +monitor_volume (void) +{ + baobab.monitor_vol = g_volume_monitor_get (); + + g_signal_connect (baobab.monitor_vol, "volume_changed", + G_CALLBACK (volume_changed), NULL); +} + +static void +monitor_home (gboolean enable) +{ + if (enable && baobab.monitor_home == NULL) { + GFile *file; + GError *error = NULL; + + file = g_file_new_for_path (g_get_home_dir ()); + baobab.monitor_home = g_file_monitor_directory (file, 0, NULL, &error); + g_object_unref (file); + + if (!baobab.monitor_home) { + message (_("Could not initialize monitoring"), + _("Changes to your home folder will not be monitored."), + GTK_MESSAGE_WARNING, NULL); + g_print ("homedir:%s\n", error->message); + g_error_free (error); + } + else { + g_signal_connect (baobab.monitor_home, + "changed", + G_CALLBACK (home_contents_changed), + NULL); + } + } + else if (!enable && baobab.monitor_home != NULL) { + g_file_monitor_cancel (baobab.monitor_home); + g_object_unref (baobab.monitor_home); + baobab.monitor_home = NULL; + } +} + +void +baobab_set_toolbar_visible (gboolean visible) +{ + GtkToggleAction *action; + + if (visible) + gtk_widget_show (baobab.toolbar); + else + gtk_widget_hide (baobab.toolbar); + + /* make sure the check menu item is consistent */ + action = GTK_TOGGLE_ACTION (gtk_builder_get_object (baobab.main_ui, "view_tb")); + gtk_toggle_action_set_active (action, visible); +} + +void +baobab_set_statusbar_visible (gboolean visible) +{ + GtkToggleAction *action; + + if (visible) + gtk_widget_show (baobab.statusbar); + else + gtk_widget_hide (baobab.statusbar); + + /* make sure the check menu item is consistent */ + action = GTK_TOGGLE_ACTION (gtk_builder_get_object (baobab.main_ui, "view_sb")); + gtk_toggle_action_set_active (action, visible); +} + +void +baobab_set_statusbar (const gchar *text) +{ + gtk_statusbar_pop (GTK_STATUSBAR (baobab.statusbar), 1); + gtk_statusbar_push (GTK_STATUSBAR (baobab.statusbar), 1, text); + + while (gtk_events_pending ()) + gtk_main_iteration (); +} + +static void +toolbar_reconfigured_cb (GtkToolItem *item, + GtkWidget *spinner) +{ + GtkToolbarStyle style; + GtkIconSize size; + + style = gtk_tool_item_get_toolbar_style (item); + + if (style == GTK_TOOLBAR_BOTH) + { + size = GTK_ICON_SIZE_DIALOG; + } + else + { + size = GTK_ICON_SIZE_LARGE_TOOLBAR; + } + + gtk_widget_set_size_request (spinner, size, size); +} + +static void +baobab_toolbar_style (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + gchar *toolbar_setting; + + toolbar_setting = baobab_mateconf_get_string_with_default (baobab.mateconf_client, + SYSTEM_TOOLBAR_STYLE_KEY, + "both"); + + if (!strcmp(toolbar_setting, "icons")) { + gtk_toolbar_set_style (GTK_TOOLBAR(baobab.toolbar), + GTK_TOOLBAR_ICONS); + } + else if (!strcmp(toolbar_setting, "both")) { + gtk_toolbar_set_style (GTK_TOOLBAR(baobab.toolbar), + GTK_TOOLBAR_BOTH); + } + else if (!strcmp(toolbar_setting, "both-horiz")) { + gtk_toolbar_set_style (GTK_TOOLBAR(baobab.toolbar), + GTK_TOOLBAR_BOTH_HORIZ); + } + else if (!strcmp(toolbar_setting, "text")) { + gtk_toolbar_set_style (GTK_TOOLBAR(baobab.toolbar), + GTK_TOOLBAR_TEXT); + } + + g_free (toolbar_setting); +} + +static void +baobab_create_toolbar (void) +{ + GtkWidget *toolbar; + GtkToolItem *item; + GtkToolItem *separator; + gboolean visible; + + toolbar = GTK_WIDGET (gtk_builder_get_object (baobab.main_ui, "toolbar1")); + if (toolbar == NULL) { + g_printerr ("Could not build toolbar\n"); + return; + } + + baobab.toolbar = toolbar; + + separator = gtk_separator_tool_item_new (); + gtk_separator_tool_item_set_draw (GTK_SEPARATOR_TOOL_ITEM (separator), FALSE); + gtk_tool_item_set_expand (GTK_TOOL_ITEM (separator), TRUE); + gtk_container_add (GTK_CONTAINER (toolbar), GTK_WIDGET (separator)); + gtk_widget_show (GTK_WIDGET (separator)); + + baobab.spinner = gtk_spinner_new (); + item = gtk_tool_item_new (); + gtk_container_add (GTK_CONTAINER (item), baobab.spinner); + gtk_container_add (GTK_CONTAINER (toolbar), GTK_WIDGET (item)); + gtk_widget_show (GTK_WIDGET (item)); + + g_signal_connect (item, "toolbar-reconfigured", + G_CALLBACK (toolbar_reconfigured_cb), baobab.spinner); + toolbar_reconfigured_cb (item, baobab.spinner); + + baobab_toolbar_style (NULL, 0, NULL, NULL); + + visible = mateconf_client_get_bool (baobab.mateconf_client, + BAOBAB_TOOLBAR_VISIBLE_KEY, + NULL); + + baobab_set_toolbar_visible (visible); +} + +static void +baobab_create_statusbar (void) +{ + gboolean visible; + + baobab.statusbar = GTK_WIDGET (gtk_builder_get_object (baobab.main_ui, + "statusbar1")); + if (baobab.statusbar == NULL) { + g_printerr ("Could not build statusbar\n"); + return; + } + + visible = mateconf_client_get_bool (baobab.mateconf_client, + BAOBAB_STATUSBAR_VISIBLE_KEY, + NULL); + + baobab_set_statusbar_visible (visible); +} + +static void +baobab_subfolderstips_toggled (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + baobab_ringschart_set_subfoldertips_enabled (baobab.rings_chart, + mateconf_client_get_bool (baobab.mateconf_client, + BAOBAB_SUBFLSTIPS_VISIBLE_KEY, + NULL)); +} + +static void +baobab_set_excluded_locations (GSList *excluded_uris) +{ + GSList *l; + + g_slist_foreach (baobab.excluded_locations, (GFunc) g_object_unref, NULL); + g_slist_free (baobab.excluded_locations); + baobab.excluded_locations = NULL; + for (l = excluded_uris; l != NULL; l = l->next) { + baobab.excluded_locations = g_slist_prepend (baobab.excluded_locations, + g_file_new_for_uri (l->data)); + } +} + +static void +store_excluded_locations (void) +{ + GSList *l; + GSList *uri_list = NULL; + + for (l = baobab.excluded_locations; l != NULL; l = l->next) { + GSList *uri_list = NULL; + + uri_list = g_slist_prepend (uri_list, g_file_get_uri(l->data)); + } + + mateconf_client_set_list (baobab.mateconf_client, + BAOBAB_EXCLUDED_DIRS_KEY, + MATECONF_VALUE_STRING, + uri_list, + NULL); + + g_slist_foreach (uri_list, (GFunc) g_free, NULL); + g_slist_free (uri_list); +} + +static void +sanity_check_excluded_locations (void) +{ + GFile *root; + GSList *l; + + /* Verify if mateconf wrongly contains root dir exclusion, and remove it from mateconf. */ + root = g_file_new_for_uri ("file:///"); + + for (l = baobab.excluded_locations; l != NULL; l = l->next) { + if (g_file_equal (l->data, root)) { + baobab.excluded_locations = g_slist_delete_link (baobab.excluded_locations, l); + store_excluded_locations (); + break; + } + } + + g_object_unref (root); +} + +static void +excluded_locations_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + GSList *uris; + + uris = mateconf_client_get_list (client, + BAOBAB_EXCLUDED_DIRS_KEY, + MATECONF_VALUE_STRING, + NULL); + baobab_set_excluded_locations (uris); + g_slist_foreach (uris, (GFunc) g_free, NULL); + g_slist_free (uris); + + baobab_update_filesystem (); + + gtk_tree_store_clear (baobab.model); + first_row (); +} + +static void +baobab_monitor_home_toggled (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + gboolean enable; + + enable = mateconf_client_get_bool (baobab.mateconf_client, + BAOBAB_ENABLE_HOME_MONITOR_KEY, + NULL); + + monitor_home (enable); +} + +static void +baobab_init (void) +{ + GSList *uri_list; + GError *error = NULL; + gboolean enable; + + /* FileSystem usage */ + baobab_get_filesystem (&baobab.fs); + + /* Load the UI */ + baobab.main_ui = gtk_builder_new (); + gtk_builder_add_from_file (baobab.main_ui, BAOBAB_UI_FILE, &error); + + if (error) { + g_object_unref (baobab.main_ui); + g_critical ("Unable to load the user interface file: %s", error->message); + g_error_free (error); + exit (1); + } + + gtk_builder_connect_signals (baobab.main_ui, NULL); + + /* Misc */ + baobab.CONTENTS_CHANGED_DELAYED = FALSE; + baobab.STOP_SCANNING = TRUE; + baobab.show_allocated = TRUE; + baobab.is_local = TRUE; + + /* MateConf */ + baobab.mateconf_client = mateconf_client_get_default (); + mateconf_client_add_dir (baobab.mateconf_client, BAOBAB_KEY_DIR, + MATECONF_CLIENT_PRELOAD_NONE, NULL); + mateconf_client_notify_add (baobab.mateconf_client, BAOBAB_EXCLUDED_DIRS_KEY, excluded_locations_changed, + NULL, NULL, NULL); + mateconf_client_notify_add (baobab.mateconf_client, SYSTEM_TOOLBAR_STYLE_KEY, baobab_toolbar_style, + NULL, NULL, NULL); + mateconf_client_notify_add (baobab.mateconf_client, BAOBAB_SUBFLSTIPS_VISIBLE_KEY, baobab_subfolderstips_toggled, + NULL, NULL, NULL); + mateconf_client_notify_add (baobab.mateconf_client, BAOBAB_ENABLE_HOME_MONITOR_KEY, baobab_monitor_home_toggled, + NULL, NULL, NULL); + + uri_list = mateconf_client_get_list (baobab.mateconf_client, + BAOBAB_EXCLUDED_DIRS_KEY, + MATECONF_VALUE_STRING, + NULL); + + baobab_set_excluded_locations (uri_list); + + g_slist_foreach (uri_list, (GFunc) g_free, NULL); + g_slist_free (uri_list); + + sanity_check_excluded_locations (); + + baobab_create_toolbar (); + + baobab_create_statusbar (); + + monitor_volume (); + + enable = mateconf_client_get_bool (baobab.mateconf_client, + BAOBAB_ENABLE_HOME_MONITOR_KEY, + NULL); + + monitor_home (enable); +} + +static void +baobab_shutdown (void) +{ + if (baobab.current_location) + g_object_unref (baobab.current_location); + + if (baobab.monitor_vol) { + g_object_unref (baobab.monitor_vol); + } + + if (baobab.monitor_home) { + g_file_monitor_cancel (baobab.monitor_home); + g_object_unref (baobab.monitor_home); + } + + g_free (baobab.selected_path); + + g_slist_foreach (baobab.excluded_locations, (GFunc) g_object_unref, NULL); + g_slist_free (baobab.excluded_locations); + + if (baobab.mateconf_client) { + g_object_unref (baobab.mateconf_client); + } +} + +static BaobabChartMenu * +create_context_menu (void) +{ + BaobabChartMenu *menu = NULL; + + baobab.chart_menu = g_new0 (BaobabChartMenu, 1); + menu = baobab.chart_menu; + + menu->widget = gtk_menu_new (); + + menu->up_item = gtk_image_menu_item_new_with_label (_("Move to parent folder")); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu->up_item), + gtk_image_new_from_stock(GTK_STOCK_GO_UP, GTK_ICON_SIZE_MENU)); + + menu->zoom_in_item = gtk_image_menu_item_new_with_label (_("Zoom in")) ; + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu->zoom_in_item), + gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_MENU)); + + menu->zoom_out_item = gtk_image_menu_item_new_with_label (_("Zoom out")); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu->zoom_out_item), + gtk_image_new_from_stock(GTK_STOCK_REMOVE, GTK_ICON_SIZE_MENU)); + + menu->snapshot_item = gtk_image_menu_item_new_with_label (_("Save screenshot")); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu->snapshot_item), + gtk_image_new_from_file (BAOBAB_PIX_DIR "shot.png")); + + gtk_menu_shell_append (GTK_MENU_SHELL (menu->widget), + menu->up_item); + gtk_menu_shell_append (GTK_MENU_SHELL (menu->widget), + gtk_separator_menu_item_new ()); + gtk_menu_shell_append (GTK_MENU_SHELL (menu->widget), + menu->zoom_in_item); + gtk_menu_shell_append (GTK_MENU_SHELL (menu->widget), + menu->zoom_out_item); + gtk_menu_shell_append (GTK_MENU_SHELL (menu->widget), + gtk_separator_menu_item_new ()); + gtk_menu_shell_append (GTK_MENU_SHELL (menu->widget), + menu->snapshot_item); + + /* connect signals */ + g_signal_connect (menu->up_item, "activate", + G_CALLBACK (on_move_upwards_cb), NULL); + g_signal_connect (menu->zoom_in_item, "activate", + G_CALLBACK (on_zoom_in_cb), NULL); + g_signal_connect (menu->zoom_out_item, "activate", + G_CALLBACK (on_zoom_out_cb), NULL); + g_signal_connect (menu->snapshot_item, "activate", + G_CALLBACK (on_chart_snapshot_cb), NULL); + + gtk_widget_show_all (menu->widget); + + return menu; +} + +static void +on_chart_item_activated (BaobabChart *chart, GtkTreeIter *iter) +{ + GtkTreePath *path; + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (baobab.model), iter); + + if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (baobab.tree_view), path)) + gtk_tree_view_expand_to_path (GTK_TREE_VIEW (baobab.tree_view), path); + + gtk_tree_view_set_cursor (GTK_TREE_VIEW (baobab.tree_view), + path, NULL, FALSE); + gtk_tree_path_free (path); +} + +static gboolean +on_chart_button_release (BaobabChart *chart, + GdkEventButton *event, + gpointer data) +{ + if (baobab_chart_is_frozen (baobab.current_chart)) + return FALSE; + + if (event->button== 3) /* right button */ + { + GtkTreePath *root_path; + BaobabChartMenu *menu; + + root_path = baobab_chart_get_root (baobab.current_chart); + + menu = baobab.chart_menu; + gtk_widget_set_sensitive (menu->up_item, + ((root_path != NULL) && + (gtk_tree_path_get_depth (root_path) > 1))); + gtk_widget_set_sensitive (menu->zoom_in_item, + baobab_chart_can_zoom_in (baobab.current_chart)); + gtk_widget_set_sensitive (menu->zoom_out_item, + baobab_chart_can_zoom_out (baobab.current_chart)); + + /* show the menu */ + gtk_menu_popup (GTK_MENU (menu->widget), + NULL, NULL, NULL, NULL, + event->button, event->time); + + gtk_tree_path_free (root_path); + } + + return FALSE; +} + +static void +drag_data_received_handl (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + GtkSelectionData *selection_data, + guint target_type, + guint time, + gpointer data) +{ + GFile *gf = NULL; + + /* set "gf" if we got some valid data */ + if ((selection_data != NULL) && + (gtk_selection_data_get_length (selection_data) >= 0) && + (target_type == DND_TARGET_URI_LIST)) { + gchar **uri_list; + uri_list = g_uri_list_extract_uris ((const gchar *) gtk_selection_data_get_data (selection_data)); + /* check list is 1 item long */ + if (uri_list != NULL && uri_list[0] != NULL && uri_list[1] == NULL) { + gf = g_file_new_for_uri (uri_list[0]); + } + g_strfreev (uri_list); + } + + /* success if "gf" has been set */ + if (gf != NULL) { + /* finish drop before beginning scan, as the drag-drop can + probably time out */ + gtk_drag_finish (context, TRUE, FALSE, time); + baobab_scan_location (gf); + g_object_unref (gf); + } else { + gtk_drag_finish (context, FALSE, FALSE, time); + } +} + +static void +initialize_charts (void) +{ + GtkWidget *hpaned_main; + GtkWidget *chart_frame; + GtkWidget *hbox1; + + chart_frame = gtk_frame_new (NULL); + gtk_frame_set_label_align (GTK_FRAME (chart_frame), 0.0, 0.0); + gtk_frame_set_shadow_type (GTK_FRAME (chart_frame), GTK_SHADOW_IN); + + hpaned_main = GTK_WIDGET (gtk_builder_get_object (baobab.main_ui, "hpaned_main")); + gtk_paned_pack2 (GTK_PANED (hpaned_main), + chart_frame, TRUE, TRUE); + gtk_paned_set_position (GTK_PANED (hpaned_main), 480); + + baobab.chart_type_combo = gtk_combo_box_new_text (); + gtk_combo_box_append_text (GTK_COMBO_BOX (baobab.chart_type_combo), + _("View as Rings Chart")); + gtk_combo_box_append_text (GTK_COMBO_BOX (baobab.chart_type_combo), + _("View as Treemap Chart")); + gtk_combo_box_set_active (GTK_COMBO_BOX (baobab.chart_type_combo), 0); + gtk_widget_show (baobab.chart_type_combo); + g_signal_connect (baobab.chart_type_combo, + "changed", + G_CALLBACK (on_chart_type_change), NULL); + + hbox1 = GTK_WIDGET (gtk_builder_get_object (baobab.main_ui, "hbox1")); + gtk_container_add (GTK_CONTAINER (hbox1), baobab.chart_type_combo); + gtk_box_set_spacing (GTK_BOX (hbox1), 50); + gtk_box_set_child_packing (GTK_BOX (hbox1), + baobab.chart_type_combo, + FALSE, + TRUE, + 0, GTK_PACK_END); + + baobab.chart_menu = create_context_menu (); + + /* Baobab's Treemap Chart */ + baobab.treemap_chart = baobab_treemap_new (); + baobab_chart_set_model_with_columns (baobab.treemap_chart, + GTK_TREE_MODEL (baobab.model), + COL_DIR_NAME, + COL_DIR_SIZE, + COL_H_PARSENAME, + COL_H_PERC, + COL_H_ELEMENTS, + NULL); + baobab_chart_set_max_depth (baobab.treemap_chart, 1); + g_signal_connect (baobab.treemap_chart, "item_activated", + G_CALLBACK (on_chart_item_activated), NULL); + g_signal_connect (baobab.treemap_chart, "button-release-event", + G_CALLBACK (on_chart_button_release), NULL); + g_signal_connect (baobab.treemap_chart, "drag-data-received", + G_CALLBACK (drag_data_received_handl), NULL); + gtk_widget_show (baobab.treemap_chart); + /* Ends Baobab's Treemap Chart */ + + /* Baobab's Rings Chart */ + baobab.rings_chart = (GtkWidget *) baobab_ringschart_new (); + baobab_chart_set_model_with_columns (baobab.rings_chart, + GTK_TREE_MODEL (baobab.model), + COL_DIR_NAME, + COL_DIR_SIZE, + COL_H_PARSENAME, + COL_H_PERC, + COL_H_ELEMENTS, + NULL); + baobab_ringschart_set_subfoldertips_enabled (baobab.rings_chart, + mateconf_client_get_bool (baobab.mateconf_client, + BAOBAB_SUBFLSTIPS_VISIBLE_KEY, + NULL)); + baobab_chart_set_max_depth (baobab.rings_chart, 1); + g_signal_connect (baobab.rings_chart, "item_activated", + G_CALLBACK (on_chart_item_activated), NULL); + g_signal_connect (baobab.rings_chart, "button-release-event", + G_CALLBACK (on_chart_button_release), NULL); + g_signal_connect (baobab.rings_chart, "drag-data-received", + G_CALLBACK (drag_data_received_handl), NULL); + gtk_widget_show (baobab.rings_chart); + /* Ends Baobab's Treemap Chart */ + + baobab.current_chart = baobab.rings_chart; + + g_object_ref_sink (baobab.treemap_chart); + baobab_chart_freeze_updates (baobab.treemap_chart); + + gtk_container_add (GTK_CONTAINER (chart_frame), + baobab.current_chart); + gtk_widget_show_all (chart_frame); + + check_drop_targets (FALSE); +} + +static gboolean +start_proc_on_command_line (GFile *file) +{ + baobab_scan_location (file); + + return FALSE; +} + +static gboolean +show_version (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + g_print("%s %s\n", g_get_application_name (), VERSION); + exit (0); + return TRUE; /* It's just good form */ +} + +int +main (int argc, char *argv[]) +{ + gchar **directories = NULL; + const GOptionEntry options[] = { + {"version", 'V', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_version, N_("Show version"), NULL}, + {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &directories, NULL, N_("[DIRECTORY]")}, + {NULL} + }; + GOptionContext *context; + GError *error = NULL; + + bindtextdomain (GETTEXT_PACKAGE, MATELOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + g_set_application_name ("Baobab"); + + context = g_option_context_new (NULL); + g_option_context_set_ignore_unknown_options (context, FALSE); + g_option_context_set_help_enabled (context, TRUE); + g_option_context_add_main_entries(context, options, GETTEXT_PACKAGE); + g_option_context_add_group (context, gtk_get_option_group (TRUE)); + + g_option_context_parse (context, &argc, &argv, &error); + + if (error) { + g_critical ("Unable to parse option: %s", error->message); + g_error_free (error); + g_option_context_free (context); + + exit (1); + } + g_option_context_free (context); + + if (directories && directories[0] && directories[1]) { + g_critical (_("Too many arguments. Only one directory can be specified.")); + exit (1); + } + + glibtop_init (); + + gtk_window_set_default_icon_name ("baobab"); + + baobab_init (); + + if (baobab.fs.total == 0) { + GtkWidget *dialog; + + dialog = gtk_message_dialog_new (NULL, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + _("Could not detect any mount point.")); + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG(dialog), + _("Without mount points disk usage cannot be analyzed.")); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + goto closing; + } + + check_menu_sens (FALSE); + update_scan_label (); + + baobab.window = GTK_WIDGET (gtk_builder_get_object (baobab.main_ui, "baobab_window")); + gtk_window_set_position (GTK_WINDOW (baobab.window), + GTK_WIN_POS_CENTER); + + baobab.tree_view = create_directory_treeview (); + + set_ui_action_sens ("menurescan", FALSE); + + /* set allocated space checkbox */ + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (gtk_builder_get_object (baobab.main_ui, + "ck_allocated")), + baobab.show_allocated); + + gtk_widget_show (baobab.window); + + first_row (); + baobab_set_statusbar (_("Ready")); + + /* The ringschart */ + initialize_charts (); + + /* commandline */ + if (directories && directories[0]) { + GFile *file; + + file = g_file_new_for_commandline_arg (directories[0]); + + /* start processing the dir specified on the + * command line as soon as we enter the main loop */ + g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, + (GSourceFunc) start_proc_on_command_line, + file, + (GDestroyNotify) g_object_unref); + } + g_strfreev (directories); + + gtk_main (); + + closing: + baobab_shutdown (); + + glibtop_close (); + + return 0; +} diff --git a/baobab/src/baobab.h b/baobab/src/baobab.h new file mode 100644 index 00000000..bab9b8bb --- /dev/null +++ b/baobab/src/baobab.h @@ -0,0 +1,128 @@ +/* + * baobab.h + * This file is part of baobab + * + * Copyright (C) 2005-2006 Fabio Marzocca <[email protected]> + * + * 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 + */ + +#ifndef __BAOBAB_H__ +#define __BAOBAB_H__ + +#include <time.h> +#include <sys/types.h> +#include <glib.h> +#include <gtk/gtk.h> +#include <gio/gio.h> +#include <mateconf/mateconf-client.h> + +typedef struct _baobab_fs baobab_fs; +struct BaobabSearchOpt; + +#define BAOBAB_UI_FILE PKGDATADIR "/baobab-main-window.ui" +#define BAOBAB_DIALOG_SCAN_UI_FILE PKGDATADIR "/baobab-dialog-scan-props.ui" + +/* Settings */ +#define BAOBAB_KEY_DIR "/apps/baobab" +#define BAOBAB_TOOLBAR_VISIBLE_KEY BAOBAB_KEY_DIR "/ui/toolbar_visible" +#define BAOBAB_STATUSBAR_VISIBLE_KEY BAOBAB_KEY_DIR "/ui/statusbar_visible" +#define BAOBAB_SUBFLSTIPS_VISIBLE_KEY BAOBAB_KEY_DIR "/ui/baobab_subfoldertips_visible" +#define BAOBAB_EXCLUDED_DIRS_KEY BAOBAB_KEY_DIR "/properties/skip_scan_uri_list" +#define BAOBAB_ENABLE_HOME_MONITOR_KEY BAOBAB_KEY_DIR "/properties/enable_home_monitor" +#define SYSTEM_TOOLBAR_STYLE_KEY "/desktop/mate/interface/toolbar_style" + +typedef struct _BaobabChartMenu BaobabChartMenu; + +struct _BaobabChartMenu { + GtkWidget *widget; + GtkWidget *up_item; + GtkWidget *zoom_in_item; + GtkWidget *zoom_out_item; + GtkWidget *subfolders_item; + GtkWidget *snapshot_item; + GtkWidget *set_root_item; +}; + +typedef struct _BaobabFS BaobabFS; + +struct _BaobabFS { + guint64 total; + guint64 used; + guint64 avail; +}; + +typedef struct _BaobabApplication BaobabApplication; + +struct _BaobabApplication { + BaobabFS fs; + + GtkBuilder *main_ui; + GtkWidget *window; + GtkWidget *tree_view; + GtkWidget *rings_chart; + GtkWidget *treemap_chart; + GtkWidget *current_chart; + GtkWidget *chart_type_combo; + BaobabChartMenu *chart_menu; + GtkWidget *toolbar; + GtkWidget *spinner; + GtkWidget *statusbar; + GtkTreeStore *model; + gboolean STOP_SCANNING; + gboolean CONTENTS_CHANGED_DELAYED; + GSList *excluded_locations; + gboolean show_allocated; + gboolean is_local; + + char *selected_path; + + GFile *current_location; + + GVolumeMonitor *monitor_vol; + GFileMonitor *monitor_home; + + MateConfClient *mateconf_client; + gint model_max_depth; +}; + +/* Application singleton */ +BaobabApplication baobab; + +struct chan_data { + guint64 size; + guint64 alloc_size; + guint64 tempHLsize; + guint depth; + gint elements; + gchar *display_name; + gchar *parse_name; +}; + +void baobab_set_busy (gboolean busy); +void baobab_update_filesystem (void); +void baobab_scan_location (GFile *); +void baobab_scan_home (void); +void baobab_scan_root (void); +void baobab_rescan_current_dir (void); +void baobab_stop_scan (void); +void baobab_fill_model (struct chan_data *); +gboolean baobab_is_excluded_location (GFile *); +void baobab_set_toolbar_visible (gboolean visible); +void baobab_set_statusbar_visible (gboolean visible); +void baobab_set_statusbar (const gchar *); + +#endif /* __BAOBAB_H_ */ diff --git a/baobab/src/callbacks.c b/baobab/src/callbacks.c new file mode 100644 index 00000000..3dc8ede4 --- /dev/null +++ b/baobab/src/callbacks.c @@ -0,0 +1,394 @@ +/* + * callbacks.c + * This file is part of baobab + * + * Copyright (C) 2005-2006 Fabio Marzocca <[email protected]> + * + * 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 + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <string.h> +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#include <mateconf/mateconf-client.h> +#include <gio/gio.h> + +#include "baobab.h" +#include "baobab-treeview.h" +#include "baobab-utils.h" +#include "callbacks.h" +#include "baobab-prefs.h" +#include "baobab-remote-connect-dialog.h" +#include "baobab-chart.h" + +void +on_menuscanhome_activate (GtkMenuItem *menuitem, gpointer user_data) +{ + baobab_scan_home (); +} + +void +on_menuallfs_activate (GtkMenuItem *menuitem, gpointer user_data) +{ + baobab_scan_root (); +} + +void +on_menuscandir_activate (GtkMenuItem *menuitem, gpointer user_data) +{ + dir_select (FALSE, baobab.window); +} + +void +on_esci1_activate (GtkObject *menuitem, gpointer user_data) +{ + baobab_stop_scan (); + gtk_main_quit (); +} + +void on_about_activate(GtkMenuItem* menuitem, gpointer user_data) +{ + const gchar* const authors[] = { + "Fabio Marzocca <[email protected]>", + "Paolo Borelli <[email protected]>", + "BenoĆ®t Dejean <[email protected]>", + "Igalia (rings-chart and treemap widget) <www.igalia.com>", + NULL + }; + + const gchar* license[] = { + N_("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."), + + N_("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."), + + N_("You should have received a copy of the GNU General Public License " + "along with this program; if not, write to the Free Software Foundation, Inc., 51 " + "Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA") + }; + + + gchar* license_trans = g_strjoin("\n\n", _(license[0]), _(license[1]), _(license[2]), NULL); + + static const gchar copyright[] = "Copyright \xc2\xa9 2005-2010 Fabio Marzocca"; + + gtk_show_about_dialog (NULL, + "name", _("Baobab"), + "comments", _("A graphical tool to analyze disk usage."), + "version", VERSION, + "copyright", copyright, + "logo-icon-name", "baobab", + "license", license_trans, + "authors", authors, + "translator-credits", _("translator-credits"), + "wrap-license", TRUE, + NULL); + + g_free(license_trans); +} + +void +on_menu_expand_activate (GtkMenuItem *menuitem, gpointer user_data) +{ + gtk_tree_view_expand_all (GTK_TREE_VIEW (baobab.tree_view)); +} + +void +on_menu_collapse_activate (GtkMenuItem *menuitem, gpointer user_data) +{ + gtk_tree_view_collapse_all (GTK_TREE_VIEW (baobab.tree_view)); +} + +void +on_menu_stop_activate (GtkMenuItem *menuitem, gpointer user_data) +{ + baobab_stop_scan (); +} + +void +on_menu_rescan_activate (GtkMenuItem *menuitem, gpointer user_data) +{ + baobab_rescan_current_dir (); +} + +void +on_tbscandir_clicked (GtkToolButton *toolbutton, gpointer user_data) +{ + dir_select (FALSE, baobab.window); +} + +void +on_tbscanhome_clicked (GtkToolButton *toolbutton, gpointer user_data) +{ + baobab_scan_home (); +} + +void +on_tbscanall_clicked (GtkToolButton *toolbutton, gpointer user_data) +{ + baobab_scan_root (); +} + +void +on_tb_scan_remote_clicked (GtkToolButton *toolbutton, gpointer user_data) +{ + GtkWidget *dlg; + + dlg = baobab_remote_connect_dialog_new (GTK_WINDOW (baobab.window), + NULL); + gtk_dialog_run (GTK_DIALOG (dlg)); + + gtk_widget_destroy (dlg); +} + +void +on_menu_scan_rem_activate (GtkMenuItem *menuitem, gpointer user_data) +{ + on_tb_scan_remote_clicked (NULL, NULL); +} + +void +on_tbstop_clicked (GtkToolButton *toolbutton, gpointer user_data) +{ + baobab_stop_scan (); +} + +void +on_tbrescan_clicked (GtkToolButton *toolbutton, gpointer user_data) +{ + baobab_rescan_current_dir (); +} + +gboolean +on_delete_activate (GtkWidget *widget, + GdkEvent *event, gpointer user_data) +{ + on_esci1_activate (NULL, NULL); + return TRUE; +} + +void +open_file_cb (GtkMenuItem *pmenu, gpointer dummy) +{ + GFile *file; + + g_assert (!dummy); + g_assert (baobab.selected_path); + + file = g_file_parse_name (baobab.selected_path); + + if (!g_file_query_exists (file, NULL)) { + message (_("The document does not exist."), "", + GTK_MESSAGE_INFO, baobab.window); + g_object_unref (file); + return; + } + + open_file_with_application (file); + g_object_unref (file); +} + +void +trash_dir_cb (GtkMenuItem *pmenu, gpointer dummy) +{ + GFile *file; + + g_assert (!dummy); + g_assert (baobab.selected_path); + + file = g_file_parse_name (baobab.selected_path); + + if (trash_file (file)) { + GtkTreeIter iter; + guint64 filesize; + GtkTreeSelection *selection; + + selection = + gtk_tree_view_get_selection ((GtkTreeView *) baobab. + tree_view); + gtk_tree_selection_get_selected (selection, NULL, &iter); + gtk_tree_model_get ((GtkTreeModel *) baobab.model, &iter, + 5, &filesize, -1); + gtk_tree_store_remove (GTK_TREE_STORE (baobab.model), + &iter); + } + + g_object_unref (file); +} + +void +on_pref_menu (GtkAction *a, gpointer user_data) +{ + baobab_prefs_dialog (); +} + +void +on_ck_allocated_activate (GtkToggleAction *action, + gpointer user_data) +{ + if (!baobab.is_local) + return; + + baobab.show_allocated = gtk_toggle_action_get_active (action); + + baobab_treeview_show_allocated_size (baobab.tree_view, + baobab.show_allocated); + + baobab_set_busy (TRUE); + baobab_set_statusbar (_("Calculating percentage bars...")); + gtk_tree_model_foreach (GTK_TREE_MODEL (baobab.model), + show_bars, NULL); + baobab_set_busy (FALSE); + baobab_set_statusbar (_("Ready")); +} + +void +on_view_tb_activate (GtkToggleAction *action, + gpointer user_data) +{ + gboolean visible; + + visible = gtk_toggle_action_get_active (action); + baobab_set_toolbar_visible (visible); + + mateconf_client_set_bool (baobab.mateconf_client, + BAOBAB_TOOLBAR_VISIBLE_KEY, + visible, + NULL); +} + +void +on_view_sb_activate (GtkToggleAction *action, + gpointer user_data) +{ + gboolean visible; + + visible = gtk_toggle_action_get_active (action); + baobab_set_statusbar_visible (visible); + + mateconf_client_set_bool (baobab.mateconf_client, + BAOBAB_STATUSBAR_VISIBLE_KEY, + visible, + NULL); +} + +void +on_helpcontents_activate (GtkAction *a, gpointer user_data) +{ + baobab_help_display (GTK_WINDOW (baobab.window), "baobab", NULL); +} + +void +scan_folder_cb (GtkMenuItem *pmenu, gpointer dummy) +{ + GFile *file; + + g_assert (!dummy); + g_assert (baobab.selected_path); + + file = g_file_parse_name (baobab.selected_path); + + if (!g_file_query_exists (file, NULL)) { + message (_("The folder does not exist."), "", GTK_MESSAGE_INFO, baobab.window); + } + + baobab_scan_location (file); + g_object_unref (file); +} + +void +on_tv_selection_changed (GtkTreeSelection *selection, gpointer user_data) +{ + GtkTreeIter iter; + + if (gtk_tree_selection_get_selected (selection, NULL, &iter)) { + GtkTreePath *path; + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (baobab.model), &iter); + + baobab_chart_set_root (baobab.rings_chart, path); + baobab_chart_set_root (baobab.treemap_chart, path); + + gtk_tree_path_free (path); + } +} + +void +on_move_upwards_cb (GtkCheckMenuItem *checkmenuitem, gpointer user_data) +{ + baobab_chart_move_up_root (baobab.current_chart); +} + +void +on_zoom_in_cb (GtkCheckMenuItem *checkmenuitem, gpointer user_data) +{ + baobab_chart_zoom_in (baobab.current_chart); +} + +void +on_zoom_out_cb (GtkCheckMenuItem *checkmenuitem, gpointer user_data) +{ + baobab_chart_zoom_out (baobab.current_chart); +} + +void +on_chart_snapshot_cb (GtkCheckMenuItem *checkmenuitem, gpointer user_data) +{ + baobab_chart_save_snapshot (baobab.current_chart); +} + +void +on_chart_type_change (GtkWidget *combo, gpointer user_data) +{ + GtkWidget *chart; + GtkWidget *frame; + + guint active; + + active = gtk_combo_box_get_active (GTK_COMBO_BOX (combo)); + + switch (active) { + case 0: + chart = baobab.rings_chart; + break; + case 1: + chart = baobab.treemap_chart; + break; + default: + g_return_if_reached (); + } + + frame = gtk_widget_get_parent (baobab.current_chart); + + baobab_chart_freeze_updates (baobab.current_chart); + baobab_chart_thaw_updates (chart); + + g_object_ref_sink (baobab.current_chart); + gtk_container_remove (GTK_CONTAINER (frame), baobab.current_chart); + gtk_container_add (GTK_CONTAINER (frame), chart); + + baobab.current_chart = chart; +} + diff --git a/baobab/src/callbacks.h b/baobab/src/callbacks.h new file mode 100644 index 00000000..93e70fbc --- /dev/null +++ b/baobab/src/callbacks.h @@ -0,0 +1,62 @@ +/* + * callbacks.h + * This file is part of baobab + * + * Copyright (C) 2005-2006 Fabio Marzocca <[email protected]> + * + * 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 + */ + +#ifndef __BAOBAB_CALLBACKS_H__ +#define __BAOBAB_CALLBACKS_H__ + +#include <gtk/gtk.h> +#include "baobab-chart.h" + +void on_about_activate (GtkMenuItem *menuitem, gpointer user_data); +void on_menuscanhome_activate (GtkMenuItem *menuitem, gpointer user_data); +void on_menuallfs_activate (GtkMenuItem *menuitem, gpointer user_data); +void on_menuscandir_activate (GtkMenuItem *menuitem, gpointer user_data); +void on_menu_stop_activate (GtkMenuItem *menuitem, gpointer user_data); +void on_menu_rescan_activate (GtkMenuItem *menuitem, gpointer user_data); +void on_tbscandir_clicked (GtkToolButton *toolbutton, gpointer user_data); +void on_tbscanhome_clicked (GtkToolButton *toolbutton, gpointer user_data); +void on_tbscanall_clicked (GtkToolButton *toolbutton, gpointer user_data); +void on_tbstop_clicked (GtkToolButton *toolbutton, gpointer user_data); +void on_tbrescan_clicked (GtkToolButton *toolbutton, gpointer user_data); +void on_radio_allfs_clicked (GtkButton *button, gpointer user_data); +void on_radio_dir_clicked (GtkButton *button, gpointer user_data); +void on_esci1_activate (GtkObject *object, gpointer user_data); +gboolean on_delete_activate (GtkWidget *widget, GdkEvent *event, gpointer user_data); +void open_file_cb (GtkMenuItem *pmenu, gpointer dummy); +void scan_folder_cb (GtkMenuItem *pmenu, gpointer dummy); +void trash_dir_cb (GtkMenuItem *pmenu, gpointer dummy); +void list_all_cb (GtkMenuItem *pmenu, gpointer dummy); +void on_pref_menu (GtkAction *a, gpointer user_data); +void on_tb_scan_remote_clicked (GtkToolButton *toolbutton, gpointer user_data); +void on_menu_scan_rem_activate (GtkMenuItem *menuitem, gpointer user_data); +void on_view_tb_activate (GtkToggleAction *action, gpointer user_data); +void on_view_sb_activate (GtkToggleAction *action, gpointer user_data); +void on_ck_allocated_activate (GtkToggleAction *action, gpointer user_data); +void on_helpcontents_activate (GtkAction *a, gpointer user_data); +void on_tv_selection_changed (GtkTreeSelection *selection, gpointer user_data); +void on_move_upwards_cb (GtkCheckMenuItem *checkmenuitem, gpointer user_data); +void on_zoom_in_cb (GtkCheckMenuItem *checkmenuitem, gpointer user_data); +void on_zoom_out_cb (GtkCheckMenuItem *checkmenuitem, gpointer user_data); +void on_chart_snapshot_cb (GtkCheckMenuItem *checkmenuitem, gpointer user_data); +void on_chart_type_change (GtkWidget *combo, gpointer user_data); + +#endif /* __BAOBAB_CALLBACKS_H__ */ |