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 "styles.h"
15 #include "util.h"
16 
17 BRISK_BEGIN_PEDANTIC
18 #include "../menu-private.h"
19 #include "category-button.h"
20 #include "classic-entry-button.h"
21 #include "classic-window.h"
22 #include "desktop-button.h"
23 #include "sidebar-scroller.h"
24 
25 #include <gio/gdesktopappinfo.h>
26 #include <glib/gi18n.h>
27 BRISK_END_PEDANTIC
28 
29 G_DEFINE_TYPE(BriskClassicWindow, brisk_classic_window, BRISK_TYPE_MENU_WINDOW)
30 
31 static void brisk_classic_window_associate_category(BriskMenuWindow *self, GtkWidget *button);
32 static void brisk_classic_window_on_toggled(BriskMenuWindow *self, GtkWidget *button);
33 static gboolean brisk_classic_window_on_enter(BriskMenuWindow *self, GdkEvent *event,
34                                               GtkWidget *button);
35 static void brisk_classic_window_load_css(BriskClassicWindow *self);
36 static void brisk_classic_window_key_activate(BriskClassicWindow *self, gpointer v);
37 static void brisk_classic_window_activated(BriskMenuWindow *self, GtkListBoxRow *row, gpointer v);
38 static void brisk_classic_window_setup_session_controls(BriskClassicWindow *self);
39 static void brisk_classic_window_build_sidebar(BriskMenuWindow *self);
40 static void brisk_classic_window_add_shortcut(BriskMenuWindow *self, const gchar *id);
41 static void brisk_classic_window_set_filters_enabled(BriskClassicWindow *self, gboolean enabled);
42 static gboolean brisk_classic_window_filter_apps(GtkListBoxRow *row, gpointer v);
43 static gint brisk_classic_window_sort(GtkListBoxRow *row1, GtkListBoxRow *row2, gpointer v);
44 
45 /**
46  * brisk_classic_window_dispose:
47  *
48  * Clean up a BriskClassicWindow instance
49  */
brisk_classic_window_dispose(GObject * obj)50 static void brisk_classic_window_dispose(GObject *obj)
51 {
52         BriskClassicWindow *self = BRISK_CLASSIC_WINDOW(obj);
53         GdkScreen *screen = NULL;
54 
55         if (self->css) {
56                 screen = gtk_widget_get_screen(GTK_WIDGET(self));
57                 gtk_style_context_remove_provider_for_screen(screen, GTK_STYLE_PROVIDER(self->css));
58                 g_clear_object(&self->css);
59         }
60 
61         G_OBJECT_CLASS(brisk_classic_window_parent_class)->dispose(obj);
62 }
63 
brisk_classic_window_get_id(__brisk_unused__ BriskMenuWindow * window)64 static const gchar *brisk_classic_window_get_id(__brisk_unused__ BriskMenuWindow *window)
65 {
66         return "classic";
67 }
68 
brisk_classic_window_get_display_name(__brisk_unused__ BriskMenuWindow * window)69 static const gchar *brisk_classic_window_get_display_name(__brisk_unused__ BriskMenuWindow *window)
70 {
71         return _("Classic");
72 }
73 
brisk_classic_window_update_screen_position(BriskMenuWindow * self)74 static void brisk_classic_window_update_screen_position(BriskMenuWindow *self)
75 {
76         GdkScreen *screen = NULL;
77         GtkAllocation relative_alloc = { 0 };
78         GdkWindow *window = NULL;
79         GdkRectangle geom = { 0 };
80         gint relative_x, relative_y = 0;      /* Real X, Y of the applet, on screen */
81         gint window_width, window_height = 0; /* Window width & height */
82         gint mon = 0;                         /* Monitor to display on */
83         gint window_x, window_y = 0;          /* Target X, Y */
84 
85         if (!self->relative_to) {
86                 g_warning("Cannot set relative location without relative widget!");
87                 return;
88         }
89 
90         /* Forcibly realize the applet window */
91         if (!gtk_widget_get_realized(self->relative_to)) {
92                 gtk_widget_realize(self->relative_to);
93         }
94 
95         /* Forcibly realize ourselves */
96         if (!gtk_widget_get_realized(GTK_WIDGET(self))) {
97                 gtk_widget_realize(GTK_WIDGET(self));
98         }
99 
100         gtk_widget_get_allocation(self->relative_to, &relative_alloc);
101 
102         /* Find out where we are on screen */
103         window = gtk_widget_get_window(self->relative_to);
104         gdk_window_get_origin(window, &relative_x, &relative_y);
105 
106         /* Find out the window size */
107         gtk_window_get_size(GTK_WINDOW(self), &window_width, &window_height);
108 
109         /* Grab the geometry for the monitor we're currently on */
110         screen = gtk_widget_get_screen(self->relative_to);
111         mon = gdk_screen_get_monitor_at_point(screen, relative_x, relative_y);
112         gdk_screen_get_monitor_geometry(screen, mon, &geom);
113 
114         switch (self->position) {
115         case GTK_POS_LEFT:
116                 /* Left vertical panel, appear to the RHS of it */
117                 window_x = relative_x + relative_alloc.width;
118                 window_y = relative_y;
119                 break;
120         case GTK_POS_RIGHT:
121                 /* Right vertical panel, appear to the LHS of it */
122                 window_x = relative_x - window_width;
123                 window_y = relative_y;
124                 break;
125         case GTK_POS_TOP:
126                 /* Top panel, appear below it */
127                 window_x = relative_x;
128                 window_y = relative_y + relative_alloc.height;
129                 break;
130         case GTK_POS_BOTTOM:
131         default:
132                 /* Bottom panel, appear above it */
133                 window_x = relative_x;
134                 window_y = relative_y - window_height;
135                 break;
136         }
137 
138         /* Bound the right side */
139         if (window_x + window_width > (geom.x + geom.width)) {
140                 window_x = (geom.x + geom.width) - window_width;
141                 if (self->position == GTK_POS_RIGHT) {
142                         window_x -= relative_alloc.width;
143                 }
144         }
145 
146         /* Bound the left side */
147         if (window_x < geom.x) {
148                 window_x = geom.x;
149                 if (self->position == GTK_POS_LEFT) {
150                         window_x -= relative_alloc.width;
151                 }
152         }
153 
154         gtk_window_move(GTK_WINDOW(self), window_x, window_y);
155 }
156 
157 /**
158  * Update the position of the search bar in accordance with settings
159  */
brisk_classic_window_update_search(BriskMenuWindow * self)160 static void brisk_classic_window_update_search(BriskMenuWindow *self)
161 {
162         SearchPosition search_position = self->search_position;
163         GtkWidget *layout = NULL;
164         gint n_pos = 0;
165 
166         layout = gtk_bin_get_child(GTK_BIN(self));
167 
168         if (search_position < SEARCH_POS_MIN || search_position >= SEARCH_POS_MAX) {
169                 search_position = SEARCH_POS_AUTOMATIC;
170         }
171 
172         switch (search_position) {
173         case SEARCH_POS_AUTOMATIC:
174                 /* Top panel, bottom search. Bottom panel, top search */
175                 n_pos = self->position == GTK_POS_TOP ? 1 : 0;
176                 break;
177         case SEARCH_POS_TOP:
178                 n_pos = 0;
179                 break;
180         case SEARCH_POS_BOTTOM:
181         default:
182                 n_pos = 1;
183                 break;
184         }
185 
186         gtk_container_child_set(GTK_CONTAINER(layout), self->search, "position", n_pos, NULL);
187 }
188 
189 /**
190  * Backend has new items for us, add to the global store
191  */
brisk_classic_window_add_item(BriskMenuWindow * self,BriskItem * item,__brisk_unused__ BriskBackend * backend)192 static void brisk_classic_window_add_item(BriskMenuWindow *self, BriskItem *item,
193                                           __brisk_unused__ BriskBackend *backend)
194 {
195         GtkWidget *button = NULL;
196         const gchar *item_id = brisk_item_get_id(item);
197 
198         button = brisk_classic_entry_button_new(self->launcher, item);
199         g_signal_connect_swapped(button,
200                                  "show-context-menu",
201                                  G_CALLBACK(brisk_menu_window_show_context),
202                                  self);
203         gtk_container_add(GTK_CONTAINER(BRISK_CLASSIC_WINDOW(self)->apps), button);
204         gtk_widget_show_all(button);
205 
206         g_hash_table_insert(self->item_store, g_strdup(item_id), button);
207 }
208 
209 /**
210  * Backend has a new sidebar section for us
211  */
brisk_classic_window_add_section(BriskMenuWindow * self,BriskSection * section,__brisk_unused__ BriskBackend * backend)212 static void brisk_classic_window_add_section(BriskMenuWindow *self, BriskSection *section,
213                                              __brisk_unused__ BriskBackend *backend)
214 {
215         GtkWidget *button = NULL;
216         const gchar *section_id = brisk_section_get_id(section);
217         GtkWidget *box_target = NULL;
218 
219         /* Skip dupes. Sections are uniquely namespaced */
220         if (g_hash_table_lookup(self->item_store, section_id) != NULL) {
221                 return;
222         }
223 
224         box_target = brisk_menu_window_get_section_box(self, backend);
225 
226         button = brisk_classic_category_button_new(section);
227         gtk_radio_button_join_group(GTK_RADIO_BUTTON(button),
228                                     GTK_RADIO_BUTTON(self->section_box_leader));
229         gtk_box_pack_start(GTK_BOX(box_target), button, FALSE, FALSE, 0);
230         brisk_classic_window_associate_category(self, button);
231         gtk_widget_show_all(button);
232 
233         /* Avoid new dupes */
234         g_hash_table_insert(self->item_store, g_strdup(section_id), button);
235 
236         brisk_menu_window_select_sections(self);
237 }
238 
239 /**
240  * A backend needs us to invalidate the filters
241  */
brisk_classic_window_invalidate_filter(BriskMenuWindow * self,__brisk_unused__ BriskBackend * backend)242 static void brisk_classic_window_invalidate_filter(BriskMenuWindow *self,
243                                                    __brisk_unused__ BriskBackend *backend)
244 {
245         gtk_list_box_invalidate_filter(GTK_LIST_BOX(BRISK_CLASSIC_WINDOW(self)->apps));
246         gtk_list_box_invalidate_sort(GTK_LIST_BOX(BRISK_CLASSIC_WINDOW(self)->apps));
247 
248         gtk_list_box_unselect_all(GTK_LIST_BOX(BRISK_CLASSIC_WINDOW(self)->apps));
249 }
250 
251 /**
252  * A backend needs us to purge any data we have for it
253  */
brisk_classic_window_reset(BriskMenuWindow * self,BriskBackend * backend)254 static void brisk_classic_window_reset(BriskMenuWindow *self, BriskBackend *backend)
255 {
256         GtkWidget *box_target = NULL;
257         GList *kids = NULL, *elem = NULL;
258         const gchar *backend_id = NULL;
259 
260         backend_id = brisk_backend_get_id(backend);
261 
262         box_target = brisk_menu_window_get_section_box(self, backend);
263         gtk_container_foreach(GTK_CONTAINER(box_target),
264                               (GtkCallback)brisk_menu_window_remove_category,
265                               self);
266 
267         /* Manual work for the items */
268         kids = gtk_container_get_children(GTK_CONTAINER(BRISK_CLASSIC_WINDOW(self)->apps));
269         for (elem = kids; elem; elem = elem->next) {
270                 GtkWidget *row = elem->data;
271                 GtkWidget *child = NULL;
272                 BriskItem *item = NULL;
273                 const gchar *local_backend_id = NULL;
274                 const gchar *local_id = NULL;
275 
276                 if (!GTK_IS_BIN(GTK_BIN(row))) {
277                         continue;
278                 }
279 
280                 child = gtk_bin_get_child(GTK_BIN(row));
281                 if (!BRISK_IS_MENU_ENTRY_BUTTON(child)) {
282                         continue;
283                 }
284 
285                 g_object_get(child, "item", &item, NULL);
286                 if (!item) {
287                         g_warning("missing item for entry in backend '%s'", backend_id);
288                         continue;
289                 }
290 
291                 local_backend_id = brisk_item_get_backend_id(item);
292                 if (!g_str_equal(backend_id, local_backend_id)) {
293                         continue;
294                 }
295                 local_id = brisk_item_get_id(item);
296                 g_hash_table_remove(self->item_store, local_id);
297                 gtk_widget_destroy(row);
298         }
299         g_list_free(kids);
300 }
301 
302 /**
303  * Override hiding so that we can invalidate all filters
304  */
brisk_classic_window_hide(GtkWidget * widget)305 static void brisk_classic_window_hide(GtkWidget *widget)
306 {
307         BriskClassicWindow *self = NULL;
308         GtkAdjustment *adjustment = NULL;
309 
310         /* Have parent deal with it first */
311         GTK_WIDGET_CLASS(brisk_classic_window_parent_class)->hide(widget);
312 
313         self = BRISK_CLASSIC_WINDOW(widget);
314 
315         /* Remove search filter */
316         gtk_entry_set_text(GTK_ENTRY(BRISK_MENU_WINDOW(self)->search), "");
317 
318         brisk_menu_window_select_sections(BRISK_MENU_WINDOW(self));
319 
320         /* Reset scrollbars */
321         adjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(self->apps_scroll));
322         gtk_adjustment_set_value(adjustment, 0);
323         adjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(self->sidebar_scroll));
324         gtk_adjustment_set_value(adjustment, 0);
325 
326         /* Unselect any current "apps" */
327         gtk_list_box_select_row(GTK_LIST_BOX(self->apps), NULL);
328 }
329 
330 /**
331  * brisk_classic_window_class_init:
332  *
333  * Handle class initialisation
334  */
brisk_classic_window_class_init(BriskClassicWindowClass * klazz)335 static void brisk_classic_window_class_init(BriskClassicWindowClass *klazz)
336 {
337         GObjectClass *obj_class = G_OBJECT_CLASS(klazz);
338         BriskMenuWindowClass *b_class = BRISK_MENU_WINDOW_CLASS(klazz);
339         GtkWidgetClass *wid_class = GTK_WIDGET_CLASS(klazz);
340 
341         /* gobject vtable hookup */
342         obj_class->dispose = brisk_classic_window_dispose;
343 
344         /* Window vtable hookup */
345         b_class->get_id = brisk_classic_window_get_id;
346         b_class->get_display_name = brisk_classic_window_get_display_name;
347         b_class->update_screen_position = brisk_classic_window_update_screen_position;
348         b_class->update_search = brisk_classic_window_update_search;
349         b_class->add_item = brisk_classic_window_add_item;
350         b_class->add_section = brisk_classic_window_add_section;
351         b_class->invalidate_filter = brisk_classic_window_invalidate_filter;
352         b_class->reset = brisk_classic_window_reset;
353 
354         /* widget vtable */
355         wid_class->hide = brisk_classic_window_hide;
356 }
357 
358 /**
359  * brisk_classic_window_init:
360  *
361  * Handle construction of the BriskClassicWindow
362  */
brisk_classic_window_init(BriskClassicWindow * self)363 static void brisk_classic_window_init(BriskClassicWindow *self)
364 {
365         GtkWidget *layout = NULL;
366         GtkWidget *widget = NULL;
367         GtkWidget *content = NULL;
368         GtkWidget *scroll = NULL;
369         GtkStyleContext *style = NULL;
370         BriskMenuWindow *base;
371         autofree(gchar) *txt_holder = NULL;
372         autofree(gchar) *place_holder = NULL;
373 
374         brisk_classic_window_load_css(self);
375 
376         base = BRISK_MENU_WINDOW(self);
377 
378         style = gtk_widget_get_style_context(GTK_WIDGET(self));
379         gtk_style_context_add_class(style, BRISK_STYLE_MAIN);
380 
381         brisk_menu_window_configure_grabs(base);
382 
383         layout = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2);
384         gtk_container_add(GTK_CONTAINER(self), layout);
385 
386         /* Create search entry - but not GtkSearchEntry to avoid rounding in themes */
387         widget = gtk_entry_new();
388         gtk_entry_set_icon_from_icon_name(GTK_ENTRY(widget),
389                                           GTK_ENTRY_ICON_PRIMARY,
390                                           "edit-find-symbolic");
391         gtk_entry_set_icon_from_icon_name(GTK_ENTRY(widget),
392                                           GTK_ENTRY_ICON_SECONDARY,
393                                           "edit-clear-symbolic");
394 
395         gtk_box_pack_start(GTK_BOX(layout), widget, FALSE, FALSE, 0);
396         /* Translators: This is the text shown in the "search box" with no content */
397         txt_holder = g_strdup_printf("%s\u2026", _("Type to search"));
398         gtk_entry_set_placeholder_text(GTK_ENTRY(widget), txt_holder);
399         base->search = widget;
400         g_signal_connect_swapped(widget, "changed", G_CALLBACK(brisk_menu_window_search), self);
401         g_signal_connect_swapped(widget,
402                                  "activate",
403                                  G_CALLBACK(brisk_classic_window_key_activate),
404                                  self);
405         g_signal_connect(widget, "icon-press", G_CALLBACK(brisk_menu_window_clear_search), self);
406 
407         /* Content layout */
408         content = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 2);
409         gtk_box_pack_start(GTK_BOX(layout), content, TRUE, TRUE, 0);
410 
411         /* Sidebar for categories */
412         widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
413         scroll = brisk_menu_sidebar_scroller_new();
414         base->section_box_holder = widget;
415         style = gtk_widget_get_style_context(base->section_box_holder);
416         gtk_style_context_add_class(style, BRISK_STYLE_SIDEBAR);
417         self->sidebar_scroll = scroll;
418         gtk_container_add(GTK_CONTAINER(scroll), widget);
419 
420         /* Create a wrapper for the categories */
421         widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
422         gtk_box_pack_start(GTK_BOX(widget), scroll, TRUE, TRUE, 0);
423         gtk_box_pack_start(GTK_BOX(content), widget, TRUE, TRUE, 0);
424         self->sidebar_wrap = widget;
425 
426         /* Scrollbar for apps */
427         scroll = gtk_scrolled_window_new(NULL, NULL);
428         gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll), GTK_SHADOW_IN);
429         gtk_box_pack_start(GTK_BOX(content), scroll, TRUE, TRUE, 0);
430         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
431                                        GTK_POLICY_NEVER,
432                                        GTK_POLICY_AUTOMATIC);
433         gtk_scrolled_window_set_overlay_scrolling(GTK_SCROLLED_WINDOW(scroll), FALSE);
434         self->apps_scroll = scroll;
435 
436         /* Application launcher display */
437         widget = gtk_list_box_new();
438         gtk_container_add(GTK_CONTAINER(scroll), widget);
439         self->apps = widget;
440         gtk_list_box_set_activate_on_single_click(GTK_LIST_BOX(self->apps), TRUE);
441         gtk_list_box_set_selection_mode(GTK_LIST_BOX(self->apps), GTK_SELECTION_SINGLE);
442         g_signal_connect_swapped(self->apps,
443                                  "row-activated",
444                                  G_CALLBACK(brisk_classic_window_activated),
445                                  self);
446 
447         /* Style up the app box */
448         style = gtk_widget_get_style_context(widget);
449         gtk_style_context_add_class(style, BRISK_STYLE_APPS_LIST);
450         gtk_style_context_add_class(style, "view");
451         gtk_style_context_add_class(style, "content-view");
452         gtk_style_context_remove_class(style, "background");
453 
454         /* Translators: This message is shown when the search results are empty */
455         place_holder = g_strdup_printf("<big>%s</big>", _("Sorry, no items found"));
456         widget = gtk_label_new(place_holder);
457         /* Add a placeholder when there are no apps for current search term */
458         gtk_label_set_use_markup(GTK_LABEL(widget), TRUE);
459         g_object_set(widget,
460                      "halign",
461                      GTK_ALIGN_CENTER,
462                      "valign",
463                      GTK_ALIGN_START,
464                      "margin",
465                      6,
466                      NULL);
467         style = gtk_widget_get_style_context(widget);
468         gtk_style_context_add_class(style, "dim-label");
469         gtk_list_box_set_placeholder(GTK_LIST_BOX(self->apps), widget);
470         gtk_widget_show_all(widget);
471 
472         brisk_classic_window_setup_session_controls(self);
473 
474         gtk_window_set_default_size(GTK_WINDOW(self), 300, 510);
475 
476         /* Hook up keyboard events */
477         g_signal_connect(self,
478                          "key-release-event",
479                          G_CALLBACK(brisk_menu_window_key_release),
480                          NULL);
481         g_signal_connect(self, "key-press-event", G_CALLBACK(brisk_menu_window_key_press), NULL);
482 
483         brisk_classic_window_build_sidebar(base);
484 
485         brisk_menu_window_init_backends(base);
486 
487         /* Hook up dbus later on */
488         g_idle_add((GSourceFunc)brisk_menu_window_setup_session, self);
489 
490         gtk_widget_show_all(layout);
491 }
492 
493 /*
494  * brisk_classic_window_new:
495  *
496  * Return a newly created BriskClassicWindow
497  */
brisk_classic_window_new(GtkWidget * relative_to)498 BriskMenuWindow *brisk_classic_window_new(GtkWidget *relative_to)
499 {
500         return g_object_new(BRISK_TYPE_CLASSIC_WINDOW,
501                             "type",
502                             GTK_WINDOW_POPUP,
503                             "relative-to",
504                             relative_to,
505                             NULL);
506 }
507 
508 /**
509  * brisk_classic_window_associate_category:
510  *
511  * This will hook up the category button for events to enable us to filter the
512  * list based on the active category.
513  */
brisk_classic_window_associate_category(BriskMenuWindow * self,GtkWidget * button)514 static void brisk_classic_window_associate_category(BriskMenuWindow *self, GtkWidget *button)
515 {
516         g_signal_connect_swapped(button,
517                                  "toggled",
518                                  G_CALLBACK(brisk_classic_window_on_toggled),
519                                  self);
520         g_signal_connect_swapped(button,
521                                  "enter-notify-event",
522                                  G_CALLBACK(brisk_classic_window_on_enter),
523                                  self);
524         g_signal_connect_swapped(button,
525                                  "focus-in-event",
526                                  G_CALLBACK(brisk_classic_window_on_enter),
527                                  self);
528 }
529 
530 /**
531  * Fired by clicking a category button
532  */
brisk_classic_window_on_toggled(BriskMenuWindow * self,GtkWidget * button)533 static void brisk_classic_window_on_toggled(BriskMenuWindow *self, GtkWidget *button)
534 {
535         BriskClassicCategoryButton *cat = NULL;
536 
537         /* Skip a double signal due to using a group */
538         if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button))) {
539                 return;
540         }
541 
542         cat = BRISK_CLASSIC_CATEGORY_BUTTON(button);
543         g_object_get(cat, "section", &self->active_section, NULL);
544 
545         /* Start the filter. */
546         brisk_classic_window_invalidate_filter(self, NULL);
547 }
548 
549 /**
550  * Fired by entering into the category button
551  */
brisk_classic_window_on_enter(BriskMenuWindow * self,__brisk_unused__ GdkEvent * event,GtkWidget * button)552 static gboolean brisk_classic_window_on_enter(BriskMenuWindow *self,
553                                               __brisk_unused__ GdkEvent *event, GtkWidget *button)
554 {
555         GtkToggleButton *but = GTK_TOGGLE_BUTTON(button);
556 
557         /* Whether we're in rollover mode */
558         if (!self->rollover) {
559                 return GDK_EVENT_PROPAGATE;
560         }
561 
562         if (gtk_toggle_button_get_active(but) || !gtk_widget_get_visible(button)) {
563                 return GDK_EVENT_PROPAGATE;
564         }
565 
566         /* Force activation */
567         gtk_widget_grab_focus(button);
568         gtk_toggle_button_set_active(but, TRUE);
569 
570         return GDK_EVENT_PROPAGATE;
571 }
572 
573 /**
574  * Load up the CSS assets
575  */
brisk_classic_window_load_css(BriskClassicWindow * self)576 static void brisk_classic_window_load_css(BriskClassicWindow *self)
577 {
578         GtkCssProvider *css = NULL;
579         autofree(GFile) *file = NULL;
580         autofree(GError) *err = NULL;
581         GdkScreen *screen = NULL;
582 
583         file = g_file_new_for_uri("resource://com/solus-project/brisk/menu/classic/styling.css");
584         if (!file) {
585                 return;
586         }
587 
588         css = gtk_css_provider_new();
589         self->css = css;
590         screen = gtk_widget_get_screen(GTK_WIDGET(self));
591         gtk_style_context_add_provider_for_screen(screen,
592                                                   GTK_STYLE_PROVIDER(css),
593                                                   GTK_STYLE_PROVIDER_PRIORITY_FALLBACK);
594 
595         if (!gtk_css_provider_load_from_file(css, file, &err)) {
596                 g_warning("Failed to load CSS: %s\n", err->message);
597                 return;
598         }
599 }
600 
brisk_classic_window_key_activate(BriskClassicWindow * self,__brisk_unused__ gpointer v)601 static void brisk_classic_window_key_activate(BriskClassicWindow *self, __brisk_unused__ gpointer v)
602 {
603         autofree(GList) *kids = NULL;
604         GList *elem = NULL;
605         BriskMenuEntryButton *button = NULL;
606 
607         kids = gtk_container_get_children(GTK_CONTAINER(self->apps));
608 
609         for (elem = kids; elem; elem = elem->next) {
610                 GtkWidget *widget = elem->data;
611 
612                 if (!gtk_widget_get_visible(widget) || !gtk_widget_get_child_visible(widget)) {
613                         continue;
614                 }
615 
616                 button = BRISK_MENU_ENTRY_BUTTON(gtk_bin_get_child(GTK_BIN(widget)));
617                 break;
618         }
619         if (!button) {
620                 return;
621         }
622         brisk_menu_entry_button_launch(button);
623 }
624 
brisk_classic_window_activated(__brisk_unused__ BriskMenuWindow * self,GtkListBoxRow * row,__brisk_unused__ gpointer v)625 static void brisk_classic_window_activated(__brisk_unused__ BriskMenuWindow *self,
626                                            GtkListBoxRow *row, __brisk_unused__ gpointer v)
627 {
628         BriskMenuEntryButton *button = NULL;
629         GtkWidget *child = NULL;
630 
631         child = gtk_bin_get_child(GTK_BIN(row));
632         if (!child) {
633                 return;
634         }
635         if (!BRISK_IS_MENU_ENTRY_BUTTON(child)) {
636                 return;
637         }
638         button = BRISK_MENU_ENTRY_BUTTON(child);
639         brisk_menu_entry_button_launch(button);
640 }
641 
642 /**
643  * Create the graphical buttons for session control
644  */
brisk_classic_window_setup_session_controls(BriskClassicWindow * self)645 static void brisk_classic_window_setup_session_controls(BriskClassicWindow *self)
646 {
647         GtkWidget *widget = NULL;
648         GtkWidget *box = NULL;
649         GtkStyleContext *style = NULL;
650 
651         box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
652         gtk_widget_set_margin_bottom(box, 4);
653 
654         gtk_box_pack_end(GTK_BOX(self->sidebar_wrap), box, FALSE, FALSE, 0);
655         gtk_widget_set_halign(box, GTK_ALIGN_CENTER);
656 
657         /* Add a separator for visual consistency */
658         widget = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
659         gtk_box_pack_end(GTK_BOX(self->sidebar_wrap), widget, FALSE, FALSE, 3);
660 
661         /* Logout */
662         widget = gtk_button_new_from_icon_name("brisk_system-log-out-symbolic", GTK_ICON_SIZE_MENU);
663         self->button_logout = widget;
664         g_signal_connect_swapped(widget, "clicked", G_CALLBACK(brisk_menu_window_logout), self);
665         gtk_widget_set_tooltip_text(widget, _("End the current session"));
666         gtk_widget_set_can_focus(widget, TRUE);
667         gtk_container_add(GTK_CONTAINER(box), widget);
668         style = gtk_widget_get_style_context(widget);
669         gtk_style_context_add_class(style, GTK_STYLE_CLASS_FLAT);
670         gtk_style_context_add_class(style, "session-button");
671 
672         /* Lock */
673         widget = gtk_button_new_from_icon_name("system-lock-screen-symbolic",
674                                                GTK_ICON_SIZE_SMALL_TOOLBAR);
675         self->button_lock = widget;
676         g_signal_connect_swapped(widget, "clicked", G_CALLBACK(brisk_menu_window_lock), self);
677         gtk_widget_set_tooltip_text(widget, _("Lock the screen"));
678         gtk_widget_set_can_focus(widget, TRUE);
679         gtk_container_add(GTK_CONTAINER(box), widget);
680         style = gtk_widget_get_style_context(widget);
681         gtk_style_context_add_class(style, GTK_STYLE_CLASS_FLAT);
682         gtk_style_context_add_class(style, "session-button");
683 
684         /* Shutdown */
685         widget =
686             gtk_button_new_from_icon_name("system-shutdown-symbolic", GTK_ICON_SIZE_SMALL_TOOLBAR);
687         self->button_shutdown = widget;
688         g_signal_connect_swapped(widget, "clicked", G_CALLBACK(brisk_menu_window_shutdown), self);
689         gtk_widget_set_tooltip_text(widget, _("Turn off the device"));
690         gtk_widget_set_can_focus(widget, TRUE);
691         gtk_container_add(GTK_CONTAINER(box), widget);
692         style = gtk_widget_get_style_context(widget);
693         gtk_style_context_add_class(style, GTK_STYLE_CLASS_FLAT);
694         gtk_style_context_add_class(style, "session-button");
695 }
696 
697 /**
698  * Begin a build of the menu structure
699  */
brisk_classic_window_build_sidebar(BriskMenuWindow * self)700 static void brisk_classic_window_build_sidebar(BriskMenuWindow *self)
701 {
702         GtkWidget *sep = NULL;
703         autofree(gstrv) *shortcuts = NULL;
704 
705         brisk_classic_window_set_filters_enabled(BRISK_CLASSIC_WINDOW(self), FALSE);
706 
707         /* Special leader to control group association, hidden from view */
708         self->section_box_leader = gtk_radio_button_new(NULL);
709         gtk_box_pack_start(GTK_BOX(self->section_box_holder),
710                            self->section_box_leader,
711                            FALSE,
712                            FALSE,
713                            0);
714         gtk_widget_set_no_show_all(self->section_box_leader, TRUE);
715         gtk_widget_hide(self->section_box_leader);
716 
717         /* Separate the things */
718         sep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
719         gtk_box_pack_start(GTK_BOX(self->section_box_holder), sep, FALSE, FALSE, 5);
720         gtk_widget_show_all(sep);
721 
722         /* Load the shortcuts up */
723         shortcuts = g_settings_get_strv(self->settings, "pinned-shortcuts");
724         if (!shortcuts) {
725                 return;
726         }
727 
728         /* Add from gsettings */
729         for (guint i = 0; i < g_strv_length(shortcuts); i++) {
730                 brisk_classic_window_add_shortcut(self, shortcuts[i]);
731         }
732 
733         brisk_classic_window_set_filters_enabled(BRISK_CLASSIC_WINDOW(self), TRUE);
734 }
735 
736 /**
737  * brisk_classic_window_add_shortcut
738  *
739  * If we can create a .desktop launcher for the given name, add a new button to
740  * the sidebar as a quick launch facility.
741  */
brisk_classic_window_add_shortcut(BriskMenuWindow * self,const gchar * id)742 static void brisk_classic_window_add_shortcut(BriskMenuWindow *self, const gchar *id)
743 {
744         GDesktopAppInfo *info = NULL;
745         GtkWidget *button = NULL;
746 
747         info = g_desktop_app_info_new(id);
748         if (!info) {
749                 g_message("Not adding missing %s to BriskMenu", id);
750                 return;
751         }
752 
753         button = brisk_menu_desktop_button_new(self->launcher, G_APP_INFO(info));
754         gtk_widget_show_all(button);
755         gtk_box_pack_start(GTK_BOX(self->section_box_holder), button, FALSE, FALSE, 1);
756 }
757 
758 /**
759  * Enable or disable the filters between building of the menus
760  */
brisk_classic_window_set_filters_enabled(BriskClassicWindow * self,gboolean enabled)761 static void brisk_classic_window_set_filters_enabled(BriskClassicWindow *self, gboolean enabled)
762 {
763         BRISK_MENU_WINDOW(self)->filtering = enabled;
764         if (enabled) {
765                 gtk_list_box_set_filter_func(GTK_LIST_BOX(self->apps),
766                                              brisk_classic_window_filter_apps,
767                                              self,
768                                              NULL);
769                 gtk_list_box_set_sort_func(GTK_LIST_BOX(self->apps),
770                                            brisk_classic_window_sort,
771                                            self,
772                                            NULL);
773                 return;
774         }
775         gtk_list_box_set_filter_func(GTK_LIST_BOX(self->apps), NULL, NULL, NULL);
776         gtk_list_box_set_sort_func(GTK_LIST_BOX(self->apps), NULL, NULL, NULL);
777 }
778 
779 /**
780  * brisk_classic_window_filter_apps:
781  *
782  * Responsible for filtering the selection based on active group or search
783  * term.
784  */
brisk_classic_window_filter_apps(GtkListBoxRow * row,gpointer v)785 __brisk_pure__ static gboolean brisk_classic_window_filter_apps(GtkListBoxRow *row, gpointer v)
786 {
787         BriskMenuWindow *self = NULL;
788         GtkWidget *child = NULL;
789 
790         self = BRISK_MENU_WINDOW(v);
791 
792         if (!self->filtering) {
793                 return FALSE;
794         }
795 
796         /* Grab our Entry widget */
797         child = gtk_bin_get_child(GTK_BIN(row));
798 
799         return brisk_menu_window_filter_apps(self, child);
800 }
801 
brisk_classic_window_sort(GtkListBoxRow * row1,GtkListBoxRow * row2,gpointer v)802 static gint brisk_classic_window_sort(GtkListBoxRow *row1, GtkListBoxRow *row2, gpointer v)
803 {
804         GtkWidget *child1, *child2 = NULL;
805         BriskItem *itemA, *itemB = NULL;
806         autofree(gchar) *nameA = NULL;
807         autofree(gchar) *nameB = NULL;
808         BriskMenuWindow *self = NULL;
809 
810         self = BRISK_MENU_WINDOW(v);
811 
812         child1 = gtk_bin_get_child(GTK_BIN(row1));
813         child2 = gtk_bin_get_child(GTK_BIN(row2));
814 
815         g_object_get(child1, "item", &itemA, NULL);
816         g_object_get(child2, "item", &itemB, NULL);
817 
818         return brisk_menu_window_sort(self, itemA, itemB);
819 }
820 
821 /*
822  * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
823  *
824  * Local variables:
825  * c-basic-offset: 8
826  * tab-width: 8
827  * indent-tabs-mode: nil
828  * End:
829  *
830  * vi: set shiftwidth=8 tabstop=8 expandtab:
831  * :indentSize=8:tabSize=8:noTabs=true:
832  */
833