1 /*
2  * Photos - access, organize and share your photos on GNOME
3  * Copyright © 2014 Pranav Kant
4  * Copyright © 2014 – 2019 Red Hat, Inc.
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 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 /* Based on code from:
21  *   + Documents
22  */
23 
24 
25 #include "config.h"
26 
27 #include <glib.h>
28 
29 #include "photos-searchbar.h"
30 
31 
32 struct _PhotosSearchbarPrivate
33 {
34   GAction *search;
35   GtkWidget *search_container;
36   GtkWidget *search_entry;
37   gboolean search_change_blocked;
38   gulong search_state_id;
39 };
40 
41 enum
42 {
43   ACTIVATE_RESULT,
44   LAST_SIGNAL
45 };
46 
47 static guint signals[LAST_SIGNAL] = { 0 };
48 
49 
50 G_DEFINE_TYPE_WITH_PRIVATE (PhotosSearchbar, photos_searchbar, GTK_TYPE_SEARCH_BAR);
51 
52 
53 static void
photos_searchbar_default_hide(PhotosSearchbar * self)54 photos_searchbar_default_hide (PhotosSearchbar *self)
55 {
56   PhotosSearchbarPrivate *priv;
57 
58   priv = photos_searchbar_get_instance_private (self);
59 
60   gtk_search_bar_set_search_mode (GTK_SEARCH_BAR (self), FALSE);
61 
62   /* Clear all the search properties when hiding the entry */
63   gtk_entry_set_text (GTK_ENTRY (priv->search_entry), "");
64 }
65 
66 
67 static void
photos_searchbar_default_show(PhotosSearchbar * self)68 photos_searchbar_default_show (PhotosSearchbar *self)
69 {
70   gtk_search_bar_set_search_mode (GTK_SEARCH_BAR (self), TRUE);
71 }
72 
73 
74 static void
photos_searchbar_enable_search(PhotosSearchbar * self,gboolean enable)75 photos_searchbar_enable_search (PhotosSearchbar *self, gboolean enable)
76 {
77   PhotosSearchbarPrivate *priv;
78   GVariant *state;
79 
80   priv = photos_searchbar_get_instance_private (self);
81 
82   state = g_variant_new ("b", enable);
83   g_action_change_state (priv->search, state);
84 }
85 
86 
87 static void
photos_searchbar_notify_search_mode_enabled(PhotosSearchbar * self)88 photos_searchbar_notify_search_mode_enabled (PhotosSearchbar *self)
89 {
90   gboolean search_enabled;
91 
92   search_enabled = gtk_search_bar_get_search_mode (GTK_SEARCH_BAR (self));
93   photos_searchbar_enable_search (self, search_enabled);
94 }
95 
96 
97 static void
photos_searchbar_search_changed(PhotosSearchbar * self)98 photos_searchbar_search_changed (PhotosSearchbar *self)
99 {
100   PhotosSearchbarPrivate *priv;
101 
102   priv = photos_searchbar_get_instance_private (self);
103 
104   if (priv->search_change_blocked)
105     return;
106 
107   PHOTOS_SEARCHBAR_GET_CLASS (self)->entry_changed (self);
108 }
109 
110 
111 static void
photos_searchbar_update_visibility(PhotosSearchbar * self)112 photos_searchbar_update_visibility (PhotosSearchbar *self)
113 {
114   PhotosSearchbarPrivate *priv;
115   g_autoptr (GVariant) state = NULL;
116 
117   priv = photos_searchbar_get_instance_private (self);
118 
119   state = g_action_get_state (priv->search);
120   g_return_if_fail (state != NULL);
121 
122   if (g_variant_get_boolean (state))
123     photos_searchbar_show (self);
124   else
125     photos_searchbar_hide (self);
126 }
127 
128 
129 static void
photos_searchbar_search_notify_state(PhotosSearchbar * self)130 photos_searchbar_search_notify_state (PhotosSearchbar *self)
131 {
132   photos_searchbar_update_visibility (self);
133 }
134 
135 
136 static void
photos_searchbar_constructed(GObject * object)137 photos_searchbar_constructed (GObject *object)
138 {
139   PhotosSearchbar *self = PHOTOS_SEARCHBAR (object);
140   PhotosSearchbarPrivate *priv;
141   GApplication *app;
142 
143   priv = photos_searchbar_get_instance_private (self);
144 
145   G_OBJECT_CLASS (photos_searchbar_parent_class)->constructed (object);
146 
147   PHOTOS_SEARCHBAR_GET_CLASS (self)->create_search_widgets (self);
148 
149   gtk_container_add (GTK_CONTAINER (self), priv->search_container);
150   gtk_search_bar_connect_entry (GTK_SEARCH_BAR (self), GTK_ENTRY (priv->search_entry));
151 
152   g_signal_connect_swapped (priv->search_entry,
153                             "search-changed",
154                             G_CALLBACK (photos_searchbar_search_changed),
155                             self);
156 
157   g_signal_connect (self,
158                     "notify::search-mode-enabled",
159                     G_CALLBACK (photos_searchbar_notify_search_mode_enabled),
160                     NULL);
161 
162   app = g_application_get_default ();
163   priv->search = g_action_map_lookup_action (G_ACTION_MAP (app), "search");
164 
165   /* g_signal_connect_object will not be able to disconnect the
166    * handler in time because we change the state of the action during
167    * dispose.
168    */
169   priv->search_state_id = g_signal_connect_swapped (priv->search,
170                                                     "notify::state",
171                                                     G_CALLBACK (photos_searchbar_search_notify_state),
172                                                     self);
173   photos_searchbar_update_visibility (self);
174 
175   gtk_widget_show_all (GTK_WIDGET (self));
176 }
177 
178 
179 static void
photos_searchbar_dispose(GObject * object)180 photos_searchbar_dispose (GObject *object)
181 {
182   PhotosSearchbar *self = PHOTOS_SEARCHBAR (object);
183   PhotosSearchbarPrivate *priv;
184 
185   priv = photos_searchbar_get_instance_private (self);
186 
187   if (priv->search_state_id != 0)
188     {
189       g_signal_handler_disconnect (priv->search, priv->search_state_id);
190       priv->search_state_id = 0;
191     }
192 
193   photos_searchbar_enable_search (self, FALSE);
194 
195   G_OBJECT_CLASS (photos_searchbar_parent_class)->dispose (object);
196 }
197 
198 
199 static void
photos_searchbar_init(PhotosSearchbar * self)200 photos_searchbar_init (PhotosSearchbar *self)
201 {
202 }
203 
204 
205 static void
photos_searchbar_class_init(PhotosSearchbarClass * class)206 photos_searchbar_class_init (PhotosSearchbarClass *class)
207 {
208   GObjectClass *object_class = G_OBJECT_CLASS (class);
209 
210   object_class->constructed = photos_searchbar_constructed;
211   object_class->dispose = photos_searchbar_dispose;
212   class->hide = photos_searchbar_default_hide;
213   class->show = photos_searchbar_default_show;
214 
215   signals[ACTIVATE_RESULT] = g_signal_new ("activate-result",
216                                            G_TYPE_FROM_CLASS (class),
217                                            G_SIGNAL_RUN_LAST,
218                                            G_STRUCT_OFFSET (PhotosSearchbarClass, activate_result),
219                                            NULL, /* accumulator */
220                                            NULL, /* accu_data */
221                                            g_cclosure_marshal_VOID__VOID,
222                                            G_TYPE_NONE,
223                                            0);
224 }
225 
226 
227 GtkWidget *
photos_searchbar_new(void)228 photos_searchbar_new (void)
229 {
230   return g_object_new (PHOTOS_TYPE_SEARCHBAR, NULL);
231 }
232 
233 
234 gboolean
photos_searchbar_handle_event(PhotosSearchbar * self,GdkEventKey * event)235 photos_searchbar_handle_event (PhotosSearchbar *self, GdkEventKey *event)
236 {
237   PhotosSearchbarPrivate *priv;
238   gboolean ret_val = GDK_EVENT_PROPAGATE;
239   gboolean search_mode_enabled;
240 
241   priv = photos_searchbar_get_instance_private (self);
242 
243   search_mode_enabled = gtk_search_bar_get_search_mode (GTK_SEARCH_BAR (self));
244 
245   /* Skip if the search bar is shown and the focus is elsewhere */
246   if (search_mode_enabled && !gtk_widget_is_focus (priv->search_entry))
247     goto out;
248 
249   if (search_mode_enabled && event->keyval == GDK_KEY_Return)
250     {
251       g_signal_emit (self, signals[ACTIVATE_RESULT], 0);
252       ret_val = GDK_EVENT_STOP;
253       goto out;
254     }
255 
256   ret_val = gtk_search_bar_handle_event (GTK_SEARCH_BAR (self), (GdkEvent *) event);
257   if (ret_val == GDK_EVENT_STOP)
258     gtk_entry_grab_focus_without_selecting (GTK_ENTRY (priv->search_entry));
259 
260  out:
261   return ret_val;
262 }
263 
264 
265 void
photos_searchbar_hide(PhotosSearchbar * self)266 photos_searchbar_hide (PhotosSearchbar *self)
267 {
268   PHOTOS_SEARCHBAR_GET_CLASS (self)->hide (self);
269 }
270 
271 
272 gboolean
photos_searchbar_is_focus(PhotosSearchbar * self)273 photos_searchbar_is_focus (PhotosSearchbar *self)
274 {
275   PhotosSearchbarPrivate *priv;
276 
277   priv = photos_searchbar_get_instance_private (self);
278   return gtk_widget_is_focus (priv->search_entry);
279 }
280 
281 
282 void
photos_searchbar_set_search_change_blocked(PhotosSearchbar * self,gboolean search_change_blocked)283 photos_searchbar_set_search_change_blocked (PhotosSearchbar *self, gboolean search_change_blocked)
284 {
285   PhotosSearchbarPrivate *priv;
286 
287   priv = photos_searchbar_get_instance_private (self);
288   priv->search_change_blocked = search_change_blocked;
289 }
290 
291 
292 void
photos_searchbar_set_search_container(PhotosSearchbar * self,GtkWidget * search_container)293 photos_searchbar_set_search_container (PhotosSearchbar *self, GtkWidget *search_container)
294 {
295   PhotosSearchbarPrivate *priv;
296 
297   priv = photos_searchbar_get_instance_private (self);
298   priv->search_container = search_container;
299 }
300 
301 
302 void
photos_searchbar_set_search_entry(PhotosSearchbar * self,GtkWidget * search_entry)303 photos_searchbar_set_search_entry (PhotosSearchbar *self, GtkWidget *search_entry)
304 {
305   PhotosSearchbarPrivate *priv;
306 
307   priv = photos_searchbar_get_instance_private (self);
308   priv->search_entry = search_entry;
309 }
310 
311 
312 void
photos_searchbar_show(PhotosSearchbar * self)313 photos_searchbar_show (PhotosSearchbar *self)
314 {
315   PHOTOS_SEARCHBAR_GET_CLASS (self)->show (self);
316 }
317