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 "category-button.h"
19 #include "dash-entry-button.h"
20 #include "dash-window.h"
21 #include <glib/gi18n.h>
22 BRISK_END_PEDANTIC
23 
24 G_DEFINE_TYPE(BriskDashWindow, brisk_dash_window, BRISK_TYPE_MENU_WINDOW)
25 
26 static void brisk_dash_window_associate_category(BriskMenuWindow *self, GtkWidget *button);
27 static void brisk_dash_window_on_toggled(BriskMenuWindow *self, GtkWidget *button);
28 static gboolean brisk_dash_window_on_enter(BriskMenuWindow *self, GdkEvent *event,
29                                            GtkWidget *button);
30 static void brisk_dash_window_load_css(GtkSettings *settings, const gchar *key,
31                                        BriskDashWindow *self);
32 static void brisk_dash_window_key_activate(BriskDashWindow *self, gpointer v);
33 static void brisk_dash_window_activated(BriskMenuWindow *self, GtkFlowBoxChild *row, gpointer v);
34 static void brisk_dash_window_set_filters_enabled(BriskDashWindow *self, gboolean enabled);
35 static gboolean brisk_dash_window_filter_apps(GtkFlowBoxChild *row, gpointer v);
36 static gint brisk_dash_window_sort(GtkFlowBoxChild *row1, GtkFlowBoxChild *row2, gpointer v);
37 
38 /**
39  * brisk_dash_window_dispose:
40  *
41  * Clean up a BriskDashWindow instance
42  */
brisk_dash_window_dispose(GObject * obj)43 static void brisk_dash_window_dispose(GObject *obj)
44 {
45         BriskDashWindow *self = BRISK_DASH_WINDOW(obj);
46         GdkScreen *screen = NULL;
47 
48         if (self->css) {
49                 screen = gtk_widget_get_screen(GTK_WIDGET(self));
50                 gtk_style_context_remove_provider_for_screen(screen, GTK_STYLE_PROVIDER(self->css));
51                 g_clear_object(&self->css);
52         }
53 
54         G_OBJECT_CLASS(brisk_dash_window_parent_class)->dispose(obj);
55 }
56 
brisk_dash_window_get_id(__brisk_unused__ BriskMenuWindow * window)57 static const gchar *brisk_dash_window_get_id(__brisk_unused__ BriskMenuWindow *window)
58 {
59         return "dash";
60 }
61 
brisk_dash_window_get_display_name(__brisk_unused__ BriskMenuWindow * window)62 static const gchar *brisk_dash_window_get_display_name(__brisk_unused__ BriskMenuWindow *window)
63 {
64         return _("Dash");
65 }
66 
brisk_dash_window_update_screen_position(BriskMenuWindow * self)67 static void brisk_dash_window_update_screen_position(BriskMenuWindow *self)
68 {
69         GdkScreen *screen = NULL;
70         GtkAllocation relative_alloc = { 0 };
71         GdkWindow *window = NULL;
72         GdkRectangle geom = { 0 };
73         gint relative_x, relative_y = 0;      /* Real X, Y of the applet, on screen */
74         gint window_width, window_height = 0; /* Window width & height */
75         gint mon = 0;                         /* Monitor to display on */
76         gint window_x, window_y = 0;          /* Target X, Y */
77 
78         if (!self->relative_to) {
79                 g_warning("Cannot set relative location without relative widget!");
80                 return;
81         }
82 
83         /* Forcibly realize the applet window */
84         if (!gtk_widget_get_realized(self->relative_to)) {
85                 gtk_widget_realize(self->relative_to);
86         }
87 
88         /* Forcibly realize ourselves */
89         if (!gtk_widget_get_realized(GTK_WIDGET(self))) {
90                 gtk_widget_realize(GTK_WIDGET(self));
91         }
92 
93         gtk_widget_get_allocation(self->relative_to, &relative_alloc);
94 
95         /* Find out where we are on screen */
96         window = gtk_widget_get_window(self->relative_to);
97         gdk_window_get_origin(window, &relative_x, &relative_y);
98 
99         /* Grab the geometry for the monitor we're currently on */
100         screen = gtk_widget_get_screen(self->relative_to);
101         mon = gdk_screen_get_monitor_at_point(screen, relative_x, relative_y);
102         gdk_screen_get_monitor_geometry(screen, mon, &geom);
103 
104         switch (self->position) {
105         case GTK_POS_LEFT:
106                 window_width = geom.width - relative_alloc.width;
107                 window_height = geom.height;
108                 window_x = geom.x + relative_alloc.width;
109                 window_y = geom.y;
110                 break;
111         case GTK_POS_RIGHT:
112                 window_width = geom.width - relative_alloc.width;
113                 window_height = geom.height;
114                 window_x = geom.x;
115                 window_y = geom.y;
116                 break;
117         case GTK_POS_TOP:
118                 /* Top panel, appear below it */
119                 window_width = geom.width;
120                 window_height = geom.height - relative_alloc.height;
121                 window_x = geom.x;
122                 window_y = relative_y + relative_alloc.height;
123                 break;
124         case GTK_POS_BOTTOM:
125         default:
126                 /* Bottom panel, appear above it */
127                 window_width = geom.width;
128                 window_height = geom.height - relative_alloc.height;
129                 window_x = geom.x;
130                 window_y = geom.y;
131                 break;
132         }
133 
134         gtk_window_set_default_size(GTK_WINDOW(self), window_width, window_height);
135         gtk_window_move(GTK_WINDOW(self), window_x, window_y);
136 }
137 
138 /**
139  * Backend has new items for us, add to the global store
140  */
brisk_dash_window_add_item(BriskMenuWindow * self,BriskItem * item,__brisk_unused__ BriskBackend * backend)141 static void brisk_dash_window_add_item(BriskMenuWindow *self, BriskItem *item,
142                                        __brisk_unused__ BriskBackend *backend)
143 {
144         BriskMenuEntryButton *button = NULL;
145         const gchar *item_id = brisk_item_get_id(item);
146 
147         button = brisk_dash_entry_button_new(self->launcher, item);
148         g_signal_connect_swapped(button,
149                                  "show-context-menu",
150                                  G_CALLBACK(brisk_menu_window_show_context),
151                                  self);
152         gtk_container_add(GTK_CONTAINER(BRISK_DASH_WINDOW(self)->apps), GTK_WIDGET(button));
153         gtk_widget_show_all(GTK_WIDGET(button));
154 
155         g_hash_table_insert(self->item_store, g_strdup(item_id), GTK_WIDGET(button));
156 }
157 
158 /**
159  * Backend has a new sidebar section for us
160  */
brisk_dash_window_add_section(BriskMenuWindow * self,BriskSection * section,__brisk_unused__ BriskBackend * backend)161 static void brisk_dash_window_add_section(BriskMenuWindow *self, BriskSection *section,
162                                           __brisk_unused__ BriskBackend *backend)
163 {
164         GtkWidget *button = NULL;
165         const gchar *section_id = brisk_section_get_id(section);
166         GtkWidget *box_target = NULL;
167 
168         /* Skip dupes. Sections are uniquely namespaced */
169         if (g_hash_table_lookup(self->item_store, section_id) != NULL) {
170                 return;
171         }
172 
173         box_target = brisk_menu_window_get_section_box(self, backend);
174         gtk_orientable_set_orientation(GTK_ORIENTABLE(box_target), GTK_ORIENTATION_HORIZONTAL);
175 
176         button = brisk_dash_category_button_new(section);
177         gtk_radio_button_join_group(GTK_RADIO_BUTTON(button),
178                                     GTK_RADIO_BUTTON(self->section_box_leader));
179         gtk_box_pack_start(GTK_BOX(box_target), button, FALSE, FALSE, 0);
180         brisk_dash_window_associate_category(self, button);
181         gtk_widget_show_all(button);
182 
183         /* Avoid new dupes */
184         g_hash_table_insert(self->item_store, g_strdup(section_id), button);
185 
186         brisk_menu_window_select_sections(self);
187 }
188 
189 /**
190  * A backend needs us to invalidate the filters
191  */
brisk_dash_window_invalidate_filter(BriskMenuWindow * self,__brisk_unused__ BriskBackend * backend)192 static void brisk_dash_window_invalidate_filter(BriskMenuWindow *self,
193                                                 __brisk_unused__ BriskBackend *backend)
194 {
195         gtk_flow_box_invalidate_filter(GTK_FLOW_BOX(BRISK_DASH_WINDOW(self)->apps));
196         gtk_flow_box_invalidate_sort(GTK_FLOW_BOX(BRISK_DASH_WINDOW(self)->apps));
197 
198         gtk_flow_box_unselect_all(GTK_FLOW_BOX(BRISK_DASH_WINDOW(self)->apps));
199 }
200 
201 /**
202  * A backend needs us to purge any data we have for it
203  */
brisk_dash_window_reset(BriskMenuWindow * self,BriskBackend * backend)204 static void brisk_dash_window_reset(BriskMenuWindow *self, BriskBackend *backend)
205 {
206         GtkWidget *box_target = NULL;
207         GList *kids = NULL, *elem = NULL;
208         const gchar *backend_id = NULL;
209 
210         backend_id = brisk_backend_get_id(backend);
211 
212         box_target = brisk_menu_window_get_section_box(self, backend);
213         gtk_container_foreach(GTK_CONTAINER(box_target),
214                               (GtkCallback)brisk_menu_window_remove_category,
215                               self);
216 
217         /* Manual work for the items */
218         kids = gtk_container_get_children(GTK_CONTAINER(BRISK_DASH_WINDOW(self)->apps));
219         for (elem = kids; elem; elem = elem->next) {
220                 GtkWidget *row = elem->data;
221                 GtkWidget *child = NULL;
222                 BriskItem *item = NULL;
223                 const gchar *local_backend_id = NULL;
224                 const gchar *local_id = NULL;
225 
226                 if (!GTK_IS_BIN(GTK_BIN(row))) {
227                         continue;
228                 }
229 
230                 child = gtk_bin_get_child(GTK_BIN(row));
231                 if (!BRISK_IS_MENU_ENTRY_BUTTON(child)) {
232                         continue;
233                 }
234 
235                 g_object_get(child, "item", &item, NULL);
236                 if (!item) {
237                         g_warning("missing item for entry in backend '%s'", backend_id);
238                         continue;
239                 }
240 
241                 local_backend_id = brisk_item_get_backend_id(item);
242                 if (!g_str_equal(backend_id, local_backend_id)) {
243                         continue;
244                 }
245                 local_id = brisk_item_get_id(item);
246                 g_hash_table_remove(self->item_store, local_id);
247                 gtk_widget_destroy(row);
248         }
249         g_list_free(kids);
250 }
251 
252 /**
253  * Override hiding so that we can invalidate all filters
254  */
brisk_dash_window_hide(GtkWidget * widget)255 static void brisk_dash_window_hide(GtkWidget *widget)
256 {
257         BriskDashWindow *self = NULL;
258         GtkAdjustment *adjustment = NULL;
259         autofree(GList) *selected_children = NULL;
260         GtkWidget *selected = NULL;
261 
262         /* Have parent deal with it first */
263         GTK_WIDGET_CLASS(brisk_dash_window_parent_class)->hide(widget);
264 
265         self = BRISK_DASH_WINDOW(widget);
266 
267         /* Remove search filter */
268         gtk_entry_set_text(GTK_ENTRY(BRISK_MENU_WINDOW(self)->search), "");
269 
270         brisk_menu_window_select_sections(BRISK_MENU_WINDOW(self));
271 
272         /* Reset scrollbars */
273         adjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(self->apps_scroll));
274         gtk_adjustment_set_value(adjustment, 0);
275         adjustment =
276             gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(self->categories_scroll));
277         gtk_adjustment_set_value(adjustment, 0);
278 
279         /* Unselect any current "apps" */
280         selected_children = gtk_flow_box_get_selected_children(GTK_FLOW_BOX(self->apps));
281         selected = g_list_nth_data(selected_children, 0);
282         if (selected) {
283                 gtk_flow_box_unselect_child(GTK_FLOW_BOX(self->apps), GTK_FLOW_BOX_CHILD(selected));
284         }
285 }
286 
287 /**
288  * brisk_dash_window_class_init:
289  *
290  * Handle class initialisation
291  */
brisk_dash_window_class_init(BriskDashWindowClass * klazz)292 static void brisk_dash_window_class_init(BriskDashWindowClass *klazz)
293 {
294         GObjectClass *obj_class = G_OBJECT_CLASS(klazz);
295         BriskMenuWindowClass *b_class = BRISK_MENU_WINDOW_CLASS(klazz);
296         GtkWidgetClass *wid_class = GTK_WIDGET_CLASS(klazz);
297 
298         /* gobject vtable hookup */
299         obj_class->dispose = brisk_dash_window_dispose;
300 
301         /* Backend vtable hookup */
302         b_class->get_id = brisk_dash_window_get_id;
303         b_class->get_display_name = brisk_dash_window_get_display_name;
304         b_class->update_screen_position = brisk_dash_window_update_screen_position;
305         b_class->add_item = brisk_dash_window_add_item;
306         b_class->add_section = brisk_dash_window_add_section;
307         b_class->invalidate_filter = brisk_dash_window_invalidate_filter;
308         b_class->reset = brisk_dash_window_reset;
309 
310         wid_class->hide = brisk_dash_window_hide;
311 }
312 
313 /**
314  * brisk_dash_window_init:
315  *
316  * Handle construction of the BriskDashWindow
317  */
brisk_dash_window_init(BriskDashWindow * self)318 static void brisk_dash_window_init(BriskDashWindow *self)
319 {
320         GtkWidget *layout = NULL;
321         GtkWidget *widget = NULL;
322         GtkWidget *scroll = NULL;
323         GtkWidget *header = NULL;
324         GtkWidget *content = NULL;
325         GdkScreen *screen = NULL;
326         GtkStyleContext *style = NULL;
327         BriskMenuWindow *base = NULL;
328         GtkSettings *gtk_settings = NULL;
329         autofree(gchar) *txt_holder = NULL;
330 
331         base = BRISK_MENU_WINDOW(self);
332 
333         brisk_dash_window_load_css(gtk_settings, "gtk-theme-name", self);
334         gtk_settings = gtk_settings_get_default();
335         g_signal_connect(gtk_settings,
336                          "notify::gtk-theme-name",
337                          G_CALLBACK(brisk_dash_window_load_css),
338                          self);
339 
340         style = gtk_widget_get_style_context(GTK_WIDGET(self));
341         gtk_style_context_add_class(style, BRISK_STYLE_MAIN);
342         gtk_style_context_add_class(style, BRISK_STYLE_DASH);
343 
344         brisk_menu_window_configure_grabs(base);
345 
346         layout = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2);
347         gtk_container_add(GTK_CONTAINER(self), layout);
348 
349         header = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2);
350         style = gtk_widget_get_style_context(GTK_WIDGET(header));
351         gtk_style_context_add_class(style, "header");
352         gtk_box_pack_start(GTK_BOX(layout), header, FALSE, FALSE, 0);
353 
354         /* Create search entry */
355         widget = gtk_entry_new();
356         gtk_entry_set_icon_from_icon_name(GTK_ENTRY(widget),
357                                           GTK_ENTRY_ICON_PRIMARY,
358                                           "edit-find-symbolic");
359         gtk_entry_set_icon_from_icon_name(GTK_ENTRY(widget),
360                                           GTK_ENTRY_ICON_SECONDARY,
361                                           "edit-clear-symbolic");
362         gtk_box_pack_start(GTK_BOX(header), widget, FALSE, FALSE, 20);
363         gtk_entry_set_width_chars(GTK_ENTRY(widget), 55);
364         gtk_widget_set_valign(widget, GTK_ALIGN_CENTER);
365         gtk_widget_set_halign(widget, GTK_ALIGN_CENTER);
366         /* Translators: This is the text shown in the "search box" with no content */
367         txt_holder = g_strdup_printf("%s\u2026", _("Type to search"));
368         gtk_entry_set_placeholder_text(GTK_ENTRY(widget), txt_holder);
369         base->search = widget;
370         g_signal_connect_swapped(widget, "changed", G_CALLBACK(brisk_menu_window_search), self);
371         g_signal_connect_swapped(widget,
372                                  "activate",
373                                  G_CALLBACK(brisk_dash_window_key_activate),
374                                  self);
375         g_signal_connect(widget, "icon-press", G_CALLBACK(brisk_menu_window_clear_search), self);
376 
377         /* Scrollbar for categories */
378         scroll = gtk_scrolled_window_new(NULL, NULL);
379         gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll), GTK_SHADOW_NONE);
380         gtk_box_pack_start(GTK_BOX(header), scroll, FALSE, FALSE, 0);
381         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
382                                        GTK_POLICY_AUTOMATIC,
383                                        GTK_POLICY_NEVER);
384         gtk_widget_set_can_focus(scroll, TRUE);
385         gtk_scrolled_window_set_overlay_scrolling(GTK_SCROLLED_WINDOW(scroll), TRUE);
386         self->categories_scroll = scroll;
387 
388         /* Container for categories */
389         widget = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
390         gtk_container_add(GTK_CONTAINER(scroll), widget);
391         base->section_box_holder = widget;
392 
393         style = gtk_widget_get_style_context(widget);
394         gtk_style_context_add_class(style, "section-box-holder");
395 
396         /* Content layout */
397         content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2);
398         gtk_box_pack_start(GTK_BOX(layout), content, TRUE, TRUE, 0);
399 
400         /* Scrollbar for apps */
401         scroll = gtk_scrolled_window_new(NULL, NULL);
402         gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll), GTK_SHADOW_NONE);
403         gtk_box_pack_start(GTK_BOX(content), scroll, TRUE, TRUE, 0);
404         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
405                                        GTK_POLICY_NEVER,
406                                        GTK_POLICY_AUTOMATIC);
407         gtk_scrolled_window_set_overlay_scrolling(GTK_SCROLLED_WINDOW(scroll), FALSE);
408         self->apps_scroll = scroll;
409 
410         /* Application launcher display */
411         widget = gtk_flow_box_new();
412         gtk_widget_set_margin_top(widget, 50);
413         gtk_widget_set_margin_bottom(widget, 50);
414         gtk_widget_set_margin_start(widget, 150);
415         gtk_widget_set_margin_end(widget, 150);
416         gtk_container_add(GTK_CONTAINER(scroll), widget);
417         self->apps = widget;
418         g_object_set(self->apps, "halign", GTK_ALIGN_FILL, "valign", GTK_ALIGN_START, NULL);
419         gtk_flow_box_set_column_spacing(GTK_FLOW_BOX(widget), 80);
420         gtk_flow_box_set_max_children_per_line(GTK_FLOW_BOX(widget), 60);
421         gtk_flow_box_set_homogeneous(GTK_FLOW_BOX(widget), TRUE);
422         gtk_flow_box_set_activate_on_single_click(GTK_FLOW_BOX(self->apps), TRUE);
423         gtk_flow_box_set_selection_mode(GTK_FLOW_BOX(self->apps), GTK_SELECTION_SINGLE);
424         g_signal_connect_swapped(self->apps,
425                                  "child-activated",
426                                  G_CALLBACK(brisk_dash_window_activated),
427                                  self);
428 
429         screen = gtk_widget_get_screen(widget);
430         GdkVisual *vis = gdk_screen_get_rgba_visual(screen);
431         if (vis) {
432                 gtk_widget_set_visual(GTK_WIDGET(self), vis);
433         }
434 
435         brisk_dash_window_set_filters_enabled(self, FALSE);
436 
437         /* Special leader to control group association, hidden from view */
438         base->section_box_leader = gtk_radio_button_new(NULL);
439         gtk_box_pack_start(GTK_BOX(base->section_box_holder),
440                            base->section_box_leader,
441                            FALSE,
442                            FALSE,
443                            0);
444         gtk_widget_set_no_show_all(base->section_box_leader, TRUE);
445         gtk_widget_hide(base->section_box_leader);
446 
447         brisk_dash_window_set_filters_enabled(self, TRUE);
448 
449         /* Hook up keyboard events */
450         g_signal_connect(self,
451                          "key-release-event",
452                          G_CALLBACK(brisk_menu_window_key_release),
453                          NULL);
454         g_signal_connect(self, "key-press-event", G_CALLBACK(brisk_menu_window_key_press), NULL);
455 
456         brisk_menu_window_init_backends(base);
457 
458         gtk_widget_show_all(layout);
459 }
460 
461 /**
462  * brisk_dash_window_new:
463  *
464  * Return a newly created BriskDashWindow
465  */
brisk_dash_window_new(GtkWidget * relative_to)466 BriskMenuWindow *brisk_dash_window_new(GtkWidget *relative_to)
467 {
468         return g_object_new(BRISK_TYPE_DASH_WINDOW,
469                             "type",
470                             GTK_WINDOW_POPUP,
471                             "relative-to",
472                             relative_to,
473                             NULL);
474 }
475 
476 /**
477  * brisk_dash_window_associate_category:
478  *
479  * This will hook up the category button for events to enable us to filter the
480  * list based on the active category.
481  */
brisk_dash_window_associate_category(BriskMenuWindow * self,GtkWidget * button)482 static void brisk_dash_window_associate_category(BriskMenuWindow *self, GtkWidget *button)
483 {
484         g_signal_connect_swapped(button, "toggled", G_CALLBACK(brisk_dash_window_on_toggled), self);
485         g_signal_connect_swapped(button,
486                                  "enter-notify-event",
487                                  G_CALLBACK(brisk_dash_window_on_enter),
488                                  self);
489         g_signal_connect_swapped(button,
490                                  "focus-in-event",
491                                  G_CALLBACK(brisk_dash_window_on_enter),
492                                  self);
493 }
494 
495 /**
496  * Fired by clicking a category button
497  */
brisk_dash_window_on_toggled(BriskMenuWindow * self,GtkWidget * button)498 static void brisk_dash_window_on_toggled(BriskMenuWindow *self, GtkWidget *button)
499 {
500         BriskDashCategoryButton *cat = NULL;
501 
502         /* Skip a double signal due to using a group */
503         if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button))) {
504                 return;
505         }
506 
507         cat = BRISK_DASH_CATEGORY_BUTTON(button);
508         g_object_get(cat, "section", &self->active_section, NULL);
509 
510         /* Start the filter. */
511         brisk_dash_window_invalidate_filter(self, NULL);
512 }
513 
514 /**
515  * Fired by entering into the category button
516  */
brisk_dash_window_on_enter(BriskMenuWindow * self,__brisk_unused__ GdkEvent * event,GtkWidget * button)517 static gboolean brisk_dash_window_on_enter(BriskMenuWindow *self, __brisk_unused__ GdkEvent *event,
518                                            GtkWidget *button)
519 {
520         GtkToggleButton *but = GTK_TOGGLE_BUTTON(button);
521 
522         /* Whether we're in rollover mode */
523         if (!self->rollover) {
524                 return GDK_EVENT_PROPAGATE;
525         }
526 
527         if (gtk_toggle_button_get_active(but) || !gtk_widget_get_visible(button)) {
528                 return GDK_EVENT_PROPAGATE;
529         }
530 
531         /* Force activation */
532         gtk_widget_grab_focus(button);
533         gtk_toggle_button_set_active(but, TRUE);
534 
535         return GDK_EVENT_PROPAGATE;
536 }
537 
538 /**
539  * Load up the CSS assets
540  */
brisk_dash_window_load_css(GtkSettings * settings,const gchar * key,BriskDashWindow * self)541 static void brisk_dash_window_load_css(GtkSettings *settings, const gchar *key,
542                                        BriskDashWindow *self)
543 {
544         GtkCssProvider *css = NULL;
545         GtkStyleContext *context = NULL;
546         autofree(GFile) *file = NULL;
547         autofree(GError) *err = NULL;
548         GdkScreen *screen = NULL;
549         GdkRGBA color;
550 
551         file = g_file_new_for_uri("resource://com/solus-project/brisk/menu/dash/styling.css");
552 
553         context = gtk_widget_get_style_context(GTK_WIDGET(self));
554         if (gtk_style_context_lookup_color(context, "dark_bg_color", &color)) {
555                 file = g_file_new_for_uri(
556                     "resource://com/solus-project/brisk/menu/dash/styling-light.css");
557         }
558 
559         if (!file) {
560                 return;
561         }
562 
563         screen = gtk_widget_get_screen(GTK_WIDGET(self));
564 
565         if (self->css) {
566                 gtk_style_context_remove_provider_for_screen(screen, GTK_STYLE_PROVIDER(self->css));
567                 g_clear_object(&self->css);
568         }
569 
570         css = gtk_css_provider_new();
571         self->css = css;
572 
573         gtk_style_context_add_provider_for_screen(screen,
574                                                   GTK_STYLE_PROVIDER(css),
575                                                   GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
576 
577         if (!gtk_css_provider_load_from_file(css, file, &err)) {
578                 g_warning("Failed to load CSS: %s\n", err->message);
579                 return;
580         }
581 }
582 
brisk_dash_window_key_activate(BriskDashWindow * self,__brisk_unused__ gpointer v)583 static void brisk_dash_window_key_activate(BriskDashWindow *self, __brisk_unused__ gpointer v)
584 {
585         autofree(GList) *kids = NULL;
586         GList *elem = NULL;
587         BriskMenuEntryButton *button = NULL;
588 
589         kids = gtk_container_get_children(GTK_CONTAINER(self->apps));
590 
591         for (elem = kids; elem; elem = elem->next) {
592                 GtkWidget *widget = elem->data;
593 
594                 if (!gtk_widget_get_visible(widget) || !gtk_widget_get_child_visible(widget)) {
595                         continue;
596                 }
597 
598                 button = BRISK_MENU_ENTRY_BUTTON(gtk_bin_get_child(GTK_BIN(widget)));
599                 break;
600         }
601         if (!button) {
602                 return;
603         }
604         brisk_menu_entry_button_launch(button);
605 }
606 
brisk_dash_window_activated(__brisk_unused__ BriskMenuWindow * self,GtkFlowBoxChild * row,__brisk_unused__ gpointer v)607 static void brisk_dash_window_activated(__brisk_unused__ BriskMenuWindow *self,
608                                         GtkFlowBoxChild *row, __brisk_unused__ gpointer v)
609 {
610         BriskMenuEntryButton *button = NULL;
611         GtkWidget *child = NULL;
612 
613         child = gtk_bin_get_child(GTK_BIN(row));
614         if (!child) {
615                 return;
616         }
617         if (!BRISK_IS_MENU_ENTRY_BUTTON(child)) {
618                 return;
619         }
620         button = BRISK_MENU_ENTRY_BUTTON(child);
621         brisk_menu_entry_button_launch(button);
622 }
623 
624 /**
625  * Enable or disable the filters between building of the menus
626  */
brisk_dash_window_set_filters_enabled(BriskDashWindow * self,gboolean enabled)627 static void brisk_dash_window_set_filters_enabled(BriskDashWindow *self, gboolean enabled)
628 {
629         BRISK_MENU_WINDOW(self)->filtering = enabled;
630         if (enabled) {
631                 gtk_flow_box_set_filter_func(GTK_FLOW_BOX(self->apps),
632                                              brisk_dash_window_filter_apps,
633                                              self,
634                                              NULL);
635                 gtk_flow_box_set_sort_func(GTK_FLOW_BOX(self->apps),
636                                            brisk_dash_window_sort,
637                                            self,
638                                            NULL);
639                 return;
640         }
641         gtk_flow_box_set_filter_func(GTK_FLOW_BOX(self->apps), NULL, NULL, NULL);
642         gtk_flow_box_set_sort_func(GTK_FLOW_BOX(self->apps), NULL, NULL, NULL);
643 }
644 
645 /**
646  * brisk_dash_window_filter_apps:
647  *
648  * Responsible for filtering the selection based on active group or search
649  * term.
650  */
brisk_dash_window_filter_apps(GtkFlowBoxChild * row,gpointer v)651 __brisk_pure__ static gboolean brisk_dash_window_filter_apps(GtkFlowBoxChild *row, gpointer v)
652 {
653         BriskMenuWindow *self = NULL;
654         GtkWidget *child = NULL;
655 
656         self = BRISK_MENU_WINDOW(v);
657 
658         if (!self->filtering) {
659                 return FALSE;
660         }
661 
662         /* Grab our Entry widget */
663         child = gtk_bin_get_child(GTK_BIN(row));
664 
665         return brisk_menu_window_filter_apps(self, child);
666 }
667 
brisk_dash_window_sort(GtkFlowBoxChild * row1,GtkFlowBoxChild * row2,gpointer v)668 static gint brisk_dash_window_sort(GtkFlowBoxChild *row1, GtkFlowBoxChild *row2, gpointer v)
669 {
670         GtkWidget *child1, *child2 = NULL;
671         BriskItem *itemA, *itemB = NULL;
672         autofree(gchar) *nameA = NULL;
673         autofree(gchar) *nameB = NULL;
674         BriskMenuWindow *self = NULL;
675 
676         self = BRISK_MENU_WINDOW(v);
677 
678         child1 = gtk_bin_get_child(GTK_BIN(row1));
679         child2 = gtk_bin_get_child(GTK_BIN(row2));
680 
681         g_object_get(child1, "item", &itemA, NULL);
682         g_object_get(child2, "item", &itemB, NULL);
683 
684         return brisk_menu_window_sort(self, itemA, itemB);
685 }
686 
687 /*
688  * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
689  *
690  * Local variables:
691  * c-basic-offset: 8
692  * tab-width: 8
693  * indent-tabs-mode: nil
694  * End:
695  *
696  * vi: set shiftwidth=8 tabstop=8 expandtab:
697  * :indentSize=8:tabSize=8:noTabs=true:
698  */
699