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