/* Copyright (c) 2011 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 . */ namespace GnomePie { // A very complex Widget. This is by far the most ugly file of this project // but well, this list *is* complex... sorry ;) class PieList : Gtk.TreeView { private Gtk.ListStore groups; private Gtk.ListStore pies; private Gtk.ListStore actions; private Gtk.TreeStore data; private const int small_icon = 24; private const int large_icon = 36; // data positions in the data ListStore private enum DataPos {IS_QUICKACTION, ICON, NAME, TYPE_ID, ACTION_TYPE, ICON_PIXBUF, FONT_WEIGHT, ICON_NAME_EDITABLE, QUICKACTION_VISIBLE, QUICKACTION_ACTIVATABLE, TYPE_VISIBLE, GROUP_VISIBLE, APP_VISIBLE, KEY_VISIBLE, PIE_VISIBLE, URI_VISIBLE, TRIGGER_VISIBLE, DISPLAY_COMMAND_GROUP, DISPLAY_COMMAND_APP, DISPLAY_COMMAND_KEY, DISPLAY_COMMAND_PIE, DISPLAY_COMMAND_URI, REAL_COMMAND_GROUP, REAL_COMMAND_PIE, REAL_COMMAND_KEY} // data positions in the actions ListStore private enum ActionPos {NAME, TYPE, CAN_QUICKACTION, ICON_NAME_EDITABLE} // data positions in the pies ListStore private enum PiePos {NAME, ID} // data positions in the groups ListStore private enum GroupPos {NAME, TYPE, ICON} public PieList() { GLib.Object(); Gtk.TreeIter last; // group choices this.groups = new Gtk.ListStore(3, typeof(string), // group name typeof(string), // group type typeof(string)); // group icon // add all registered group types foreach (var type in GroupRegistry.types) { this.groups.append(out last); this.groups.set(last, GroupPos.NAME, GroupRegistry.names[type], GroupPos.TYPE, type.name(), GroupPos.ICON, GroupRegistry.icons[type]); } // pie choices this.pies = new Gtk.ListStore(2, typeof(string), // pie name typeof(string)); // pie id // action type choices this.actions = new Gtk.ListStore(4, typeof(string), // type name typeof(string), // action type typeof(bool), // can be quickaction typeof(bool)); // icon/name editable // add all registered action types foreach (var type in ActionRegistry.types) { this.actions.append(out last); this.actions.set(last, ActionPos.NAME, ActionRegistry.names[type], ActionPos.TYPE, type.name(), ActionPos.CAN_QUICKACTION, true, ActionPos.ICON_NAME_EDITABLE, ActionRegistry.icon_name_editables[type]); } // and one type for groups this.actions.append(out last); this.actions.set(last, ActionPos.NAME, _("Slice group"), ActionPos.TYPE, typeof(ActionGroup).name(), ActionPos.CAN_QUICKACTION, false, ActionPos.ICON_NAME_EDITABLE, false); // main data model this.data = new Gtk.TreeStore(25, typeof(bool), // is quickaction typeof(string), // icon typeof(string), // name typeof(string), // slice: type label, pie: "ID: %id" typeof(string), // typeof(action), typeof(ActionGroup).name() if group action, pie_id if Pie typeof(Gdk.Pixbuf), // icon pixbuf typeof(int), // font weight typeof(bool), // icon/name editable typeof(bool), // quickaction visible typeof(bool), // quickaction activatable typeof(bool), // type visible typeof(bool), // group renderer visible typeof(bool), // app renderer visible typeof(bool), // key renderer visible typeof(bool), // pie renderer visible typeof(bool), // uri renderer visible typeof(bool), // trigger renderer visible typeof(string), // display command group typeof(string), // display command app typeof(string), // display command key typeof(string), // display command pie typeof(string), // display command uri typeof(string), // real command group typeof(string), // real command pie typeof(string)); // real command key this.set_model(this.data); this.set_grid_lines(Gtk.TreeViewGridLines.NONE); this.set_enable_tree_lines(false); this.set_reorderable(false); this.set_level_indentation(-10); // create the gui // icon column var icon_column = new Gtk.TreeViewColumn(); icon_column.title = _("Icon"); icon_column.expand = false; // quickaction checkbox var check_render = new Gtk.CellRendererToggle(); check_render.activatable = true; check_render.radio = true; check_render.width = 15; check_render.toggled.connect((path) => { Gtk.TreeIter toggled; this.data.get_iter_from_string(out toggled, path); bool current = false; this.data.get(toggled, DataPos.IS_QUICKACTION, out current); // set all others off Gtk.TreeIter parent; this.data.iter_parent(out parent, toggled); string parent_pos = this.data.get_string_from_iter(parent); int child_count = this.data.iter_n_children(parent); for (int i=0; i { Gtk.TreeIter iter; this.data.get_iter_from_string(out iter, path); int icon_size = this.data.iter_depth(iter) == 0 ? this.large_icon : this.small_icon; this.data.set(iter, DataPos.ICON, icon_name); this.data.set(iter, DataPos.ICON_PIXBUF, this.load_icon(icon_name, icon_size)); this.update_pie(iter); this.update_linked(); }); icon_column.pack_start(icon_render, false); icon_column.add_attribute(icon_render, "icon_name", DataPos.ICON); icon_column.add_attribute(icon_render, "pixbuf", DataPos.ICON_PIXBUF); icon_column.add_attribute(icon_render, "editable", DataPos.ICON_NAME_EDITABLE); icon_column.add_attribute(icon_render, "icon_sensitive", DataPos.ICON_NAME_EDITABLE); // command column var command_column = new Gtk.TreeViewColumn(); command_column.title = _("Command"); command_column.resizable = true; command_column.expand = true; // trigger var command_renderer_trigger = new CellRendererTrigger(); command_renderer_trigger.editable = true; command_renderer_trigger.ellipsize = Pango.EllipsizeMode.END; command_renderer_trigger.on_select.connect((path, trigger) => { Gtk.TreeIter data_iter; this.data.get_iter_from_string(out data_iter, path); this.data.set(data_iter, DataPos.DISPLAY_COMMAND_KEY, trigger.label_with_specials); this.data.set(data_iter, DataPos.REAL_COMMAND_KEY, trigger.name); this.update_pie(data_iter); }); command_column.pack_end(command_renderer_trigger, true); command_column.add_attribute(command_renderer_trigger, "weight", DataPos.FONT_WEIGHT); command_column.add_attribute(command_renderer_trigger, "markup", DataPos.DISPLAY_COMMAND_KEY); command_column.add_attribute(command_renderer_trigger, "visible", DataPos.TRIGGER_VISIBLE); command_column.add_attribute(command_renderer_trigger, "trigger", DataPos.REAL_COMMAND_KEY); // slice group var command_renderer_group = new Gtk.CellRendererCombo(); command_renderer_group.editable = true; command_renderer_group.has_entry = false; command_renderer_group.text_column = 0; command_renderer_group.ellipsize = Pango.EllipsizeMode.END; command_renderer_group.model = this.groups; command_renderer_group.changed.connect((path, iter) => { string display_name; string type; string icon; this.groups.get(iter, GroupPos.NAME, out display_name); this.groups.get(iter, GroupPos.TYPE, out type); this.groups.get(iter, GroupPos.ICON, out icon); Gtk.TreeIter data_iter; this.data.get_iter_from_string(out data_iter, path); this.data.set(data_iter, DataPos.DISPLAY_COMMAND_GROUP, display_name); this.data.set(data_iter, DataPos.REAL_COMMAND_GROUP, type); this.data.set(data_iter, DataPos.NAME, display_name); this.data.set(data_iter, DataPos.ICON, icon); this.update_pie(data_iter); }); command_column.pack_end(command_renderer_group, true); command_column.add_attribute(command_renderer_group, "weight", DataPos.FONT_WEIGHT); command_column.add_attribute(command_renderer_group, "text", DataPos.DISPLAY_COMMAND_GROUP); command_column.add_attribute(command_renderer_group, "visible", DataPos.GROUP_VISIBLE); // app action var command_renderer_app = new Gtk.CellRendererText(); command_renderer_app.editable = true; command_renderer_app.ellipsize = Pango.EllipsizeMode.END; command_renderer_app.edited.connect((path, command) => { Gtk.TreeIter data_iter; this.data.get_iter_from_string(out data_iter, path); this.data.set(data_iter, DataPos.DISPLAY_COMMAND_APP, command); this.update_pie(data_iter); }); command_column.pack_end(command_renderer_app, true); command_column.add_attribute(command_renderer_app, "weight", DataPos.FONT_WEIGHT); command_column.add_attribute(command_renderer_app, "text", DataPos.DISPLAY_COMMAND_APP); command_column.add_attribute(command_renderer_app, "visible", DataPos.APP_VISIBLE); // key action var command_renderer_key = new Gtk.CellRendererAccel(); command_renderer_key.editable = true; command_renderer_key.ellipsize = Pango.EllipsizeMode.END; command_renderer_key.accel_edited.connect((path, key, mods) => { Gtk.TreeIter data_iter; this.data.get_iter_from_string(out data_iter, path); string label = Gtk.accelerator_get_label(key, mods); string accelerator = Gtk.accelerator_name(key, mods); this.data.set(data_iter, DataPos.DISPLAY_COMMAND_KEY, label); this.data.set(data_iter, DataPos.REAL_COMMAND_KEY, accelerator); this.update_pie(data_iter); }); command_renderer_key.accel_cleared.connect((path) => { Gtk.TreeIter data_iter; this.data.get_iter_from_string(out data_iter, path); this.data.set(data_iter, DataPos.DISPLAY_COMMAND_KEY, _("Not bound")); this.data.set(data_iter, DataPos.REAL_COMMAND_KEY, ""); this.update_pie(data_iter); }); command_column.pack_end(command_renderer_key, true); command_column.add_attribute(command_renderer_key, "weight", DataPos.FONT_WEIGHT); command_column.add_attribute(command_renderer_key, "text", DataPos.DISPLAY_COMMAND_KEY); command_column.add_attribute(command_renderer_key, "visible", DataPos.KEY_VISIBLE); // pie action var command_renderer_pie = new Gtk.CellRendererCombo(); command_renderer_pie.editable = true; command_renderer_pie.has_entry = false; command_renderer_pie.text_column = 0; command_renderer_pie.ellipsize = Pango.EllipsizeMode.END; command_renderer_pie.model = this.pies; command_renderer_pie.changed.connect((path, iter) => { string name; string id; this.pies.get(iter, PiePos.NAME, out name); this.pies.get(iter, PiePos.ID, out id); Gtk.TreeIter data_iter; this.data.get_iter_from_string(out data_iter, path); this.data.set(data_iter, DataPos.DISPLAY_COMMAND_PIE, name); this.data.set(data_iter, DataPos.REAL_COMMAND_PIE, id); this.update_pie(data_iter); this.update_linked(); }); command_column.pack_end(command_renderer_pie, true); command_column.add_attribute(command_renderer_pie, "weight", DataPos.FONT_WEIGHT); command_column.add_attribute(command_renderer_pie, "text", DataPos.DISPLAY_COMMAND_PIE); command_column.add_attribute(command_renderer_pie, "visible", DataPos.PIE_VISIBLE); // uri action var command_renderer_uri = new Gtk.CellRendererText(); command_renderer_uri.editable = true; command_renderer_uri.ellipsize = Pango.EllipsizeMode.END; command_renderer_uri.edited.connect((path, uri) => { Gtk.TreeIter data_iter; this.data.get_iter_from_string(out data_iter, path); this.data.set(data_iter, DataPos.DISPLAY_COMMAND_URI, uri); this.update_pie(data_iter); }); command_column.pack_end(command_renderer_uri, true); command_column.add_attribute(command_renderer_uri, "weight", DataPos.FONT_WEIGHT); command_column.add_attribute(command_renderer_uri, "text", DataPos.DISPLAY_COMMAND_URI); command_column.add_attribute(command_renderer_uri, "visible", DataPos.URI_VISIBLE); // type column var type_column = new Gtk.TreeViewColumn(); type_column.title = _("Pie-ID / Action type"); type_column.resizable = true; type_column.expand = false; var type_render = new Gtk.CellRendererCombo(); type_render.editable = true; type_render.has_entry = false; type_render.model = actions; type_render.text_column = 0; type_render.ellipsize = Pango.EllipsizeMode.END; // change command_render's visibility accordingly type_render.changed.connect((path, iter) => { string text = ""; string type; bool can_quickaction; bool icon_name_editable; this.actions.get(iter, ActionPos.NAME, out text); this.actions.get(iter, ActionPos.TYPE, out type); this.actions.get(iter, ActionPos.CAN_QUICKACTION, out can_quickaction); this.actions.get(iter, ActionPos.ICON_NAME_EDITABLE, out icon_name_editable); Gtk.TreeIter data_iter; this.data.get_iter_from_string(out data_iter, path); this.data.set(data_iter, DataPos.TYPE_ID, text); this.data.set(data_iter, DataPos.ACTION_TYPE, type); this.data.set(data_iter, DataPos.QUICKACTION_ACTIVATABLE, can_quickaction); this.data.set(data_iter, DataPos.ICON_NAME_EDITABLE, icon_name_editable); // set all command renderes invisible this.data.set(data_iter, DataPos.GROUP_VISIBLE, false); this.data.set(data_iter, DataPos.APP_VISIBLE, false); this.data.set(data_iter, DataPos.KEY_VISIBLE, false); this.data.set(data_iter, DataPos.PIE_VISIBLE, false); this.data.set(data_iter, DataPos.URI_VISIBLE, false); // set one visible int type_id = 0; if(type == typeof(AppAction).name()) type_id = 1; else if(type == typeof(KeyAction).name()) type_id = 2; else if(type == typeof(PieAction).name()) type_id = 3; else if(type == typeof(UriAction).name()) type_id = 4; else type_id = 0; this.data.set(data_iter, DataPos.GROUP_VISIBLE + type_id, true); this.update_linked(); this.update_pie(data_iter); //this.set_cursor(new Gtk.TreePath.from_string(path), command_column, true); }); type_column.pack_start(type_render, true); type_column.add_attribute(type_render, "sensitive", DataPos.TYPE_VISIBLE); type_column.add_attribute(type_render, "editable", DataPos.TYPE_VISIBLE); type_column.add_attribute(type_render, "text", DataPos.TYPE_ID); // name column var name_column = new Gtk.TreeViewColumn(); name_column.title = _("Name"); name_column.expand = true; name_column.resizable = true; var name_render = new Gtk.CellRendererText(); name_render.editable = true; name_render.ellipsize = Pango.EllipsizeMode.END; name_render.edited.connect((path, text) => { Gtk.TreeIter iter; this.data.get_iter_from_string(out iter, path); this.data.set(iter, DataPos.NAME, text); // try to change icon to a fitting one string icon; this.data.get(iter, DataPos.ICON, out icon); if (icon == "application-default-icon" && Gtk.IconTheme.get_default().has_icon(text.down())) { this.data.set(iter, DataPos.ICON, text.down()); } this.update_pie(iter); this.update_linked(); //this.set_cursor(new Gtk.TreePath.from_string(path), type_column, true); }); name_column.pack_start(name_render, true); name_column.add_attribute(name_render, "weight", DataPos.FONT_WEIGHT); name_column.add_attribute(name_render, "text", DataPos.NAME); name_column.add_attribute(name_render, "sensitive", DataPos.ICON_NAME_EDITABLE); name_column.add_attribute(name_render, "editable", DataPos.ICON_NAME_EDITABLE); this.append_column(icon_column); this.append_column(name_column); this.append_column(type_column); this.append_column(command_column); this.realize.connect(this.load); // context menu var menu = new Gtk.Menu(); var item = new Gtk.ImageMenuItem.with_label(_("Add new Pie")); item.set_image(new Gtk.Image.from_stock(Gtk.Stock.ADD, Gtk.IconSize.MENU)); item.activate.connect(this.add_empty_pie); menu.append(item); item = new Gtk.ImageMenuItem.with_label(_("Add new Slice")); item.set_image(new Gtk.Image.from_stock(Gtk.Stock.ADD, Gtk.IconSize.MENU)); item.activate.connect(this.add_empty_slice); menu.append(item); var sepa = new Gtk.SeparatorMenuItem(); menu.append(sepa); item = new Gtk.ImageMenuItem.with_label(_("Delete")); item.set_image(new Gtk.Image.from_stock(Gtk.Stock.DELETE, Gtk.IconSize.MENU)); item.activate.connect(this.delete_selection); menu.append(item); menu.show_all(); this.button_press_event.connect((event) => { if (event.type == Gdk.EventType.BUTTON_PRESS && event.button == 3) { menu.popup(null, null, null, event.button, event.time); } return false; }); // setup drag'n'drop Gtk.TargetEntry uri_source = {"text/uri-list", 0, 0}; Gtk.TargetEntry[] entries = { uri_source }; this.drag_data_received.connect(this.on_dnd_received); this.drag_data_get.connect(this.on_dnd_source); this.enable_model_drag_dest(entries, Gdk.DragAction.COPY | Gdk.DragAction.MOVE | Gdk.DragAction.LINK); this.get_selection().changed.connect(() => { Gtk.TreeIter selected; if (this.get_selection().get_selected(null, out selected)) { if (this.data.iter_depth(selected) == 0) { this.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, entries, Gdk.DragAction.LINK); } else { this.unset_rows_drag_source(); } } }); this.drag_begin.connect(() => { this.unset_rows_drag_dest(); }); this.drag_end.connect(() => { this.enable_model_drag_dest(entries, Gdk.DragAction.COPY | Gdk.DragAction.MOVE | Gdk.DragAction.LINK); }); } // moves the selected slice up public void selection_up() { Gtk.TreeIter selected; if (this.get_selection().get_selected(null, out selected)) { Gtk.TreePath path = this.data.get_path(selected); Gtk.TreeIter? before = null;; if (path.prev() && this.data.get_iter(out before, path)) { this.data.swap(selected, before); this.get_selection().changed(); this.update_pie(selected); } } } // moves the selected slice down public void selection_down() { Gtk.TreeIter selected; if (this.get_selection().get_selected(null, out selected)) { Gtk.TreePath path = this.data.get_path(selected); Gtk.TreeIter? after = null; path.next(); if (this.data.get_iter(out after, path)) { this.data.swap(selected, after); this.get_selection().changed(); this.update_pie(selected); } } } // updates the entire list, checking for changed cross-references via PieActions // updates their names and icons if needed private void update_linked() { this.data.foreach((model, path, iter) => { string action_type; this.data.get(iter, DataPos.ACTION_TYPE, out action_type); if (action_type == typeof(PieAction).name()) { string command; this.data.get(iter, DataPos.REAL_COMMAND_PIE, out command); var referee = PieManager.all_pies[command]; if (referee != null) { this.data.set(iter, DataPos.ICON, referee.icon); this.data.set(iter, DataPos.NAME, referee.name); this.data.set(iter, DataPos.ICON_PIXBUF, this.load_icon(referee.icon, this.small_icon)); this.data.set(iter, DataPos.DISPLAY_COMMAND_PIE, referee.name); } else { // referenced Pie does not exist anymore or no is selected; // select the first one... Gtk.TreeIter first_pie; this.pies.get_iter_first(out first_pie); string name; string id; this.pies.get(first_pie, PiePos.NAME, out name); this.pies.get(first_pie, PiePos.ID, out id); this.data.set(iter, DataPos.DISPLAY_COMMAND_PIE, name); this.data.set(iter, DataPos.REAL_COMMAND_PIE, id); update_linked(); } } else if (action_type == typeof(ActionGroup).name()) { string command; this.data.get(iter, DataPos.REAL_COMMAND_GROUP, out command); if (command == "") { // no group is selected, select the first one... Gtk.TreeIter first_group; this.groups.get_iter_first(out first_group); string name; string type; string icon; this.groups.get(first_group, GroupPos.NAME, out name); this.groups.get(first_group, GroupPos.TYPE, out type); this.groups.get(first_group, GroupPos.ICON, out icon); this.data.set(iter, DataPos.DISPLAY_COMMAND_GROUP, name); this.data.set(iter, DataPos.NAME, name); this.data.set(iter, DataPos.REAL_COMMAND_GROUP, type); this.data.set(iter, DataPos.ICON, icon); } } return false; }); } // adds a new, empty pie to the list private void add_empty_pie() { var new_one = PieManager.create_persistent_pie(_("New Pie"), "application-default-icon", null); Gtk.TreeIter last; this.pies.append(out last); this.pies.set(last, 0, new_one.name, 1, new_one.id); Gtk.TreeIter parent; this.data.append(out parent, null); this.data.set(parent, DataPos.IS_QUICKACTION, false, DataPos.ICON, new_one.icon, DataPos.NAME, new_one.name, DataPos.TYPE_ID, "ID: " + new_one.id, DataPos.ACTION_TYPE, new_one.id, DataPos.ICON_PIXBUF, this.load_icon(new_one.icon, this.large_icon), DataPos.FONT_WEIGHT, 800, DataPos.ICON_NAME_EDITABLE, true, DataPos.QUICKACTION_VISIBLE, false, DataPos.QUICKACTION_ACTIVATABLE, false, DataPos.TYPE_VISIBLE, false, DataPos.GROUP_VISIBLE, false, DataPos.APP_VISIBLE, false, DataPos.KEY_VISIBLE, false, DataPos.PIE_VISIBLE, false, DataPos.URI_VISIBLE, false, DataPos.TRIGGER_VISIBLE, true, DataPos.DISPLAY_COMMAND_GROUP, "", DataPos.DISPLAY_COMMAND_APP, "", DataPos.DISPLAY_COMMAND_KEY, PieManager.get_accelerator_label_of(new_one.id), DataPos.DISPLAY_COMMAND_PIE, "", DataPos.DISPLAY_COMMAND_URI, "", DataPos.REAL_COMMAND_GROUP, "", DataPos.REAL_COMMAND_PIE, "", DataPos.REAL_COMMAND_KEY, PieManager.get_accelerator_of(new_one.id)); this.get_selection().select_iter(parent); this.scroll_to_cell(this.data.get_path(parent), null, true, 0.5f, 0.0f); } // adds a new empty slice to the list private void add_empty_slice() { Gtk.TreeIter selected; if (this.get_selection().get_selected(null, out selected)) { var path = this.data.get_path(selected); if (path != null) { if (path.get_depth() == 2) this.data.iter_parent(out selected, selected); this.load_action(selected, new AppAction(_("New Action"), "application-default-icon", "")); Gtk.TreeIter new_one; this.data.iter_nth_child(out new_one, selected, this.data.iter_n_children(selected)-1); this.expand_to_path(this.data.get_path(new_one)); this.get_selection().select_iter(new_one); this.scroll_to_cell(this.data.get_path(new_one), null, true, 0.5f, 0.0f); this.update_pie(selected); } } else { var dialog = new Gtk.MessageDialog((Gtk.Window)this.get_toplevel(), Gtk.DialogFlags.MODAL, Gtk.MessageType.INFO, Gtk.ButtonsType.CLOSE, _("You have to select a Pie to add a Slice to!")); dialog.run(); dialog.destroy(); } } // writes the contents of action to the position pointed by slice private void write_action(Action action, Gtk.TreeIter slice) { this.data.set(slice, DataPos.IS_QUICKACTION, action.is_quick_action, DataPos.ICON, action.icon, DataPos.NAME, action.name, DataPos.TYPE_ID, ActionRegistry.names[action.get_type()], DataPos.ACTION_TYPE, action.get_type().name(), DataPos.ICON_PIXBUF, this.load_icon(action.icon, this.small_icon), DataPos.FONT_WEIGHT, 400, DataPos.ICON_NAME_EDITABLE, !(action is PieAction), DataPos.QUICKACTION_VISIBLE, true, DataPos.QUICKACTION_ACTIVATABLE, true, DataPos.TYPE_VISIBLE, true, DataPos.GROUP_VISIBLE, false, DataPos.TRIGGER_VISIBLE, false, DataPos.APP_VISIBLE, action is AppAction, DataPos.KEY_VISIBLE, action is KeyAction, DataPos.PIE_VISIBLE, action is PieAction, DataPos.URI_VISIBLE, action is UriAction, DataPos.DISPLAY_COMMAND_GROUP, "", DataPos.DISPLAY_COMMAND_APP, (action is AppAction) ? action.display_command : "", DataPos.DISPLAY_COMMAND_KEY, (action is KeyAction) ? action.display_command : _("Not bound"), DataPos.DISPLAY_COMMAND_PIE, (action is PieAction) ? action.display_command : "", DataPos.DISPLAY_COMMAND_URI, (action is UriAction) ? action.display_command : "", DataPos.REAL_COMMAND_GROUP, "", DataPos.REAL_COMMAND_PIE, (action is PieAction) ? action.real_command : "", DataPos.REAL_COMMAND_KEY, (action is KeyAction) ? action.real_command : ""); } // deletes the currently selected pie or slice private void delete_selection() { Gtk.TreeIter selected; if (this.get_selection().get_selected(null, out selected)) { var path = this.data.get_path(selected); if (path != null) { if (path.get_depth() == 1) this.delete_pie(selected); else this.delete_slice(selected); } } else { var dialog = new Gtk.MessageDialog((Gtk.Window)this.get_toplevel(), Gtk.DialogFlags.MODAL, Gtk.MessageType.INFO, Gtk.ButtonsType.CLOSE, _("You have to select a Pie or a Slice to delete!")); dialog.run(); dialog.destroy(); } } // deletes the given pie private void delete_pie(Gtk.TreeIter pie) { var dialog = new Gtk.MessageDialog((Gtk.Window)this.get_toplevel(), Gtk.DialogFlags.MODAL, Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO, _("Do you really want to delete the selected Pie with all contained Slices?")); dialog.response.connect((response) => { if (response == Gtk.ResponseType.YES) { string id; this.data.get(pie, DataPos.ACTION_TYPE, out id); this.data.remove(pie); PieManager.remove_pie(id); this.pies.foreach((model, path, iter) => { string pies_id; this.pies.get(iter, PiePos.ID, out pies_id); if (id == pies_id) { this.pies.remove(iter); return true; } return false; }); this.update_linked(); } }); dialog.run(); dialog.destroy(); } // deletes the given slice private void delete_slice(Gtk.TreeIter slice) { var dialog = new Gtk.MessageDialog((Gtk.Window)this.get_toplevel(), Gtk.DialogFlags.MODAL, Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO, _("Do you really want to delete the selected Slice?")); dialog.response.connect((response) => { if (response == Gtk.ResponseType.YES) { Gtk.TreeIter parent; this.data.iter_parent(out parent, slice); this.data.remove(slice); this.update_pie(parent); } }); dialog.run(); dialog.destroy(); } // loads all pies to the list private void load() { foreach (var pie in PieManager.all_pies.entries) { this.load_pie(pie.value); } } // loads one given pie to the list private void load_pie(Pie pie) { if (pie.id.length == 3) { Gtk.TreeIter last; this.pies.append(out last); this.pies.set(last, PiePos.NAME, pie.name, PiePos.ID, pie.id); Gtk.TreeIter parent; this.data.append(out parent, null); this.data.set(parent, DataPos.IS_QUICKACTION, false, DataPos.ICON, pie.icon, DataPos.NAME, pie.name, DataPos.TYPE_ID, "ID: " + pie.id, DataPos.ACTION_TYPE, pie.id, DataPos.ICON_PIXBUF, this.load_icon(pie.icon, this.large_icon), DataPos.FONT_WEIGHT, 800, DataPos.ICON_NAME_EDITABLE, true, DataPos.QUICKACTION_VISIBLE, false, DataPos.QUICKACTION_ACTIVATABLE, false, DataPos.TYPE_VISIBLE, false, DataPos.GROUP_VISIBLE, false, DataPos.APP_VISIBLE, false, DataPos.KEY_VISIBLE, false, DataPos.PIE_VISIBLE, false, DataPos.URI_VISIBLE, false, DataPos.TRIGGER_VISIBLE, true, DataPos.DISPLAY_COMMAND_GROUP, "", DataPos.DISPLAY_COMMAND_APP, "", DataPos.DISPLAY_COMMAND_KEY, PieManager.get_accelerator_label_of(pie.id), DataPos.DISPLAY_COMMAND_PIE, "", DataPos.DISPLAY_COMMAND_URI, "", DataPos.REAL_COMMAND_GROUP, "", DataPos.REAL_COMMAND_PIE, "", DataPos.REAL_COMMAND_KEY, PieManager.get_accelerator_of(pie.id)); foreach (var group in pie.action_groups) { this.load_group(parent, group); } } } // loads a given group private void load_group(Gtk.TreeIter parent, ActionGroup group) { if (group.get_type() == typeof(ActionGroup)) { foreach (var action in group.actions) { this.load_action(parent, action); } } else { Gtk.TreeIter child; this.data.append(out child, parent); this.data.set(child, DataPos.IS_QUICKACTION, false, DataPos.ICON, GroupRegistry.icons[group.get_type()], DataPos.NAME, GroupRegistry.names[group.get_type()], DataPos.TYPE_ID, _("Slice group"), DataPos.ACTION_TYPE, typeof(ActionGroup).name(), DataPos.ICON_PIXBUF, this.load_icon(GroupRegistry.icons[group.get_type()], this.small_icon), DataPos.FONT_WEIGHT, 400, DataPos.ICON_NAME_EDITABLE, false, DataPos.QUICKACTION_VISIBLE, true, DataPos.QUICKACTION_ACTIVATABLE, false, DataPos.TYPE_VISIBLE, true, DataPos.GROUP_VISIBLE, true, DataPos.APP_VISIBLE, false, DataPos.KEY_VISIBLE, false, DataPos.PIE_VISIBLE, false, DataPos.URI_VISIBLE, false, DataPos.TRIGGER_VISIBLE, false, DataPos.DISPLAY_COMMAND_GROUP, GroupRegistry.names[group.get_type()], DataPos.DISPLAY_COMMAND_APP, "", DataPos.DISPLAY_COMMAND_KEY, _("Not bound"), DataPos.DISPLAY_COMMAND_PIE, "", DataPos.DISPLAY_COMMAND_URI, "", DataPos.REAL_COMMAND_GROUP, group.get_type().name(), DataPos.REAL_COMMAND_PIE, "", DataPos.REAL_COMMAND_KEY, ""); } } // loads a given slice private void load_action(Gtk.TreeIter parent, Action action) { Gtk.TreeIter child; this.data.append(out child, parent); this.write_action(action, child); } // applies all changes done to the given pie private void update_pie(Gtk.TreeIter slice_or_pie) { // get pie iter var path = this.data.get_path(slice_or_pie); if (path != null) { var pie = slice_or_pie; if (path.get_depth() == 2) this.data.iter_parent(out pie, slice_or_pie); // get information on pie string id; string icon; string name; string hotkey; this.data.get(pie, DataPos.ICON, out icon); this.data.get(pie, DataPos.NAME, out name); this.data.get(pie, DataPos.ACTION_TYPE, out id); this.data.get(pie, DataPos.REAL_COMMAND_KEY, out hotkey); // remove pie PieManager.remove_pie(id); this.pies.foreach((model, path, iter) => { string pies_id; this.pies.get(iter, PiePos.ID, out pies_id); if (id == pies_id) { this.pies.set(iter, PiePos.NAME, name); return true; } return false; }); // create new pie var new_pie = PieManager.create_persistent_pie(name, icon, new Trigger.from_string(hotkey), id); // add actions accordingly if (this.data.iter_has_child(pie)) { Gtk.TreeIter child; this.data.iter_children(out child, pie); do { // get slice information string slice_type; string slice_icon; string slice_name; bool is_quick_action; this.data.get(child, DataPos.ICON, out slice_icon); this.data.get(child, DataPos.NAME, out slice_name); this.data.get(child, DataPos.ACTION_TYPE, out slice_type); this.data.get(child, DataPos.IS_QUICKACTION, out is_quick_action); if (slice_type == typeof(AppAction).name()) { string slice_command; this.data.get(child, DataPos.DISPLAY_COMMAND_APP, out slice_command); var group = new ActionGroup(new_pie.id); group.add_action(new AppAction(slice_name, slice_icon, slice_command, is_quick_action)); new_pie.add_group(group); } else if (slice_type == typeof(KeyAction).name()) { string slice_command; this.data.get(child, DataPos.REAL_COMMAND_KEY, out slice_command); var group = new ActionGroup(new_pie.id); group.add_action(new KeyAction(slice_name, slice_icon, slice_command, is_quick_action)); new_pie.add_group(group); } else if (slice_type == typeof(PieAction).name()) { string slice_command; this.data.get(child, DataPos.REAL_COMMAND_PIE, out slice_command); var group = new ActionGroup(new_pie.id); group.add_action(new PieAction(slice_command, is_quick_action)); new_pie.add_group(group); } else if (slice_type == typeof(UriAction).name()) { string slice_command; this.data.get(child, DataPos.DISPLAY_COMMAND_URI, out slice_command); var group = new ActionGroup(new_pie.id); group.add_action(new UriAction(slice_name, slice_icon, slice_command, is_quick_action)); new_pie.add_group(group); } else if (slice_type == typeof(ActionGroup).name()) { string slice_command; this.data.get(child, DataPos.REAL_COMMAND_GROUP, out slice_command); var group = GLib.Object.new(GLib.Type.from_name(slice_command), "parent_id", new_pie.id); new_pie.add_group(group as ActionGroup); } } while (this.data.iter_next(ref child)); } } } // creates new action when the list receives a drag'n'drop event private void on_dnd_received(Gdk.DragContext context, int x, int y, Gtk.SelectionData selection_data, uint info, uint time_) { string[] uris = selection_data.get_uris(); Gtk.TreePath path; Gtk.TreeViewDropPosition pos; // check for valid position if (!this.get_dest_row_at_pos(x, y, out path, out pos) || (path.to_string() == "0" && pos == Gtk.TreeViewDropPosition.BEFORE)) { warning("Failed to insert Slice: Invalid location!"); return; } // get position to insert (when child: after, when parent: as first child) Gtk.TreeIter parent; int insert_pos = 0; if (path.get_depth() == 1) { if (pos == Gtk.TreeViewDropPosition.BEFORE) { path.prev(); this.data.get_iter(out parent, path); insert_pos = this.data.iter_n_children(parent); } else { this.data.get_iter(out parent, path); } } else { if (pos == Gtk.TreeViewDropPosition.BEFORE) { insert_pos = path.get_indices()[1]; } else { insert_pos = path.get_indices()[1]+1; } path.up(); this.data.get_iter(out parent, path); } foreach (var uri in uris) { Gtk.TreeIter new_child; this.data.insert(out new_child, parent, insert_pos); this.write_action(ActionRegistry.new_for_uri(uri), new_child); } this.update_pie(parent); } private void on_dnd_source(Gdk.DragContext context, Gtk.SelectionData selection_data, uint info, uint time_) { Gtk.TreeIter selected; if (this.get_selection().get_selected(null, out selected)) { string id = ""; this.data.get(selected, DataPos.ACTION_TYPE, out id); selection_data.set_uris({"file://" + Paths.launchers + "/" + id + ".desktop"}); } } private Gdk.Pixbuf load_icon(string name, int size) { Gdk.Pixbuf pixbuf = null; try { if (name.contains("/")) pixbuf = new Gdk.Pixbuf.from_file_at_size(name, size, size); else pixbuf = new Gdk.Pixbuf.from_file_at_size(Icon.get_icon_file(name, size), size, size); } catch (GLib.Error e) { warning(e.message); } return pixbuf; } } }