///////////////////////////////////////////////////////////////////////// // Copyright (c) 2011-2016 by Simon Schneegans // // 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 3 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, see . ///////////////////////////////////////////////////////////////////////// using GLib.Math; namespace GnomePie { ///////////////////////////////////////////////////////////////////////// /// A complex class which is able to draw the preview of a Pie. It can /// manipulate the displayed Pie as well. ///////////////////////////////////////////////////////////////////////// public class PiePreviewRenderer : GLib.Object { ///////////////////////////////////////////////////////////////////// /// These signals get emitted when a slice is added, removed or /// manipulated. ///////////////////////////////////////////////////////////////////// public signal void on_add_slice(int position); public signal void on_remove_slice(int position); public signal void on_edit_slice(int position); ///////////////////////////////////////////////////////////////////// /// True, when there is currently a drag going on. ///////////////////////////////////////////////////////////////////// public bool drag_n_drop_mode { get; private set; default=false; } ///////////////////////////////////////////////////////////////////// /// A list containing all SliceRenderers of this Pie. ///////////////////////////////////////////////////////////////////// public Gee.ArrayList slices; ///////////////////////////////////////////////////////////////////// /// When a Slice is moved within a Pie it is temporarily removed. /// If so, it is stored in this member. ///////////////////////////////////////////////////////////////////// public PiePreviewSliceRenderer hidden_group { get; private set; default=null; } ///////////////////////////////////////////////////////////////////// /// The add sign which indicates that a new Slice could be added. ///////////////////////////////////////////////////////////////////// private PiePreviewAddSign add_sign = null; ///////////////////////////////////////////////////////////////////// /// The object which renders the name of the currently selected Slice /// in the middle. ///////////////////////////////////////////////////////////////////// private PiePreviewCenter center_renderer = null; private enum CenterDisplay { NONE, ACTIVE_SLICE, DROP, ADD, DELETE } ///////////////////////////////////////////////////////////////////// /// Some members storing some inter-frame-information. ///////////////////////////////////////////////////////////////////// private int active_slice = -1; private double angle = 0.0; private double mouse_x = 0.0; private double mouse_y = 0.0; ///////////////////////////////////////////////////////////////////// /// The parent DrawingArea. ///////////////////////////////////////////////////////////////////// public unowned Gtk.DrawingArea parent; ///////////////////////////////////////////////////////////////////// /// C'tor, initializes members. ///////////////////////////////////////////////////////////////////// public PiePreviewRenderer(Gtk.DrawingArea parent) { this.parent = parent; this.slices = new Gee.ArrayList(); this.center_renderer = new PiePreviewCenter(this); this.add_sign = new PiePreviewAddSign(this); this.add_sign.load(); this.add_sign.on_clicked.connect((pos) => { this.on_add_slice(pos); }); } ///////////////////////////////////////////////////////////////////// /// Loads an Pie. All members are initialized accordingly. ///////////////////////////////////////////////////////////////////// public void load_pie(Pie pie) { this.slices.clear(); foreach (var group in pie.action_groups) { var renderer = new PiePreviewSliceRenderer(this); renderer.load(group); this.add_slice_renderer(renderer); this.connect_siganls(renderer); } this.active_slice = -1; this.update_sizes(); this.update_positions(false); } ///////////////////////////////////////////////////////////////////// /// Enables or disables the drag n dropn mode. ///////////////////////////////////////////////////////////////////// public void set_dnd_mode(bool dnd) { if (this.drag_n_drop_mode != dnd) { this.drag_n_drop_mode = dnd; this.update_positions(); this.update_sizes(); } } ///////////////////////////////////////////////////////////////////// /// Returns the number of Slices. ///////////////////////////////////////////////////////////////////// public int slice_count() { if (this.drag_n_drop_mode && !(this.slices.size == 0)) return slices.size+1; return slices.size; } ///////////////////////////////////////////////////////////////////// /// Returns the index of the currently hovered Slice. ///////////////////////////////////////////////////////////////////// public int get_active_slice() { if (this.slices.size == 0) return 0; if (this.drag_n_drop_mode) return (int)(this.angle/(2*PI)*this.slice_count() + 0.5) % this.slice_count(); return this.active_slice; } ///////////////////////////////////////////////////////////////////// /// Returns the Icon of the currently hovered Slice. ///////////////////////////////////////////////////////////////////// public Icon get_active_icon() { if (this.active_slice >= 0 && this.active_slice < this.slices.size) return this.slices[this.active_slice].icon; else return new Icon("", 24); } ///////////////////////////////////////////////////////////////////// /// Draws the entire Pie to the given context. ///////////////////////////////////////////////////////////////////// public void draw(double frame_time, Cairo.Context ctx) { this.add_sign.draw(frame_time, ctx); this.center_renderer.draw(frame_time, ctx); foreach (var slice in this.slices) slice.draw(frame_time, ctx); } ///////////////////////////////////////////////////////////////////// /// Called when the mouse leaves the drawing area of this renderer. ///////////////////////////////////////////////////////////////////// public void on_mouse_leave() { this.add_sign.hide(); this.update_positions(); this.update_center(CenterDisplay.NONE); foreach (var slice in this.slices) slice.on_mouse_leave(); } ///////////////////////////////////////////////////////////////////// /// Called when the mouse enters the drawing area of this renderer. ///////////////////////////////////////////////////////////////////// public void on_mouse_enter() { this.add_sign.show(); this.update_positions(); } ///////////////////////////////////////////////////////////////////// /// Called when the mouse moves in the drawing area of this renderer. ///////////////////////////////////////////////////////////////////// public void on_mouse_move(double x, double y) { this.mouse_x = x; this.mouse_y = y; this.angle = acos(x/sqrt(x*x + y*y)); if (y < 0) this.angle = 2*PI - this.angle; if (!this.drag_n_drop_mode) this.active_slice = -1; bool delete_hovered = false; for (int i=0; i index) { this.slices.remove_at(index); this.update_positions(); this.update_sizes(); } } ///////////////////////////////////////////////////////////////////// /// Hides the Slice at the given position temporarily. ///////////////////////////////////////////////////////////////////// public void hide_group(int index) { if (this.slices.size > index) { this.hidden_group = this.slices[index]; this.remove_group(index); } } ///////////////////////////////////////////////////////////////////// /// Re-shows a Slice which has been hidden before. ///////////////////////////////////////////////////////////////////// public void show_hidden_group_at(int index) { if (this.slices.size >= index && this.hidden_group != null) { this.hidden_group.set_position(index, false); this.add_slice_renderer(this.hidden_group, index); this.hidden_group = null; } } ///////////////////////////////////////////////////////////////////// /// Updates a Slice at the given position. ///////////////////////////////////////////////////////////////////// public void update_group(ActionGroup group, int index) { if (this.slices.size > index) { var renderer = new PiePreviewSliceRenderer(this); this.slices.set(index, renderer); renderer.load(group); this.connect_siganls(renderer); this.update_positions(false); this.update_sizes(); } } ///////////////////////////////////////////////////////////////////// /// Disables all quickactions of this pie preview. ///////////////////////////////////////////////////////////////////// public void disable_quickactions() { foreach (var slice in this.slices) slice.disable_quickactions(); } ///////////////////////////////////////////////////////////////////// /// Helper method which adds a new Slice to the given position. ///////////////////////////////////////////////////////////////////// private void add_slice_renderer(PiePreviewSliceRenderer renderer, int at_position = -1) { if (at_position < 0 || at_position >= this.slices.size) this.slices.add(renderer); else this.slices.insert(at_position, renderer); this.update_positions(false); this.update_sizes(); } ///////////////////////////////////////////////////////////////////// /// Helper method which connects all neccessary signals of a newly /// added Slice. ///////////////////////////////////////////////////////////////////// private void connect_siganls(PiePreviewSliceRenderer renderer) { renderer.on_clicked.connect((pos) => { this.on_edit_slice(pos); }); renderer.on_remove.connect((pos) => { this.on_remove_slice(pos); }); } ///////////////////////////////////////////////////////////////////// /// Moves all slices to their positions. This may happen smoothly if /// desired. ///////////////////////////////////////////////////////////////////// private void update_positions(bool smoothly = true) { if (this.slices.size > 0) { if (this.add_sign.visible) { int add_position = 0; add_position = (int)(this.angle/(2*PI)*this.slice_count()) % this.slice_count(); this.add_sign.set_position(add_position); for (int i=0; i= add_position ? i+1 : i, smoothly); } this.update_center(CenterDisplay.DROP); } else { for (int i=0; i 20) size = 0.5; else if (this.slice_count() > 8) size = 1.0 - (double)(this.slice_count() - 8)/24.0; this.add_sign.set_size(size); for (int i=0; i= 0 && this.active_slice < this.slices.size) this.center_renderer.set_text("" + GLib.Markup.escape_text(slices[this.active_slice].name) + "\n" + _("Click to edit") + "\n" + _("Drag to move") + ""); break; case CenterDisplay.ADD: this.center_renderer.set_text("" + _("Click to add a new Slice") + ""); break; case CenterDisplay.DROP: if (hidden_group == null) this.center_renderer.set_text("" + _("Drop to add as new Slice") + ""); else this.center_renderer.set_text("" + GLib.Markup.escape_text(this.hidden_group.name) + "\n" + _("Drop to move Slice") + ""); break; case CenterDisplay.DELETE: if (this.active_slice >= 0 && this.active_slice < this.slices.size) this.center_renderer.set_text("" + GLib.Markup.escape_text(slices[this.active_slice].name) + "\n" + _("Click to delete") + "\n" + _("Drag to move") + ""); break; default: this.center_renderer.set_text(""); break; } } } }