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