1 /*
2  * This file is part of brisk-menu.
3  *
4  * Copyright © 2017-2020 Brisk Menu Developers
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  */
11 
12 #define _GNU_SOURCE
13 
14 #include "util.h"
15 
16 BRISK_BEGIN_PEDANTIC
17 #include "favourites-backend.h"
18 #include "favourites-section.h"
19 #include <glib/gi18n.h>
20 BRISK_END_PEDANTIC
21 
22 G_DEFINE_TYPE(BriskFavouritesBackend, brisk_favourites_backend, BRISK_TYPE_BACKEND)
23 
24 /* Helper for gsettings */
25 typedef gchar *gstrv;
DEF_AUTOFREE(gstrv,g_strfreev)26 DEF_AUTOFREE(gstrv, g_strfreev)
27 
28 static inline void _g_array_clean(GArray *array)
29 {
30         g_array_free(array, TRUE);
31 }
32 DEF_AUTOFREE(GArray, _g_array_clean)
33 
34 static gboolean brisk_favourites_backend_load(BriskBackend *backend);
35 static void brisk_favourites_backend_pin_item(GSimpleAction *action, GVariant *parameter,
36                                               BriskFavouritesBackend *self);
37 static void brisk_favourites_backend_unpin_item(GSimpleAction *action, GVariant *parameter,
38                                                 BriskFavouritesBackend *self);
39 
40 /**
41  * Tell the frontends what we are
42  */
brisk_favourites_backend_get_flags(__brisk_unused__ BriskBackend * backend)43 static unsigned int brisk_favourites_backend_get_flags(__brisk_unused__ BriskBackend *backend)
44 {
45         return BRISK_BACKEND_SOURCE;
46 }
47 
brisk_favourites_backend_get_id(__brisk_unused__ BriskBackend * backend)48 static const gchar *brisk_favourites_backend_get_id(__brisk_unused__ BriskBackend *backend)
49 {
50         return "favourites";
51 }
52 
brisk_favourites_backend_get_display_name(__brisk_unused__ BriskBackend * backend)53 static const gchar *brisk_favourites_backend_get_display_name(
54     __brisk_unused__ BriskBackend *backend)
55 {
56         return _("Favourites");
57 }
58 
brisk_favourites_backend_get_item_actions(BriskBackend * backend,BriskItem * item,GActionGroup * group)59 static GMenu *brisk_favourites_backend_get_item_actions(BriskBackend *backend, BriskItem *item,
60                                                         GActionGroup *group)
61 {
62         GMenu *ret = NULL;
63         BriskFavouritesBackend *self = BRISK_FAVOURITES_BACKEND(backend);
64 
65         g_action_map_add_action(G_ACTION_MAP(group), G_ACTION(self->action_add));
66         g_action_map_add_action(G_ACTION_MAP(group), G_ACTION(self->action_remove));
67 
68         self->active_item = item;
69 
70         ret = g_menu_new();
71 
72         if (brisk_favourites_backend_is_pinned(self, item)) {
73                 g_menu_append(ret,
74                               _("Unpin from favourites menu"),
75                               "brisk-context-items.favourites.unpin");
76         } else {
77                 g_menu_append(ret,
78                               _("Pin to favourites menu"),
79                               "brisk-context-items.favourites.pin");
80         }
81 
82         brisk_favourites_backend_menu_desktop(self, ret, group);
83 
84         return ret;
85 }
86 
87 /**
88  * brisk_favourites_backend_dispose:
89  *
90  * Clean up a BriskFavouritesBackend instance
91  */
brisk_favourites_backend_dispose(GObject * obj)92 static void brisk_favourites_backend_dispose(GObject *obj)
93 {
94         BriskFavouritesBackend *self = BRISK_FAVOURITES_BACKEND(obj);
95         g_clear_object(&self->action_add);
96         g_clear_object(&self->action_remove);
97         g_clear_object(&self->action_add_desktop);
98         g_clear_object(&self->action_remove_desktop);
99         g_clear_object(&self->settings);
100         g_clear_pointer(&self->favourites, g_hash_table_unref);
101         G_OBJECT_CLASS(brisk_favourites_backend_parent_class)->dispose(obj);
102 }
103 
104 /**
105  * brisk_favourites_backend_class_init:
106  *
107  * Handle class initialisation
108  */
brisk_favourites_backend_class_init(BriskFavouritesBackendClass * klazz)109 static void brisk_favourites_backend_class_init(BriskFavouritesBackendClass *klazz)
110 {
111         GObjectClass *obj_class = G_OBJECT_CLASS(klazz);
112         BriskBackendClass *b_class = BRISK_BACKEND_CLASS(klazz);
113 
114         /* Backend vtable hookup */
115         b_class->get_flags = brisk_favourites_backend_get_flags;
116         b_class->get_id = brisk_favourites_backend_get_id;
117         b_class->get_display_name = brisk_favourites_backend_get_display_name;
118         b_class->load = brisk_favourites_backend_load;
119         b_class->get_item_actions = brisk_favourites_backend_get_item_actions;
120 
121         /* gobject vtable hookup */
122         obj_class->dispose = brisk_favourites_backend_dispose;
123 }
124 
125 /**
126  * Handle changes to the favourites schema. We'll reset our table and restore the
127  * entries and retain the order within the list, which will come in useful in
128  * future when we want to enable reordering of entries.
129  */
brisk_favourites_backend_changed(GSettings * settings,const gchar * key,BriskFavouritesBackend * self)130 static void brisk_favourites_backend_changed(GSettings *settings, const gchar *key,
131                                              BriskFavouritesBackend *self)
132 {
133         autofree(gstrv) *favs = g_settings_get_strv(settings, key);
134         g_hash_table_remove_all(self->favourites);
135 
136         if (!favs) {
137                 return;
138         }
139 
140         for (guint i = 0; i < g_strv_length(favs); i++) {
141                 g_hash_table_insert(self->favourites, g_strdup(favs[i]), GUINT_TO_POINTER(i));
142         }
143 }
144 
145 /**
146  * brisk_favourites_backend_init:
147  *
148  * Handle construction of the BriskFavouritesBackend
149  */
brisk_favourites_backend_init(BriskFavouritesBackend * self)150 static void brisk_favourites_backend_init(BriskFavouritesBackend *self)
151 {
152         self->settings = g_settings_new("com.solus-project.brisk-menu");
153         g_signal_connect(self->settings,
154                          "changed::favourites",
155                          G_CALLBACK(brisk_favourites_backend_changed),
156                          self);
157 
158         self->action_add = g_simple_action_new("favourites.pin", NULL);
159         g_signal_connect(self->action_add,
160                          "activate",
161                          G_CALLBACK(brisk_favourites_backend_pin_item),
162                          self);
163         self->action_remove = g_simple_action_new("favourites.unpin", NULL);
164         g_signal_connect(self->action_remove,
165                          "activate",
166                          G_CALLBACK(brisk_favourites_backend_unpin_item),
167                          self);
168 
169         brisk_favourites_backend_init_desktop(self);
170 
171         /* Allow O(1) lookup for the "is pinned" logic */
172         self->favourites = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
173 
174         /* Force load of the backend pinned items */
175         brisk_favourites_backend_changed(self->settings, "favourites", self);
176 }
177 
178 /**
179  * brisk_favourites_backend_is_pinned:
180  *
181  * Determine whether the BriskItem is registered as pinned
182  */
brisk_favourites_backend_is_pinned(BriskFavouritesBackend * self,BriskItem * item)183 gboolean brisk_favourites_backend_is_pinned(BriskFavouritesBackend *self, BriskItem *item)
184 {
185         if (!item || !self) {
186                 return FALSE;
187         }
188 
189         const gchar *id = brisk_item_get_id(item);
190         return g_hash_table_contains(self->favourites, id);
191 }
192 
193 /**
194  * brisk_favourites_backend_load:
195  *
196  * On load we just emit a new stock section item
197  */
brisk_favourites_backend_load(BriskBackend * backend)198 static gboolean brisk_favourites_backend_load(BriskBackend *backend)
199 {
200         BriskFavouritesBackend *self = BRISK_FAVOURITES_BACKEND(backend);
201         brisk_backend_section_added(backend, brisk_favourites_section_new(self));
202         return TRUE;
203 }
204 
brisk_favourites_backend_pin_item(__brisk_unused__ GSimpleAction * action,__brisk_unused__ GVariant * parameter,BriskFavouritesBackend * self)205 static void brisk_favourites_backend_pin_item(__brisk_unused__ GSimpleAction *action,
206                                               __brisk_unused__ GVariant *parameter,
207                                               BriskFavouritesBackend *self)
208 {
209         autofree(gstrv) *old = NULL;
210         autofree(GArray) *array = NULL;
211 
212         if (!self->active_item) {
213                 return;
214         }
215 
216         const gchar *item_id = brisk_item_get_id(self->active_item);
217         self->active_item = NULL;
218 
219         /* prevent duping.. */
220         if (g_hash_table_contains(self->favourites, item_id)) {
221                 return;
222         }
223 
224         old = g_settings_get_strv(self->settings, "favourites");
225         array = g_array_new(TRUE, TRUE, sizeof(gchar *));
226 
227         for (guint i = 0; i < g_strv_length(old); i++) {
228                 if (!old[i] || g_str_equal(old[i], "")) {
229                         continue;
230                 }
231                 array = g_array_append_val(array, old[i]);
232         }
233 
234         array = g_array_append_val(array, item_id);
235         g_settings_set_strv(self->settings, "favourites", (const gchar **)array->data);
236 }
237 
brisk_favourites_backend_unpin_item(__brisk_unused__ GSimpleAction * action,__brisk_unused__ GVariant * parameter,BriskFavouritesBackend * self)238 static void brisk_favourites_backend_unpin_item(__brisk_unused__ GSimpleAction *action,
239                                                 __brisk_unused__ GVariant *parameter,
240                                                 BriskFavouritesBackend *self)
241 {
242         autofree(gstrv) *old = NULL;
243         autofree(GArray) *array = NULL;
244 
245         if (!self->active_item) {
246                 return;
247         }
248 
249         const gchar *item_id = brisk_item_get_id(self->active_item);
250         self->active_item = NULL;
251 
252         old = g_settings_get_strv(self->settings, "favourites");
253         array = g_array_new(TRUE, TRUE, sizeof(gchar *));
254 
255         for (guint i = 0; i < g_strv_length(old); i++) {
256                 if (!old[i] || g_str_equal(old[i], "")) {
257                         continue;
258                 }
259                 if (!g_str_equal(old[i], item_id)) {
260                         array = g_array_append_val(array, old[i]);
261                 }
262         }
263 
264         g_settings_set_strv(self->settings, "favourites", (const gchar **)array->data);
265 
266         brisk_backend_invalidate_filter(BRISK_BACKEND(self));
267 }
268 
269 /**
270  * brisk_favourites_backend_get_item_order:
271  *
272  * Return the pin priority for any given item, if its known as pinned.
273  * We may be storing `0` as a value for a pin priority, which when using glibc,
274  * is equivalent to NULL, so we add checks that there is really something stored.
275  */
brisk_favourites_backend_get_item_order(BriskFavouritesBackend * self,BriskItem * item)276 gint brisk_favourites_backend_get_item_order(BriskFavouritesBackend *self, BriskItem *item)
277 {
278         const gchar *item_id = brisk_item_get_id(item);
279         __brisk_unused__ void *key = NULL;
280         void *val = NULL;
281 
282         if (!g_hash_table_lookup_extended(self->favourites, item_id, &key, &val)) {
283                 return -1;
284         }
285 
286         return GPOINTER_TO_INT(val);
287 }
288 
289 /**
290  * brisk_favourites_backend_new:
291  *
292  * Return a newly created BriskFavouritesBackend
293  */
brisk_favourites_backend_new(void)294 BriskBackend *brisk_favourites_backend_new(void)
295 {
296         return g_object_new(BRISK_TYPE_FAVOURITES_BACKEND, NULL);
297 }
298 
299 /*
300  * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
301  *
302  * Local variables:
303  * c-basic-offset: 8
304  * tab-width: 8
305  * indent-tabs-mode: nil
306  * End:
307  *
308  * vi: set shiftwidth=8 tabstop=8 expandtab:
309  * :indentSize=8:tabSize=8:noTabs=true:
310  */
311