1/* 2Copyright (c) 2011 by Simon Schneegans 3 4This program is free software: you can redistribute it and/or modify it 5under the terms of the GNU General Public License as published by the Free 6Software Foundation, either version 3 of the License, or (at your option) 7any later version. 8 9This program is distributed in the hope that it will be useful, but WITHOUT 10ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 12more details. 13 14You should have received a copy of the GNU General Public License along with 15this program. If not, see <http://www.gnu.org/licenses/>. 16*/ 17 18namespace GnomePie { 19 20///////////////////////////////////////////////////////////////////////// 21/// A static class which stores all Pies. It can be used to add, delete 22/// and open Pies. 23///////////////////////////////////////////////////////////////////////// 24 25public class PieManager : GLib.Object { 26 27 ///////////////////////////////////////////////////////////////////// 28 /// A map of all Pies. It contains both, dynamic and persistent Pies. 29 /// They are associated to their ID's. 30 ///////////////////////////////////////////////////////////////////// 31 32 public static Gee.HashMap<string, Pie?> all_pies { get; private set; } 33 34 ///////////////////////////////////////////////////////////////////// 35 /// Stores all PieWindows which are currently opened. Should be 36 /// rarely more than two... 37 ///////////////////////////////////////////////////////////////////// 38 39 public static Gee.HashSet<PieWindow?> opened_windows { get; private set; } 40 41 ///////////////////////////////////////////////////////////////////// 42 /// Stores all global hotkeys. 43 ///////////////////////////////////////////////////////////////////// 44 45 private static BindingManager bindings; 46 47 ///////////////////////////////////////////////////////////////////// 48 /// True, if any pie has the current focus. If it is closing this 49 /// will be false already. 50 ///////////////////////////////////////////////////////////////////// 51 52 private static bool a_pie_is_active = false; 53 54 ///////////////////////////////////////////////////////////////////// 55 /// Storing the position of the last Pie. Used for subpies, which are 56 /// opened at their parents location. 57 ///////////////////////////////////////////////////////////////////// 58 59 private static int last_x = 0; 60 private static int last_y = 0; 61 62 ///////////////////////////////////////////////////////////////////// 63 /// Initializes all Pies. They are loaded from the pies.conf file. 64 ///////////////////////////////////////////////////////////////////// 65 66 public static void init() { 67 all_pies = new Gee.HashMap<string, Pie?>(); 68 opened_windows = new Gee.HashSet<PieWindow?>(); 69 bindings = new BindingManager(); 70 71 // load all Pies from th pies.conf file 72 Pies.load(); 73 74 // open the according pie if it's hotkey is pressed 75 bindings.on_press.connect((id) => { 76 open_pie(id); 77 }); 78 } 79 80 ///////////////////////////////////////////////////////////////////// 81 /// Opens the Pie with the given ID, if it exists. 82 ///////////////////////////////////////////////////////////////////// 83 84 public static void open_pie(string id) { 85 if (!a_pie_is_active) { 86 Pie? pie = all_pies[id]; 87 88 if (pie != null) { 89 90 a_pie_is_active = true; 91 92 var window = new PieWindow(); 93 window.load_pie(pie); 94 95 window.open(); 96 97 opened_windows.add(window); 98 99 window.on_closed.connect(() => { 100 opened_windows.remove(window); 101 if (opened_windows.size == 0) { 102 Icon.clear_cache(); 103 } 104 }); 105 106 window.on_closing.connect(() => { 107 window.get_center_pos(out last_x, out last_y); 108 a_pie_is_active = false; 109 }); 110 111 112 } else { 113 warning("Failed to open pie with ID \"" + id + "\": ID does not exist!"); 114 } 115 } 116 } 117 118 ///////////////////////////////////////////////////////////////////// 119 /// Returns the hotkey which the Pie with the given ID is bound to. 120 ///////////////////////////////////////////////////////////////////// 121 122 public static string get_accelerator_of(string id) { 123 return bindings.get_accelerator_of(id); 124 } 125 126 ///////////////////////////////////////////////////////////////////// 127 /// Returns a human-readable version of the hotkey which the Pie 128 /// with the given ID is bound to. 129 ///////////////////////////////////////////////////////////////////// 130 131 public static string get_accelerator_label_of(string id) { 132 return bindings.get_accelerator_label_of(id); 133 } 134 135 ///////////////////////////////////////////////////////////////////// 136 /// Bind the Pie with the given ID to the given trigger. 137 ///////////////////////////////////////////////////////////////////// 138 139 public static void bind_trigger(Trigger trigger, string id) { 140 bindings.unbind(id); 141 bindings.bind(trigger, id); 142 } 143 144 ///////////////////////////////////////////////////////////////////// 145 /// Returns true if the pie with the given id is in turbo mode. 146 ///////////////////////////////////////////////////////////////////// 147 148 public static bool get_is_turbo(string id) { 149 return bindings.get_is_turbo(id); 150 } 151 152 ///////////////////////////////////////////////////////////////////// 153 /// Returns true if the pie with the given id opens in the middle of 154 /// the screen. 155 ///////////////////////////////////////////////////////////////////// 156 157 public static bool get_is_centered(string id) { 158 return bindings.get_is_centered(id); 159 } 160 161 ///////////////////////////////////////////////////////////////////// 162 /// Returns the name of the Pie with the given ID. 163 ///////////////////////////////////////////////////////////////////// 164 165 public static string get_name_of(string id) { 166 Pie? pie = all_pies[id]; 167 if (pie == null) return ""; 168 else return pie.name; 169 } 170 171 ///////////////////////////////////////////////////////////////////// 172 /// Returns the name ID of the Pie bound to the given Trigger. 173 /// Returns "" if there is nothing bound to this trigger. 174 ///////////////////////////////////////////////////////////////////// 175 176 public static string get_assigned_id(Trigger trigger) { 177 return bindings.get_assigned_id(trigger); 178 } 179 180 ///////////////////////////////////////////////////////////////////// 181 /// Creates a new Pie which is displayed in the configuration dialog 182 /// and gets saved. 183 ///////////////////////////////////////////////////////////////////// 184 185 public static Pie create_persistent_pie(string name, string icon_name, Trigger? hotkey, string? desired_id = null) { 186 Pie pie = create_pie(name, icon_name, 100, 999, desired_id); 187 188 if (hotkey != null) bindings.bind(hotkey, pie.id); 189 190 create_launcher(pie.id); 191 192 return pie; 193 } 194 195 ///////////////////////////////////////////////////////////////////// 196 /// Creates a new Pie which is not displayed in the configuration 197 /// dialog and is not saved. 198 ///////////////////////////////////////////////////////////////////// 199 200 public static Pie create_dynamic_pie(string name, string icon_name, string? desired_id = null) { 201 return create_pie(name, icon_name, 1000, 9999, desired_id); 202 } 203 204 ///////////////////////////////////////////////////////////////////// 205 /// Adds a new Pie. Can't be accesd from outer scope. Use 206 /// create_persistent_pie or create_dynamic_pie instead. 207 ///////////////////////////////////////////////////////////////////// 208 209 private static Pie create_pie(string name, string icon_name, int min_id, int max_id, string? desired_id = null) { 210 var random = new GLib.Rand(); 211 212 string final_id; 213 214 if (desired_id == null) 215 final_id = random.int_range(min_id, max_id).to_string(); 216 else { 217 final_id = desired_id; 218 final_id.canon("0123456789", '_'); 219 final_id = final_id.replace("_", ""); 220 221 int id = int.parse(final_id); 222 223 if (id < min_id || id > max_id) { 224 final_id = random.int_range(min_id, max_id).to_string(); 225 warning("The ID for pie \"" + name + "\" should be in range %u - %u! Using \"" + final_id + "\" instead of \"" + desired_id + "\"...", min_id, max_id); 226 } 227 } 228 229 if (all_pies.has_key(final_id)) { 230 var tmp = final_id; 231 var id_number = int.parse(final_id) + 1; 232 if (id_number == max_id+1) id_number = min_id; 233 final_id = id_number.to_string(); 234 warning("Trying to add pie \"" + name + "\": ID \"" + tmp + "\" already exists! Using \"" + final_id + "\" instead..."); 235 return create_pie(name, icon_name, min_id, max_id, final_id); 236 } 237 238 Pie pie = new Pie(final_id, name, icon_name); 239 all_pies.set(final_id, pie); 240 241 return pie; 242 } 243 244 ///////////////////////////////////////////////////////////////////// 245 /// Removes the Pie with the given ID if it exists. Additionally it 246 /// unbinds it's global hotkey. 247 ///////////////////////////////////////////////////////////////////// 248 249 public static void remove_pie(string id) { 250 if (all_pies.has_key(id)) { 251 all_pies[id].on_remove(); 252 all_pies.unset(id); 253 bindings.unbind(id); 254 255 if (id.length == 3) 256 remove_launcher(id); 257 } 258 else { 259 warning("Failed to remove pie with ID \"" + id + "\": ID does not exist!"); 260 } 261 } 262 263 ///////////////////////////////////////////////////////////////////// 264 /// Creates a desktop file for which opens the Pie with given ID. 265 ///////////////////////////////////////////////////////////////////// 266 267 public static void create_launcher(string id) { 268 if (all_pies.has_key(id)) { 269 Pie? pie = all_pies[id]; 270 271 string launcher_entry = 272 "#!/usr/bin/env xdg-open\n" + 273 "[Desktop Entry]\n" + 274 "Name=%s\n".printf(pie.name) + 275 "Exec=%s -o %s\n".printf(Paths.executable, pie.id) + 276 "Encoding=UTF-8\n" + 277 "Type=Application\n" + 278 "Icon=%s\n".printf(pie.icon); 279 280 // create the launcher file 281 string launcher = Paths.launchers + "/%s.desktop".printf(pie.id); 282 283 try { 284 FileUtils.set_contents(launcher, launcher_entry); 285 FileUtils.chmod(launcher, 0755); 286 } catch (Error e) { 287 warning(e.message); 288 } 289 } 290 } 291 292 ///////////////////////////////////////////////////////////////////// 293 /// Deletes the desktop file for the Pie with the given ID. 294 ///////////////////////////////////////////////////////////////////// 295 296 private static void remove_launcher(string id) { 297 string launcher = Paths.launchers + "/%s.desktop".printf(id); 298 if (FileUtils.test(launcher, FileTest.EXISTS)) { 299 FileUtils.remove(launcher); 300 } 301 } 302} 303 304} 305