[baobab] Port chart widgets to Vala
- From: Stefano Facchini <sfacchini src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [baobab] Port chart widgets to Vala
- Date: Thu, 13 Jun 2013 11:34:10 +0000 (UTC)
commit d6b347265c66c5fa059d2a5ae0e1285aeb23e311
Author: Stefano Facchini <stefano facchini gmail com>
Date: Sun Jun 9 17:37:18 2013 +0200
Port chart widgets to Vala
Almost a line-by-line translation of the original C code. Main
differences are:
* _freeze() and _thaw() methods are removed (now useless as a spinner is
used for the busy state
* GObject properties are used throughout the code
* contextual menu implemented with GMenu/GActions
* slight rework of the subfolder tips code in RingsChart
src/Makefile.am | 12 +-
src/baobab-chart.c | 1772 --------------------------------------------
src/baobab-chart.h | 161 ----
src/baobab-chart.vala | 630 ++++++++++++++++
src/baobab-menu.ui | 18 +
src/baobab-ringschart.c | 683 -----------------
src/baobab-ringschart.h | 74 --
src/baobab-ringschart.vala | 399 ++++++++++
src/baobab-treemap.c | 354 ---------
src/baobab-treemap.h | 65 --
src/baobab-treemap.vala | 200 +++++
src/baobab-window.vala | 8 +-
src/baobab.vapi | 36 -
13 files changed, 1252 insertions(+), 3160 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index b1ad1c4..4e92fd0 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -16,14 +16,12 @@ baobab_VALAFLAGS = \
--pkg gio-unix-2.0 \
--gresources=baobab.gresource.xml
-noinst_HEADERS = \
- baobab-chart.h \
- baobab-treemap.h \
- baobab-ringschart.h
-
VALA_SOURCES = \
baobab-application.vala \
baobab-cellrenderers.vala \
+ baobab-chart.vala \
+ baobab-treemap.vala \
+ baobab-ringschart.vala \
baobab-connect-server.vala \
baobab-location.vala \
baobab-location-list.vala \
@@ -40,11 +38,7 @@ egg_list_box_sources = \
baobab_SOURCES = \
$(VALA_SOURCES) \
fixes.vapi \
- baobab.vapi \
config.vapi \
- baobab-chart.c \
- baobab-treemap.c \
- baobab-ringschart.c \
$(egg_list_box_sources) \
$(BUILT_SOURCES)
diff --git a/src/baobab-chart.vala b/src/baobab-chart.vala
new file mode 100644
index 0000000..b2ec662
--- /dev/null
+++ b/src/baobab-chart.vala
@@ -0,0 +1,630 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* Baobab - disk usage analyzer
+ *
+ * Copyright (C) 2006, 2007, 2008 Igalia
+ * Copyright (C) 2013 Stefano Facchini <stefano facchini gmail com>
+ *
+ * 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 of the original code:
+ * Felipe Erias <femorandeira igalia com>
+ * Pablo Santamaria <psantamaria igalia com>
+ * Jacobo Aragunde <jaragunde igalia com>
+ * Eduardo Lima <elima igalia com>
+ * Mario Sanchez <msanchez igalia com>
+ * Miguel Gomez <magomez igalia com>
+ * Henrique Ferreiro <hferreiro igalia com>
+ * Alejandro Pinheiro <apinheiro igalia com>
+ * Carlos Sanmartin <csanmartin igalia com>
+ * Alejandro Garcia <alex igalia com>
+ */
+
+namespace Baobab {
+
+ public abstract class ChartItem {
+ public string name;
+ public string size;
+ public uint depth;
+ public double rel_start;
+ public double rel_size;
+ public Gtk.TreeIter iter;
+ public bool visible;
+ public bool has_any_child;
+ public bool has_visible_children;
+ public Gdk.Rectangle rect;
+
+ public unowned List<ChartItem> parent;
+ }
+
+ public abstract class Chart : Gtk.Widget {
+
+ protected const uint MAX_DEPTH = 8;
+ protected const uint MIN_DEPTH = 1;
+
+ const Gdk.RGBA TANGO_COLORS[] = {{0.94, 0.16, 0.16, 1.0}, /* tango: ef2929 */
+ {0.68, 0.49, 0.66, 1.0}, /* tango: ad7fa8 */
+ {0.45, 0.62, 0.82, 1.0}, /* tango: 729fcf */
+ {0.54, 0.89, 0.20, 1.0}, /* tango: 8ae234 */
+ {0.91, 0.73, 0.43, 1.0}, /* tango: e9b96e */
+ {0.99, 0.68, 0.25, 1.0}}; /* tango: fcaf3e */
+
+ uint name_column;
+ uint size_column;
+ uint info_column;
+ uint percentage_column;
+ uint valid_column;
+
+ bool model_changed;
+
+ Gtk.Menu context_menu = null;
+
+ List<ChartItem> items;
+
+ uint max_depth_ = MAX_DEPTH;
+ public uint max_depth {
+ set {
+ var m = value.clamp (MIN_DEPTH, MAX_DEPTH);
+ if (max_depth_ == m) {
+ return;
+ }
+ max_depth_ = m;
+ model_changed = true;
+ queue_draw ();
+ }
+ get {
+ return max_depth_;
+ }
+ }
+
+ Gtk.TreeModel model_;
+ public Gtk.TreeModel model {
+ set {
+ if (model_ == value) {
+ return;
+ }
+
+ if (model_ != null) {
+ disconnect_model_signals (model_);
+ }
+
+ model_ = value;
+ root = null;
+
+ connect_model_signals (model_);
+
+ queue_draw ();
+ }
+ get {
+ return model_;
+ }
+ }
+
+ public void set_model_with_columns (Gtk.TreeModel m,
+ uint name_column_,
+ uint size_column_,
+ uint info_column_,
+ uint percentage_column_,
+ uint valid_column_,
+ Gtk.TreePath? root_) {
+ model = m;
+ if (root_ != null) {
+ root = root_;
+ }
+
+ name_column = name_column_;
+ size_column = size_column_;
+ info_column = info_column_;
+ percentage_column = percentage_column_;
+ valid_column = valid_column_;
+ }
+
+ Gtk.TreeRowReference? root_;
+ public Gtk.TreePath? root {
+ set {
+ if (root_ != null) {
+ var current_root = root_.get_path ();
+ if (current_root != null && current_root.compare (value) == 0) {
+ return;
+ }
+ } else if (value == null) {
+ return;
+ }
+ root_ = (value != null) ? new Gtk.TreeRowReference (model, value) : null;
+
+ queue_draw ();
+ }
+ owned get {
+ if (root_ != null) {
+ var path = root_.get_path ();
+ if (path != null) {
+ return path;
+ }
+ root_ = null;
+ }
+ return new Gtk.TreePath.first ();
+ }
+ }
+
+ ChartItem? highlighted_item_ = null;
+ public ChartItem? highlighted_item {
+ set {
+ if (highlighted_item_ == value) {
+ return;
+ }
+
+ if (highlighted_item_ != null) {
+ get_window ().invalidate_rect (highlighted_item_.rect, true);
+ }
+ if (value != null) {
+ get_window ().invalidate_rect (value.rect, true);
+ }
+
+ highlighted_item_ = value;
+ }
+ get {
+ return highlighted_item_;
+ }
+ }
+
+ public virtual signal void item_activated (Gtk.TreeIter iter) {
+ root = model.get_path (iter);
+ }
+
+ protected virtual void post_draw (Cairo.Context cr) {
+ }
+
+ protected abstract void draw_item (Cairo.Context cr, ChartItem item, bool highlighted);
+ protected abstract void calculate_item_geometry (ChartItem item);
+
+ protected abstract bool is_point_over_item (ChartItem item, double x, double y);
+
+ protected abstract void get_item_rectangle (ChartItem item);
+
+ protected abstract bool can_zoom_in ();
+ protected abstract bool can_zoom_out ();
+
+ protected abstract ChartItem create_new_chartitem ();
+
+ SimpleActionGroup action_group;
+
+ const ActionEntry[] action_entries = {
+ { "move-up", move_up_root },
+ { "zoom-in", zoom_in },
+ { "zoom-out", zoom_out }
+ };
+
+ construct {
+ action_group = new SimpleActionGroup ();
+ action_group.add_action_entries (action_entries, this);
+ insert_action_group ("chart", action_group);
+ }
+
+ public override void realize () {
+ Gtk.Allocation allocation;
+ get_allocation (out allocation);
+ set_realized (true);
+
+ Gdk.WindowAttr attributes = {};
+ attributes.window_type = Gdk.WindowType.CHILD;
+ attributes.x = allocation.x;
+ attributes.y = allocation.y;
+ attributes.width = allocation.width;
+ attributes.height = allocation.height;
+ attributes.wclass = Gdk.WindowWindowClass.INPUT_OUTPUT;
+ //attributes.visual = gtk_widget_get_visual (widget);
+ attributes.event_mask = this.get_events () | Gdk.EventMask.EXPOSURE_MASK |
Gdk.EventMask.ENTER_NOTIFY_MASK | Gdk.EventMask.LEAVE_NOTIFY_MASK | Gdk.EventMask.BUTTON_PRESS_MASK |
Gdk.EventMask.POINTER_MOTION_MASK | Gdk.EventMask.POINTER_MOTION_HINT_MASK | Gdk.EventMask.SCROLL_MASK;
+
+ var window = new Gdk.Window (get_parent_window (), attributes, Gdk.WindowAttributesType.X |
Gdk.WindowAttributesType.Y);
+
+ set_window (window);
+ window.set_user_data (this);
+
+ get_style_context ().set_background (window);
+ }
+
+ public override void size_allocate (Gtk.Allocation allocation) {
+ set_allocation (allocation);
+ if (get_realized ()) {
+ get_window ().move_resize (allocation.x,
+ allocation.y,
+ allocation.width,
+ allocation.height);
+
+ foreach (ChartItem item in items) {
+ item.has_visible_children = false;
+ item.visible = false;
+ calculate_item_geometry (item);
+ }
+ }
+ }
+
+ public override bool motion_notify_event (Gdk.EventMotion event) {
+ bool found = false;
+
+ for (unowned List<ChartItem> node = items.last (); node != null; node = node.prev) {
+ var item = node.data;
+ if (item.visible && is_point_over_item (item, event.x, event.y)) {
+ highlighted_item = item;
+ has_tooltip = true;
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ highlighted_item = null;
+ has_tooltip = false;
+ }
+
+ Gdk.Event.request_motions (event);
+
+ return false;
+ }
+
+ unowned List<ChartItem> add_item (uint depth, double rel_start, double rel_size, Gtk.TreeIter iter) {
+ string name;
+ uint64 size;
+ model.get (iter, name_column, out name, size_column, out size, -1);
+
+ var item = create_new_chartitem ();
+ item.name = name;
+ item.size = format_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;
+
+ items.prepend (item);
+
+ unowned List<ChartItem> ret = items;
+ return ret;
+ }
+
+ void get_items (Gtk.TreePath root_path) {
+ unowned List<ChartItem> node = null;
+ Gtk.TreeIter initial_iter = {0};
+ double size;
+ Gtk.TreePath model_root_path;
+ Gtk.TreeIter model_root_iter;
+ Gtk.TreeIter child_iter = {0};
+ unowned List<ChartItem> child_node;
+ double rel_start;
+
+ items = null;
+
+ if (!model.get_iter (out initial_iter, root_path)) {
+ model_changed = false;
+ return;
+ }
+
+ model_root_path = new Gtk.TreePath.first ();
+ model.get_iter (out model_root_iter, model_root_path);
+ model.get (model_root_iter, percentage_column, out size, -1);
+
+ node = add_item (0, 0, 100, initial_iter);
+
+ do {
+ ChartItem item = node.data;
+ item.has_any_child = model.iter_children (out child_iter, item.iter);
+
+ calculate_item_geometry (item);
+
+ if (!item.visible) {
+ node = node.prev;
+ continue;
+ }
+
+ if ((item.has_any_child) && (item.depth < max_depth + 1)) {
+ rel_start = 0;
+ do {
+ model.get (child_iter, percentage_column, out size, -1);
+ child_node = add_item (item.depth + 1, rel_start, size, child_iter);
+ var child = child_node.data;
+ child.parent = node;
+ rel_start += size;
+ } while (model.iter_next (ref child_iter));
+ }
+
+ node = node.prev;
+ } while (node != null);
+
+ items.reverse ();
+
+ model_changed = false;
+ }
+
+ void draw_chart (Cairo.Context cr) {
+ cr.save ();
+
+ foreach (ChartItem item in items) {
+ Gdk.Rectangle clip;
+ if (Gdk.cairo_get_clip_rectangle (cr, out clip) &&
+ item.visible &&
+ clip.intersect (item.rect, null) &&
+ (item.depth <= max_depth)) {
+ bool highlighted = (item == highlighted_item);
+ draw_item (cr, item, highlighted);
+ }
+ }
+
+ cr.restore ();
+
+ post_draw (cr);
+ }
+
+ void update_draw (Gtk.TreePath path) {
+ if (!get_realized ()) {
+ return;
+ }
+
+ var root_depth = root.get_depth ();
+ var node_depth = path.get_depth ();
+
+ if (((node_depth - root_depth) <= max_depth) &&
+ (root.is_ancestor (path) ||
+ root.compare (path) == 0)) {
+ queue_draw ();
+ }
+ }
+
+ void row_changed (Gtk.TreeModel model,
+ Gtk.TreePath path,
+ Gtk.TreeIter iter) {
+ model_changed = true;
+ update_draw (path);
+ }
+
+ void row_inserted (Gtk.TreeModel model,
+ Gtk.TreePath path,
+ Gtk.TreeIter iter) {
+ model_changed = true;
+ update_draw (path);
+ }
+
+ void row_deleted (Gtk.TreeModel model,
+ Gtk.TreePath path) {
+ model_changed = true;
+ update_draw (path);
+ }
+
+ void row_has_child_toggled (Gtk.TreeModel model,
+ Gtk.TreePath path,
+ Gtk.TreeIter iter) {
+ model_changed = true;
+ update_draw (path);
+ }
+
+ void rows_reordered (Gtk.TreeModel model,
+ Gtk.TreePath path,
+ Gtk.TreeIter? iter,
+ void *new_order) {
+ model_changed = true;
+ update_draw (path);
+ }
+
+ public override bool draw (Cairo.Context cr) {
+ if (name_column == percentage_column) {
+ // Columns not set
+ return false;
+ }
+
+ if (model != null) {
+ cr.set_source_rgb (1, 1, 1);
+ cr.fill_preserve ();
+
+ if (model_changed || items == null) {
+ get_items (root);
+ } else {
+ var current_path = model.get_path (items.data.iter);
+ if (root.compare (current_path) != 0) {
+ get_items (root);
+ }
+ }
+
+ draw_chart (cr);
+ }
+
+ return false;
+ }
+
+ Gdk.RGBA interpolate_colors (Gdk.RGBA colora, Gdk.RGBA colorb, double percentage) {
+ var color = Gdk.RGBA ();
+
+ double 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;
+
+ color.alpha = 1.0;
+
+ return color;
+ }
+
+ protected Gdk.RGBA get_item_color (double rel_position, uint depth, bool highlighted) {
+ const Gdk.RGBA level_color = {0.83, 0.84, 0.82, 1.0};
+ const Gdk.RGBA level_color_hi = {0.88, 0.89, 0.87, 1.0};
+
+
+ var color = Gdk.RGBA ();
+
+ double intensity = 1 - (((depth - 1) * 0.3) / MAX_DEPTH);
+
+ if (depth == 0) {
+ color = level_color;
+ } else {
+ int color_number = (int) (rel_position / (100.0/3));
+ int next_color_number = (color_number + 1) % 6;
+
+ color = interpolate_colors (TANGO_COLORS[color_number],
+ TANGO_COLORS[next_color_number],
+ (rel_position - color_number * 100/3) / (100/3));
+ color.red *= intensity;
+ color.green *= intensity;
+ color.blue *= intensity;
+ }
+
+ if (highlighted) {
+ if (depth == 0) {
+ color = level_color_hi;
+ } else {
+ double maximum = double.max (color.red, double.max (color.green, color.blue));
+ color.red /= maximum;
+ color.green /= maximum;
+ color.blue /= maximum;
+ }
+ }
+
+ return color;
+ }
+
+ protected override bool button_press_event (Gdk.EventButton event) {
+ if (event.type == Gdk.EventType.BUTTON_PRESS) {
+ if (((Gdk.Event) (&event)).triggers_context_menu ()) {
+ show_popup_menu (event);
+ return true;
+ }
+
+ switch (event.button) {
+ case 1:
+ if (highlighted_item != null) {
+ var path = model.get_path (highlighted_item.iter);
+ if (root.compare (path) == 0) {
+ move_up_root ();
+ } else {
+ item_activated (highlighted_item.iter);
+ }
+ }
+ break;
+ case 2:
+ move_up_root ();
+ break;
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ protected override bool scroll_event (Gdk.EventScroll event) {
+ Gdk.EventMotion *e = (Gdk.EventMotion *) (&event);
+ switch (event.direction) {
+ case Gdk.ScrollDirection.LEFT:
+ case Gdk.ScrollDirection.UP:
+ zoom_out ();
+ motion_notify_event (*e);
+ break;
+ case Gdk.ScrollDirection.RIGHT:
+ case Gdk.ScrollDirection.DOWN:
+ zoom_in ();
+ motion_notify_event (*e);
+ break;
+ case Gdk.ScrollDirection.SMOOTH:
+ break;
+ }
+
+ return false;
+ }
+
+ public void move_up_root () {
+ Gtk.TreeIter iter, parent_iter;
+
+ model.get_iter (out iter, root);
+ if (model.iter_parent (out parent_iter, iter)) {
+ root = model.get_path (parent_iter);
+ item_activated (parent_iter);
+ }
+ }
+
+ public void zoom_in () {
+ if (can_zoom_in ()) {
+ max_depth--;
+ }
+ }
+
+ public void zoom_out () {
+ if (can_zoom_out ()) {
+ max_depth++;
+ }
+ }
+
+ void ensure_context_menu () {
+ if (context_menu != null) {
+ return;
+ }
+
+ var builder = new Gtk.Builder ();
+ try {
+ builder.add_from_resource ("/org/gnome/baobab/ui/baobab-menu.ui");
+ } catch (Error e) {
+ error ("loading context menu from resources: %s", e.message);
+ }
+
+ var menu_model = builder.get_object ("chartmenu") as MenuModel;
+ context_menu = new Gtk.Menu.from_model (menu_model);
+ context_menu.attach_to_widget (this, null);
+ }
+
+ void show_popup_menu (Gdk.EventButton? event) {
+ ensure_context_menu ();
+
+ if (event != null) {
+ context_menu.popup (null, null, null, event.button, event.time);
+ } else {
+ context_menu.popup (null, null, null, 0, Gtk.get_current_event_time ());
+ }
+ }
+
+ void connect_model_signals (Gtk.TreeModel m) {
+ m.row_changed.connect (row_changed);
+ m.row_inserted.connect (row_inserted);
+ m.row_has_child_toggled.connect (row_has_child_toggled);
+ m.row_deleted.connect (row_deleted);
+ m.rows_reordered.connect (rows_reordered);
+ }
+
+ void disconnect_model_signals (Gtk.TreeModel m) {
+ m.row_changed.disconnect (row_changed);
+ m.row_inserted.disconnect (row_inserted);
+ m.row_has_child_toggled.disconnect (row_has_child_toggled);
+ m.row_deleted.disconnect (row_deleted);
+ m.rows_reordered.disconnect (rows_reordered);
+ }
+
+ protected override bool query_tooltip (int x, int y, bool keyboard_tooltip, Gtk.Tooltip tooltip) {
+ if (highlighted_item == null ||
+ highlighted_item.name == null ||
+ highlighted_item.size == null) {
+ return false;
+ }
+
+ tooltip.set_tip_area (highlighted_item.rect);
+
+ var markup = highlighted_item.name + "\n" + highlighted_item.size;
+ tooltip.set_markup (Markup.escape_text (markup));
+
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/baobab-menu.ui b/src/baobab-menu.ui
index 75566d1..50647bb 100644
--- a/src/baobab-menu.ui
+++ b/src/baobab-menu.ui
@@ -83,4 +83,22 @@
</section>
</submenu>
</menu>
+ <menu id="chartmenu">
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_Move to parent folder</attribute>
+ <attribute name="action">chart.move-up</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">Zoom _in</attribute>
+ <attribute name="action">chart.zoom-in</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Zoom _out</attribute>
+ <attribute name="action">chart.zoom-out</attribute>
+ </item>
+ </section>
+ </menu>
</interface>
diff --git a/src/baobab-ringschart.vala b/src/baobab-ringschart.vala
new file mode 100644
index 0000000..cd04447
--- /dev/null
+++ b/src/baobab-ringschart.vala
@@ -0,0 +1,399 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* Baobab - disk usage analyzer
+ *
+ * Copyright (C) 2008 Igalia
+ * Copyright (C) 2013 Stefano Facchini <stefano facchini gmail com>
+ *
+ * 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 of the original code:
+ * Felipe Erias <femorandeira igalia com>
+ * Pablo Santamaria <psantamaria igalia com>
+ * Jacobo Aragunde <jaragunde igalia com>
+ * Eduardo Lima <elima igalia com>
+ * Mario Sanchez <msanchez igalia com>
+ * Miguel Gomez <magomez igalia com>
+ * Henrique Ferreiro <hferreiro igalia com>
+ * Alejandro Pinheiro <apinheiro igalia com>
+ * Carlos Sanmartin <csanmartin igalia com>
+ * Alejandro Garcia <alex igalia com>
+ */
+
+namespace Baobab {
+
+ class RingschartItem : ChartItem {
+ public double min_radius;
+ public double max_radius;
+ public double start_angle;
+ public double angle;
+ public bool continued;
+ }
+
+ public class Ringschart : Chart {
+
+ const int ITEM_BORDER_WIDTH = 1;
+ const int CHART_PADDING = 13;
+ const double ITEM_MIN_ANGLE = 0.03;
+ const double EDGE_ANGLE = 0.004;
+
+ const int SUBFOLDER_TIP_PADDING = 3;
+
+ int subtip_timeout;
+ uint tips_timeout_id = 0;
+ bool drawing_subtips = false;
+ List<ChartItem> subtip_items;
+
+ void subtips_update () {
+ if (drawing_subtips) {
+ queue_draw ();
+ }
+ drawing_subtips = false;
+
+ if (tips_timeout_id != 0) {
+ Source.remove (tips_timeout_id);
+ tips_timeout_id = 0;
+ }
+
+ subtip_items = null;
+
+ if (highlighted_item != null) {
+ tips_timeout_id = Timeout.add (subtip_timeout, () => {
+ drawing_subtips = true;
+ queue_draw ();
+ return false;
+ });
+ }
+ }
+
+ construct {
+ subtip_timeout = Gtk.Settings.get_default ().gtk_tooltip_timeout * 2;
+
+ notify["max-depth"].connect (subtips_update);
+ notify["highlighted-item"].connect (subtips_update);
+ }
+
+ protected override ChartItem create_new_chartitem () {
+ return (new RingschartItem () as ChartItem);
+ }
+
+ protected override void post_draw (Cairo.Context cr) {
+ if (!drawing_subtips) {
+ return;
+ }
+
+ Gtk.Allocation allocation;
+ get_allocation (out allocation);
+
+ var q_width = allocation.width / 2;
+ var q_height = allocation.height / 2;
+ var q_angle = Math.atan2 (q_height, q_width);
+
+ Gdk.Rectangle last_rect = Gdk.Rectangle ();
+
+ foreach (ChartItem item in subtip_items) {
+ RingschartItem ringsitem = item as RingschartItem;
+
+ // get the middle angle
+ var middle_angle = ringsitem.start_angle + ringsitem.angle / 2;
+ // normalize the middle angle (take it to the first quadrant
+ var middle_angle_n = middle_angle;
+ while (middle_angle_n > Math.PI / 2) {
+ middle_angle_n -= Math.PI;
+ }
+ middle_angle_n = Math.fabs (middle_angle_n);
+
+ // get the pango layout and its enclosing rectangle
+ var layout = create_pango_layout (null);
+ var markup = "<span size=\"small\">" + Markup.escape_text (item.name) + "</span>";
+ layout.set_markup (markup, -1);
+ layout.set_indent (0);
+ layout.set_spacing (0);
+ Pango.Rectangle layout_rect;
+ layout.get_pixel_extents (null, out layout_rect);
+
+ // get the center point of the tooltip rectangle
+ double tip_x, tip_y;
+ if (middle_angle_n < q_angle) {
+ tip_x = q_width - layout_rect.width / 2 - SUBFOLDER_TIP_PADDING * 2;
+ tip_y = Math.tan (middle_angle_n) * tip_x;
+ } else {
+ tip_y = q_height - layout_rect.height / 2 - SUBFOLDER_TIP_PADDING * 2;
+ tip_x = tip_y / Math.tan (middle_angle_n);
+ }
+
+ // get the tooltip rectangle
+ Cairo.Rectangle tooltip_rect = Cairo.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) {
+ continue;
+ }
+
+ // translate tooltip rectangle and edge angles to the original quadrant
+ var a = middle_angle;
+ int i = 0;
+ while (a > Math.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 -= Math.PI / 2;
+ }
+
+ // get the Gdk.Rectangle of the tooltip (with a little padding)
+ Gdk.Rectangle _rect = Gdk.Rectangle ();
+ _rect.x = (int) (tooltip_rect.x - 1);
+ _rect.y = (int) (tooltip_rect.y - 1);
+ _rect.width = (int) (tooltip_rect.width + 2);
+ _rect.height = (int) (tooltip_rect.height + 2);
+
+ // check if tooltip overlaps
+ if (!_rect.intersect (last_rect, null)) {
+ last_rect = _rect;
+
+ // finally draw the tooltip!
+
+ // TODO: do not hardcode colors
+
+ // avoid blurred lines
+ tooltip_rect.x = (int) Math.floor (tooltip_rect.x) + 0.5;
+ tooltip_rect.y = (int) Math.floor (tooltip_rect.y) + 0.5;
+
+ var middle_radius = ringsitem.min_radius + (ringsitem.max_radius - ringsitem.min_radius)
/ 2;
+ var sector_center_x = q_width + middle_radius * Math.cos (middle_angle);
+ var sector_center_y = q_height + middle_radius * Math.sin (middle_angle);
+
+ // draw line from sector center to tooltip center
+ cr.set_line_width (1);
+ cr.move_to (sector_center_x, sector_center_y);
+ cr.set_source_rgb (0.8275, 0.8431, 0.8118); // tango: #d3d7cf
+ cr.line_to (tooltip_rect.x + tooltip_rect.width / 2,
+ tooltip_rect.y + tooltip_rect.height / 2);
+ cr.stroke ();
+
+ // draw a tiny circle in sector center
+ cr.set_source_rgba (1.0, 1.0, 1.0, 0.6666);
+ cr.arc (sector_center_x, sector_center_y, 1.0, 0, 2 * Math.PI);
+ cr.stroke ();
+
+ // draw tooltip box
+ cr.set_line_width (0.5);
+ cr.rectangle (tooltip_rect.x, tooltip_rect.y, tooltip_rect.width, tooltip_rect.height);
+ cr.set_source_rgb (0.8275, 0.8431, 0.8118); // tango: #d3d7cf
+ cr.fill_preserve ();
+ cr.set_source_rgb (0.5333, 0.5412, 0.5216); // tango: #888a85
+ cr.stroke ();
+
+ // draw the text inside the box
+ cr.set_source_rgb (0, 0, 0);
+ cr.move_to (tooltip_rect.x + SUBFOLDER_TIP_PADDING, tooltip_rect.y +
SUBFOLDER_TIP_PADDING);
+ Pango.cairo_show_layout (cr, layout);
+ cr.set_line_width (1);
+ cr.stroke ();
+ }
+ }
+ }
+
+ void draw_sector (Cairo.Context cr,
+ double center_x,
+ double center_y,
+ double radius,
+ double thickness,
+ double init_angle,
+ double final_angle,
+ Gdk.RGBA fill_color,
+ bool continued,
+ uint border) {
+ cr.set_line_width (border);
+ if (radius > 0) {
+ cr.arc (center_x, center_y, radius, init_angle, final_angle);
+ }
+ cr.arc_negative (center_x, center_y, radius + thickness, final_angle, init_angle);
+
+ cr.close_path ();
+
+ Gdk.cairo_set_source_rgba (cr, fill_color);
+ cr.fill_preserve ();
+ cr.set_source_rgb (0, 0, 0);
+ cr.stroke ();
+
+ if (continued) {
+ cr.set_line_width (3);
+ cr.arc (center_x, center_y, radius + thickness + 4, init_angle + EDGE_ANGLE, final_angle -
EDGE_ANGLE);
+ cr.stroke ();
+ }
+ }
+
+ protected override void draw_item (Cairo.Context cr, ChartItem item, bool highlighted) {
+ RingschartItem ringsitem = item as RingschartItem;
+
+ if (drawing_subtips) {
+ if (highlighted_item != null &&
+ item.parent != null &&
+ item.parent.data == highlighted_item) {
+ subtip_items.prepend (item);
+ }
+ }
+
+ var fill_color = get_item_color (ringsitem.start_angle / Math.PI * 99,
+ item.depth,
+ highlighted);
+
+ Gtk.Allocation allocation;
+ get_allocation (out allocation);
+
+ draw_sector (cr,
+ allocation.width / 2,
+ allocation.height / 2,
+ ringsitem.min_radius,
+ ringsitem.max_radius - ringsitem.min_radius,
+ ringsitem.start_angle,
+ ringsitem.start_angle + ringsitem.angle,
+ fill_color,
+ ringsitem.continued,
+ ITEM_BORDER_WIDTH);
+ }
+
+ protected override void calculate_item_geometry (ChartItem item) {
+ RingschartItem ringsitem = item as RingschartItem;
+
+ ringsitem.continued = false;
+ ringsitem.visible = false;
+
+ Gtk.Allocation allocation;
+ get_allocation (out allocation);
+ var max_radius = int.min (allocation.width / 2, allocation.height / 2) - CHART_PADDING;
+ var thickness = max_radius / (max_depth + 1);
+
+ if (ringsitem.parent == null) {
+ ringsitem.min_radius = 0;
+ ringsitem.max_radius = thickness;
+ ringsitem.start_angle = 0;
+ ringsitem.angle = 2 * Math.PI;
+ } else {
+ var parent = item.parent.data as RingschartItem;
+ ringsitem.min_radius = ringsitem.depth * thickness;
+ if (ringsitem.depth > max_depth) {
+ return;
+ } else {
+ ringsitem.max_radius = ringsitem.min_radius + thickness;
+ }
+
+ ringsitem.angle = parent.angle * ringsitem.rel_size / 100;
+ if (ringsitem.angle < ITEM_MIN_ANGLE) {
+ return;
+ }
+
+ ringsitem.start_angle = parent.start_angle + parent.angle * ringsitem.rel_start / 100;
+ ringsitem.continued = (ringsitem.has_any_child) && (ringsitem.depth == max_depth);
+ parent.has_visible_children = true;
+ }
+
+ ringsitem.visible = true;
+ get_item_rectangle (item);
+ }
+
+ void get_point_min_rect (double cx, double cy, double radius, double angle, ref Gdk.Rectangle r) {
+ double x, y;
+ x = cx + Math.cos (angle) * radius;
+ y = cy + Math.sin (angle) * radius;
+
+ r.x = int.min (r.x, (int)x);
+ r.y = int.min (r.y, (int)y);
+ r.width = int.max (r.width, (int)x);
+ r.height = int.max (r.height, (int)y);
+ }
+
+ protected override void get_item_rectangle (ChartItem item) {
+ RingschartItem ringsitem = item as RingschartItem;
+ Gdk.Rectangle rect = Gdk.Rectangle ();
+ double cx, cy, r1, r2, a1, a2;
+ Gtk.Allocation allocation;
+
+ get_allocation (out allocation);
+ cx = allocation.width / 2;
+ cy = allocation.height / 2;
+ r1 = ringsitem.min_radius;
+ r2 = ringsitem.max_radius;
+ a1 = ringsitem.start_angle;
+ a2 = ringsitem.start_angle + ringsitem.angle;
+
+ rect.x = allocation.width;
+ rect.y = allocation.height;
+ rect.width = 0;
+ rect.height = 0;
+
+ get_point_min_rect (cx, cy, r1, a1, ref rect);
+ get_point_min_rect (cx, cy, r2, a1, ref rect);
+ get_point_min_rect (cx, cy, r1, a2, ref rect);
+ get_point_min_rect (cx, cy, r2, a2, ref rect);
+
+ if ((a1 <= Math.PI / 2) && (a2 >= Math.PI / 2)) {
+ rect.height = (int) double.max (rect.height, cy + Math.sin (Math.PI / 2) * r2);
+ }
+
+ if ((a1 <= Math.PI) && (a2 >= Math.PI)) {
+ rect.x = (int) double.min (rect.x, cx + Math.cos (Math.PI) * r2);
+ }
+
+ if ((a1 <= Math.PI * 1.5) && (a2 >= Math.PI * 1.5)) {
+ rect.y = (int) double.min (rect.y, cy + Math.sin (Math.PI * 1.5) * r2);
+ }
+
+ if ((a1 <= Math.PI * 2) && (a2 >= Math.PI * 2)) {
+ rect.width = (int) double.max (rect.width, cx + Math.cos (Math.PI * 2) * r2);
+ }
+
+ rect.width -= rect.x;
+ rect.height -= rect.y;
+
+ ringsitem.rect = rect;
+ }
+
+ protected override bool is_point_over_item (ChartItem item, double x, double y) {
+ var ringsitem = item as RingschartItem;
+
+ Gtk.Allocation allocation;
+ get_allocation (out allocation);
+
+ x -= allocation.width / 2;
+ y -= allocation.height / 2;
+
+ var radius = Math.sqrt (x * x + y * y);
+ var angle = Math.atan2 (y, x);
+ angle = (angle > 0) ? angle : (angle + 2 * Math.PI);
+
+ return ((radius >= ringsitem.min_radius) &&
+ (radius <= ringsitem.max_radius) &&
+ (angle >= ringsitem.start_angle) &&
+ (angle <= (ringsitem.start_angle + ringsitem.angle)));
+ }
+
+ protected override bool can_zoom_out () {
+ return (max_depth < MAX_DEPTH);
+ }
+
+ protected override bool can_zoom_in () {
+ return (max_depth > MIN_DEPTH);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/baobab-treemap.vala b/src/baobab-treemap.vala
new file mode 100644
index 0000000..a38140f
--- /dev/null
+++ b/src/baobab-treemap.vala
@@ -0,0 +1,200 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* Baobab - disk usage analyzer
+ *
+ * Copyright (C) 2008 Igalia
+ * Copyright (C) 2013 Stefano Facchini <stefano facchini gmail com>
+ *
+ * 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 of the original code:
+ * Fabio Marzocca <thesaltydog gmail com>
+ * Paolo Borelli <pborelli katamail com>
+ * Miguel Gomez <magomez igalia com>
+ * Eduardo Lima Mitev <elima igalia com>
+ */
+
+namespace Baobab {
+
+ class TreemapItem : ChartItem {
+ public Cairo.Rectangle cr_rect;
+ }
+
+ public class Treemap : Chart {
+
+ const int ITEM_BORDER_WIDTH = 1;
+ const int ITEM_PADDING = 6;
+ const int ITEM_TEXT_PADDING = 3;
+
+ const int ITEM_MIN_WIDTH = 3;
+ const int ITEM_MIN_HEIGHT = 3;
+
+ uint max_visible_depth;
+ bool more_visible_children;
+
+ protected override ChartItem create_new_chartitem () {
+ return (new TreemapItem () as ChartItem);
+ }
+
+ void draw_rectangle (Cairo.Context cr,
+ double x,
+ double y,
+ double width,
+ double height,
+ Gdk.RGBA fill_color,
+ string text,
+ bool show_text) {
+ uint border = ITEM_BORDER_WIDTH;
+
+ cr.stroke ();
+
+ cr.set_line_width (border);
+ cr.rectangle (x + border, y + border, width - border * 2, height - border * 2);
+ Gdk.cairo_set_source_rgba (cr, fill_color);
+ cr.fill_preserve ();
+ cr.set_source_rgb (0, 0, 0);
+ cr.stroke ();
+
+ if (show_text) {
+ var layout = create_pango_layout (null);
+ var markup = Markup.escape_text (text);
+ layout.set_markup (markup, -1);
+
+ Pango.Rectangle rect;
+ layout.get_pixel_extents (null, out rect);
+
+ if ((rect.width + ITEM_TEXT_PADDING * 2 <= width) &&
+ (rect.height + ITEM_TEXT_PADDING * 2 <= height)) {
+ cr.move_to (x + width / 2 - rect.width / 2, y + height / 2 - rect.height / 2);
+ Pango.cairo_show_layout (cr, layout);
+ }
+ }
+ }
+
+ protected override void draw_item (Cairo.Context cr, ChartItem item, bool highlighted) {
+ Cairo.Rectangle rect;
+ Gdk.RGBA fill_color = {0};
+ Gtk.Allocation allocation;
+ double width = 0, height = 0;
+
+ rect = (item as TreemapItem).cr_rect;
+ get_allocation (out allocation);
+
+ if ((item.depth % 2) != 0) {
+ fill_color = get_item_color (rect.x / allocation.width * 200,
+ item.depth, highlighted);
+ width = rect.width - ITEM_PADDING;
+ height = rect.height;
+ } else {
+ fill_color = get_item_color (rect.y / allocation.height * 200,
+ item.depth, highlighted);
+ width = rect.width;
+ height = rect.height - ITEM_PADDING;
+ }
+
+ draw_rectangle (cr, rect.x, rect.y, width, height, fill_color, item.name,
(!item.has_visible_children));
+ }
+
+ protected override void calculate_item_geometry (ChartItem item) {
+ TreemapItem treemapitem = item as TreemapItem;
+ Cairo.Rectangle p_area = Cairo.Rectangle ();
+
+ if (item.depth == 0) {
+ max_visible_depth = 0;
+ more_visible_children = false;
+ }
+
+ item.visible = false;
+ if (item.parent == null) {
+ Gtk.Allocation allocation;
+ get_allocation (out allocation);
+ p_area.x = -ITEM_PADDING / 2;
+ p_area.y = -ITEM_PADDING / 2;
+ p_area.width = allocation.width + ITEM_PADDING * 2;
+ p_area.height = allocation.height + ITEM_PADDING * 2;
+ } else {
+ p_area = (item.parent.data as TreemapItem).cr_rect;
+ }
+
+ if (item.depth % 2 != 0) {
+ var width = p_area.width - ITEM_PADDING;
+
+ treemapitem.cr_rect.x = p_area.x + (item.rel_start * width / 100) + ITEM_PADDING;
+ treemapitem.cr_rect.y = p_area.y + ITEM_PADDING;
+ treemapitem.cr_rect.width = width * item.rel_size / 100;
+ treemapitem.cr_rect.height = p_area.height - ITEM_PADDING * 3;
+ } else {
+ var height = p_area.height - ITEM_PADDING;
+
+ treemapitem.cr_rect.x = p_area.x + ITEM_PADDING;
+ treemapitem.cr_rect.y = p_area.y + (item.rel_start * height / 100) + ITEM_PADDING;
+ treemapitem.cr_rect.width = p_area.width - ITEM_PADDING * 3;
+ treemapitem.cr_rect.height = height * item.rel_size / 100;
+ }
+ if ((treemapitem.cr_rect.width - ITEM_PADDING < ITEM_MIN_WIDTH) ||
+ (treemapitem.cr_rect.height - ITEM_PADDING < ITEM_MIN_HEIGHT)) {
+ return;
+ }
+
+ treemapitem.cr_rect.x = Math.floor (treemapitem.cr_rect.x) + 0.5;
+ treemapitem.cr_rect.y = Math.floor (treemapitem.cr_rect.y) + 0.5;
+ treemapitem.cr_rect.width = Math.floor (treemapitem.cr_rect.width);
+ treemapitem.cr_rect.height = Math.floor (treemapitem.cr_rect.height);
+
+ item.visible = true;
+
+ if (item.parent != null) {
+ item.parent.data.has_visible_children = true;
+ }
+
+ get_item_rectangle (item);
+
+ if (item.depth == max_depth + 1) {
+ more_visible_children = true;
+ } else {
+ max_visible_depth = uint.max (max_visible_depth, item.depth);
+ }
+ }
+
+ protected override void get_item_rectangle (ChartItem item) {
+ var crect = (item as TreemapItem).cr_rect;
+
+ item.rect.x = (int) crect.x;
+ item.rect.y = (int) crect.y;
+
+ if (item.depth % 2 != 0) {
+ item.rect.width = (int) crect.width - ITEM_PADDING;
+ item.rect.height = (int) crect.height;
+ } else {
+ item.rect.width = (int) crect.width;
+ item.rect.height = (int) crect.height - ITEM_PADDING;
+ }
+ }
+
+ protected override bool is_point_over_item (ChartItem item, double x, double y) {
+ var rect = item.rect;
+ return ((x >= rect.x) && (x <= (rect.x + rect.width)) &&
+ (y >= rect.y) && (y <= (rect.y + rect.height)));
+ }
+
+ protected override bool can_zoom_out () {
+ return more_visible_children;
+ }
+
+ protected override bool can_zoom_in () {
+ return (max_visible_depth > 1);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/baobab-window.vala b/src/baobab-window.vala
index bd42a18..5ba87b4 100644
--- a/src/baobab-window.vala
+++ b/src/baobab-window.vala
@@ -453,8 +453,8 @@ namespace Baobab {
Gtk.TreeIter iter;
if (selection.get_selected (null, out iter)) {
var path = active_location.scanner.get_path (iter);
- rings_chart.set_root (path);
- treemap_chart.set_root (path);
+ rings_chart.root = path;
+ treemap_chart.root = path;
}
});
}
@@ -476,15 +476,11 @@ namespace Baobab {
if (busy) {
cursor = busy_cursor;
disable_drop ();
- rings_chart.freeze_updates ();
- treemap_chart.freeze_updates ();
(lookup_action ("active-chart") as SimpleAction).set_enabled (false);
chart_stack.visible_child = spinner;
spinner.start ();
} else {
enable_drop ();
- rings_chart.thaw_updates ();
- treemap_chart.thaw_updates ();
(lookup_action ("active-chart") as SimpleAction).set_enabled (true);
spinner.stop ();
lookup_action ("active-chart").change_state (ui_settings.get_value ("active-chart"));
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]