1 /* dzl-search-bar.c
2 *
3 * Copyright (C) 2015 Christian Hergert <christian@hergert.me>
4 *
5 * This file is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU Lesser General Public License as
7 * published by the Free Software Foundation; either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This file is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #define G_LOG_DOMAIN "dzl-search-bar"
20
21 #include "config.h"
22
23 #include <glib/gi18n.h>
24
25 #include "bindings/dzl-signal-group.h"
26 #include "widgets/dzl-search-bar.h"
27
28 typedef struct
29 {
30 GtkRevealer *revealer;
31 GtkBox *box;
32 GtkSearchEntry *entry;
33 GtkButton *close_button;
34
35 DzlSignalGroup *window_signals;
36
37 guint search_mode_enabled : 1;
38 } DzlSearchBarPrivate;
39
40 static void dzl_search_bar_init_buildable (GtkBuildableIface *iface);
41
42 G_DEFINE_TYPE_WITH_CODE (DzlSearchBar, dzl_search_bar, GTK_TYPE_BIN,
43 G_ADD_PRIVATE (DzlSearchBar)
44 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
45 dzl_search_bar_init_buildable))
46
47 enum {
48 PROP_0,
49 PROP_SHOW_CLOSE_BUTTON,
50 PROP_SEARCH_MODE_ENABLED,
51 LAST_PROP
52 };
53
54 enum {
55 ACTIVATE,
56 REVEAL,
57 LAST_SIGNAL
58 };
59
60 static GParamSpec *properties [LAST_PROP];
61 static guint signals [LAST_SIGNAL];
62
63 static void
dzl_search_bar__entry_activate(DzlSearchBar * self,GtkSearchEntry * entry)64 dzl_search_bar__entry_activate (DzlSearchBar *self,
65 GtkSearchEntry *entry)
66 {
67 g_assert (DZL_IS_SEARCH_BAR (self));
68 g_assert (GTK_IS_SEARCH_ENTRY (entry));
69
70 g_signal_emit (self, signals [ACTIVATE], 0);
71 }
72
73 static gboolean
is_modifier_key(const GdkEventKey * event)74 is_modifier_key (const GdkEventKey *event)
75 {
76 static const guint modifier_keyvals[] = {
77 GDK_KEY_Shift_L, GDK_KEY_Shift_R, GDK_KEY_Shift_Lock,
78 GDK_KEY_Caps_Lock, GDK_KEY_ISO_Lock, GDK_KEY_Control_L,
79 GDK_KEY_Control_R, GDK_KEY_Meta_L, GDK_KEY_Meta_R,
80 GDK_KEY_Alt_L, GDK_KEY_Alt_R, GDK_KEY_Super_L, GDK_KEY_Super_R,
81 GDK_KEY_Hyper_L, GDK_KEY_Hyper_R, GDK_KEY_ISO_Level3_Shift,
82 GDK_KEY_ISO_Next_Group, GDK_KEY_ISO_Prev_Group,
83 GDK_KEY_ISO_First_Group, GDK_KEY_ISO_Last_Group,
84 GDK_KEY_Mode_switch, GDK_KEY_Num_Lock, GDK_KEY_Multi_key,
85 GDK_KEY_Scroll_Lock,
86 0
87 };
88 const guint *ac_val;
89
90 g_return_val_if_fail (event != NULL, FALSE);
91
92 ac_val = modifier_keyvals;
93
94 while (*ac_val)
95 {
96 if (event->keyval == *ac_val++)
97 return TRUE;
98 }
99
100 return FALSE;
101 }
102
103 static gboolean
toplevel_key_press_event_before(DzlSearchBar * self,GdkEventKey * event,GtkWindow * toplevel)104 toplevel_key_press_event_before (DzlSearchBar *self,
105 GdkEventKey *event,
106 GtkWindow *toplevel)
107 {
108 DzlSearchBarPrivate *priv = dzl_search_bar_get_instance_private (self);
109
110 g_assert (DZL_IS_SEARCH_BAR (self));
111 g_assert (event != NULL);
112 g_assert (GTK_IS_WINDOW (toplevel));
113
114 switch (event->keyval)
115 {
116 case GDK_KEY_Escape:
117 if (priv->search_mode_enabled && gtk_widget_has_focus (GTK_WIDGET (priv->entry)))
118 {
119 dzl_search_bar_set_search_mode_enabled (self, FALSE);
120 return GDK_EVENT_STOP;
121 }
122 break;
123
124 default:
125 break;
126 }
127
128 return GDK_EVENT_PROPAGATE;
129 }
130
131 static gboolean
toplevel_key_press_event_after(DzlSearchBar * self,GdkEventKey * event,GtkWindow * toplevel)132 toplevel_key_press_event_after (DzlSearchBar *self,
133 GdkEventKey *event,
134 GtkWindow *toplevel)
135 {
136 DzlSearchBarPrivate *priv = dzl_search_bar_get_instance_private (self);
137 GtkWidget *entry;
138
139 g_assert (DZL_IS_SEARCH_BAR (self));
140 g_assert (event != NULL);
141 g_assert (GTK_IS_WINDOW (toplevel));
142
143 entry = GTK_WIDGET (priv->entry);
144
145 switch (event->keyval)
146 {
147 case GDK_KEY_Escape:
148 case GDK_KEY_Up:
149 case GDK_KEY_KP_Up:
150 case GDK_KEY_Down:
151 case GDK_KEY_KP_Down:
152 case GDK_KEY_Left:
153 case GDK_KEY_KP_Left:
154 case GDK_KEY_Right:
155 case GDK_KEY_KP_Right:
156 case GDK_KEY_Home:
157 case GDK_KEY_KP_Home:
158 case GDK_KEY_End:
159 case GDK_KEY_KP_End:
160 case GDK_KEY_Page_Up:
161 case GDK_KEY_KP_Page_Up:
162 case GDK_KEY_Page_Down:
163 case GDK_KEY_KP_Page_Down:
164 case GDK_KEY_KP_Tab:
165 case GDK_KEY_Tab:
166 /* ignore keynav */
167 break;
168
169 default:
170 if (((event->state & GDK_MOD1_MASK) != 0) ||
171 ((event->state & GDK_CONTROL_MASK) != 0) ||
172 priv->search_mode_enabled ||
173 is_modifier_key (event))
174 break;
175
176 dzl_search_bar_set_search_mode_enabled (self, TRUE);
177
178 return GTK_WIDGET_GET_CLASS (entry)->key_press_event (entry, event);
179 }
180
181 return GDK_EVENT_PROPAGATE;
182 }
183
184 static void
dzl_search_bar_hierarchy_changed(GtkWidget * widget,GtkWidget * old_toplevel)185 dzl_search_bar_hierarchy_changed (GtkWidget *widget,
186 GtkWidget *old_toplevel)
187 {
188 DzlSearchBar *self = (DzlSearchBar *)widget;
189 DzlSearchBarPrivate *priv = dzl_search_bar_get_instance_private (self);
190 GtkWidget *toplevel;
191
192 g_assert (DZL_IS_SEARCH_BAR (self));
193
194 toplevel = gtk_widget_get_toplevel (widget);
195
196 if (GTK_IS_WINDOW (toplevel))
197 dzl_signal_group_set_target (priv->window_signals, toplevel);
198 else
199 dzl_signal_group_set_target (priv->window_signals, NULL);
200 }
201
202 static void
dzl_search_bar_reveal(DzlSearchBar * self)203 dzl_search_bar_reveal (DzlSearchBar *self)
204 {
205 g_assert (DZL_IS_SEARCH_BAR (self));
206
207 dzl_search_bar_set_search_mode_enabled (self, TRUE);
208 }
209
210 static GObject *
dzl_search_bar_get_internal_child(GtkBuildable * buildable,GtkBuilder * builder,const gchar * childname)211 dzl_search_bar_get_internal_child (GtkBuildable *buildable,
212 GtkBuilder *builder,
213 const gchar *childname)
214 {
215 DzlSearchBar *self = (DzlSearchBar *)buildable;
216 DzlSearchBarPrivate *priv = dzl_search_bar_get_instance_private (self);
217
218 g_assert (GTK_IS_BUILDABLE (buildable));
219 g_assert (DZL_IS_SEARCH_BAR (self));
220 g_assert (GTK_IS_BUILDER (builder));
221 g_assert (childname != NULL);
222
223 if (g_strcmp0 (childname, "entry") == 0)
224 return G_OBJECT (priv->entry);
225 else if (g_strcmp0 (childname, "revealer") == 0)
226 return G_OBJECT (priv->revealer);
227
228 return NULL;
229 }
230
231 static void
dzl_search_bar_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)232 dzl_search_bar_get_property (GObject *object,
233 guint prop_id,
234 GValue *value,
235 GParamSpec *pspec)
236 {
237 DzlSearchBar *self = DZL_SEARCH_BAR (object);
238
239 switch (prop_id)
240 {
241 case PROP_SEARCH_MODE_ENABLED:
242 g_value_set_boolean (value, dzl_search_bar_get_search_mode_enabled (self));
243 break;
244
245 case PROP_SHOW_CLOSE_BUTTON:
246 g_value_set_boolean (value, dzl_search_bar_get_show_close_button (self));
247 break;
248
249 default:
250 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
251 }
252 }
253
254 static void
dzl_search_bar_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)255 dzl_search_bar_set_property (GObject *object,
256 guint prop_id,
257 const GValue *value,
258 GParamSpec *pspec)
259 {
260 DzlSearchBar *self = DZL_SEARCH_BAR (object);
261
262 switch (prop_id)
263 {
264 case PROP_SEARCH_MODE_ENABLED:
265 dzl_search_bar_set_search_mode_enabled (self, g_value_get_boolean (value));
266 break;
267
268 case PROP_SHOW_CLOSE_BUTTON:
269 dzl_search_bar_set_show_close_button (self, g_value_get_boolean (value));
270 break;
271
272 default:
273 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
274 }
275 }
276
277 static void
dzl_search_bar_finalize(GObject * object)278 dzl_search_bar_finalize (GObject *object)
279 {
280 DzlSearchBar *self = (DzlSearchBar *)object;
281 DzlSearchBarPrivate *priv = dzl_search_bar_get_instance_private (self);
282
283 g_clear_object (&priv->window_signals);
284
285 G_OBJECT_CLASS (dzl_search_bar_parent_class)->finalize (object);
286 }
287
288 static void
dzl_search_bar_class_init(DzlSearchBarClass * klass)289 dzl_search_bar_class_init (DzlSearchBarClass *klass)
290 {
291 GObjectClass *object_class = G_OBJECT_CLASS (klass);
292 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
293
294 object_class->finalize = dzl_search_bar_finalize;
295 object_class->get_property = dzl_search_bar_get_property;
296 object_class->set_property = dzl_search_bar_set_property;
297
298 widget_class->hierarchy_changed = dzl_search_bar_hierarchy_changed;
299
300 properties [PROP_SEARCH_MODE_ENABLED] =
301 g_param_spec_boolean ("search-mode-enabled",
302 "Search Mode Enabled",
303 "Search Mode Enabled",
304 FALSE,
305 (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
306
307 properties [PROP_SHOW_CLOSE_BUTTON] =
308 g_param_spec_boolean ("show-close-button",
309 "Show Close Button",
310 "Show Close Button",
311 FALSE,
312 (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
313
314 g_object_class_install_properties (object_class, LAST_PROP, properties);
315
316 signals [ACTIVATE] =
317 g_signal_new ("activate",
318 G_TYPE_FROM_CLASS (klass),
319 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
320 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
321
322 signals [REVEAL] =
323 g_signal_new_class_handler ("reveal",
324 G_TYPE_FROM_CLASS (klass),
325 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
326 G_CALLBACK (dzl_search_bar_reveal),
327 NULL, NULL, NULL, G_TYPE_NONE, 0);
328
329 gtk_widget_class_set_css_name (widget_class, "dzlsearchbar");
330 }
331
332 static void
dzl_search_bar_init_buildable(GtkBuildableIface * iface)333 dzl_search_bar_init_buildable (GtkBuildableIface *iface)
334 {
335 iface->get_internal_child = dzl_search_bar_get_internal_child;
336 }
337
338 static void
dzl_search_bar_init(DzlSearchBar * self)339 dzl_search_bar_init (DzlSearchBar *self)
340 {
341 DzlSearchBarPrivate *priv = dzl_search_bar_get_instance_private (self);
342 GtkStyleContext *style_context;
343 GtkBox *box;
344
345 priv->window_signals = dzl_signal_group_new (GTK_TYPE_WINDOW);
346 dzl_signal_group_connect_object (priv->window_signals,
347 "key-press-event",
348 G_CALLBACK (toplevel_key_press_event_before),
349 self,
350 G_CONNECT_SWAPPED);
351 dzl_signal_group_connect_object (priv->window_signals,
352 "key-press-event",
353 G_CALLBACK (toplevel_key_press_event_after),
354 self,
355 G_CONNECT_SWAPPED | G_CONNECT_AFTER);
356
357 priv->revealer =
358 g_object_new (GTK_TYPE_REVEALER,
359 "transition-type", GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN,
360 "visible", TRUE,
361 NULL);
362 /* outer box used for styling */
363 box =
364 g_object_new (GTK_TYPE_BOX,
365 "orientation", GTK_ORIENTATION_HORIZONTAL,
366 "visible", TRUE,
367 NULL);
368 priv->box =
369 g_object_new (GTK_TYPE_BOX,
370 "hexpand", TRUE,
371 "margin-bottom", 3,
372 "margin-end", 6,
373 "margin-start", 6,
374 "margin-top", 3,
375 "orientation", GTK_ORIENTATION_HORIZONTAL,
376 "visible", TRUE,
377 NULL);
378 priv->entry =
379 g_object_connect (g_object_new (GTK_TYPE_SEARCH_ENTRY,
380 "placeholder-text", _("Search"),
381 "visible", TRUE,
382 NULL),
383 "swapped_object_signal::activate", dzl_search_bar__entry_activate, self,
384 NULL);
385 priv->close_button =
386 g_object_new (GTK_TYPE_BUTTON,
387 "child", g_object_new (GTK_TYPE_IMAGE,
388 "icon-name", "window-close-symbolic",
389 "visible", TRUE,
390 NULL),
391 "visible", FALSE,
392 NULL);
393
394 style_context = gtk_widget_get_style_context (GTK_WIDGET (box));
395 gtk_style_context_add_class (style_context, "search-bar");
396
397 gtk_container_add (GTK_CONTAINER (priv->revealer), GTK_WIDGET (box));
398 gtk_container_add (GTK_CONTAINER (box), GTK_WIDGET (priv->box));
399 gtk_container_add (GTK_CONTAINER (self), GTK_WIDGET (priv->revealer));
400 gtk_container_add_with_properties (GTK_CONTAINER (priv->box),
401 GTK_WIDGET (priv->close_button),
402 "fill", FALSE,
403 "pack-type", GTK_PACK_END,
404 NULL);
405 gtk_box_set_center_widget (priv->box, GTK_WIDGET (priv->entry));
406 }
407
408 GtkWidget *
dzl_search_bar_new(void)409 dzl_search_bar_new (void)
410 {
411 return g_object_new (DZL_TYPE_SEARCH_BAR, NULL);
412 }
413
414 gboolean
dzl_search_bar_get_search_mode_enabled(DzlSearchBar * self)415 dzl_search_bar_get_search_mode_enabled (DzlSearchBar *self)
416 {
417 DzlSearchBarPrivate *priv = dzl_search_bar_get_instance_private (self);
418
419 g_return_val_if_fail (DZL_IS_SEARCH_BAR (self), FALSE);
420
421 return priv->search_mode_enabled;
422 }
423
424 void
dzl_search_bar_set_search_mode_enabled(DzlSearchBar * self,gboolean search_mode_enabled)425 dzl_search_bar_set_search_mode_enabled (DzlSearchBar *self,
426 gboolean search_mode_enabled)
427 {
428 DzlSearchBarPrivate *priv = dzl_search_bar_get_instance_private (self);
429
430 g_return_if_fail (DZL_IS_SEARCH_BAR (self));
431
432 search_mode_enabled = !!search_mode_enabled;
433
434 if (search_mode_enabled != priv->search_mode_enabled)
435 {
436 priv->search_mode_enabled = search_mode_enabled;
437 gtk_revealer_set_reveal_child (priv->revealer, search_mode_enabled);
438 gtk_entry_set_text (GTK_ENTRY (priv->entry), "");
439 if (search_mode_enabled)
440 gtk_widget_grab_focus (GTK_WIDGET (priv->entry));
441
442 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SEARCH_MODE_ENABLED]);
443 }
444 }
445
446 gboolean
dzl_search_bar_get_show_close_button(DzlSearchBar * self)447 dzl_search_bar_get_show_close_button (DzlSearchBar *self)
448 {
449 DzlSearchBarPrivate *priv = dzl_search_bar_get_instance_private (self);
450
451 g_return_val_if_fail (DZL_IS_SEARCH_BAR (self), FALSE);
452
453 return gtk_widget_get_visible (GTK_WIDGET (priv->close_button));
454 }
455
456 void
dzl_search_bar_set_show_close_button(DzlSearchBar * self,gboolean show_close_button)457 dzl_search_bar_set_show_close_button (DzlSearchBar *self,
458 gboolean show_close_button)
459 {
460 DzlSearchBarPrivate *priv = dzl_search_bar_get_instance_private (self);
461
462 g_return_if_fail (DZL_IS_SEARCH_BAR (self));
463
464 gtk_widget_set_visible (GTK_WIDGET (priv->close_button), show_close_button);
465 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SHOW_CLOSE_BUTTON]);
466 }
467
468 /**
469 * dzl_search_bar_get_entry:
470 *
471 * Returns: (transfer none) (type Gtk.SearchEntry): A #GtkSearchEntry.
472 */
473 GtkWidget *
dzl_search_bar_get_entry(DzlSearchBar * self)474 dzl_search_bar_get_entry (DzlSearchBar *self)
475 {
476 DzlSearchBarPrivate *priv = dzl_search_bar_get_instance_private (self);
477
478 g_return_val_if_fail (DZL_IS_SEARCH_BAR (self), NULL);
479
480 return GTK_WIDGET (priv->entry);
481 }
482