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