1 /* dzl-suggestion-button.c
2  *
3  * Copyright 2019 Christian Hergert <chergert@redhat.com>
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU 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  * SPDX-License-Identifier: GPL-3.0-or-later
19  */
20 
21 #define G_LOG_DOMAIN "dzl-suggestion-button"
22 
23 #include "config.h"
24 
25 #include "suggestions/dzl-suggestion-button.h"
26 #include "suggestions/dzl-suggestion-entry.h"
27 #include "util/dzl-gtk.h"
28 
29 typedef struct
30 {
31   DzlSuggestionEntry *entry;
32   GtkButton          *button;
33   gint                max_width_chars;
34 } DzlSuggestionButtonPrivate;
35 
36 enum {
37   PROP_0,
38   PROP_BUTTON,
39   PROP_ENTRY,
40   N_PROPS
41 };
42 
43 static void buildable_iface_init (GtkBuildableIface *iface);
44 
45 G_DEFINE_TYPE_WITH_CODE (DzlSuggestionButton, dzl_suggestion_button, GTK_TYPE_STACK,
46                          G_ADD_PRIVATE (DzlSuggestionButton)
47                          G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, buildable_iface_init))
48 
49 static GParamSpec *properties [N_PROPS];
50 
51 static void
entry_icon_press_cb(DzlSuggestionButton * self,GtkEntryIconPosition position,GdkEvent * event,DzlSuggestionEntry * entry)52 entry_icon_press_cb (DzlSuggestionButton  *self,
53                      GtkEntryIconPosition  position,
54                      GdkEvent             *event,
55                      DzlSuggestionEntry   *entry)
56 {
57   DzlSuggestionButtonPrivate *priv = dzl_suggestion_button_get_instance_private (self);
58 
59   g_assert (DZL_IS_SUGGESTION_BUTTON (self));
60   g_assert (DZL_IS_SUGGESTION_ENTRY (entry));
61 
62   if (position == GTK_ENTRY_ICON_PRIMARY)
63     gtk_stack_set_visible_child (GTK_STACK (self), GTK_WIDGET (priv->button));
64 }
65 
66 static gboolean
entry_focus_in_event_cb(DzlSuggestionButton * self,GdkEventFocus * focus,DzlSuggestionEntry * entry)67 entry_focus_in_event_cb (DzlSuggestionButton *self,
68                          GdkEventFocus       *focus,
69                          DzlSuggestionEntry  *entry)
70 {
71   DzlSuggestionButtonPrivate *priv = dzl_suggestion_button_get_instance_private (self);
72 
73   g_assert (DZL_IS_SUGGESTION_BUTTON (self));
74   g_assert (DZL_IS_SUGGESTION_ENTRY (entry));
75 
76   gtk_entry_set_width_chars (GTK_ENTRY (priv->entry), 1);
77   gtk_entry_set_max_width_chars (GTK_ENTRY (priv->entry), priv->max_width_chars ?: 20);
78 
79   return GDK_EVENT_PROPAGATE;
80 }
81 
82 static gboolean
entry_focus_out_event_cb(DzlSuggestionButton * self,GdkEventFocus * focus,DzlSuggestionEntry * entry)83 entry_focus_out_event_cb (DzlSuggestionButton *self,
84                           GdkEventFocus       *focus,
85                           DzlSuggestionEntry  *entry)
86 {
87   DzlSuggestionButtonPrivate *priv = dzl_suggestion_button_get_instance_private (self);
88 
89   g_assert (DZL_IS_SUGGESTION_BUTTON (self));
90   g_assert (DZL_IS_SUGGESTION_ENTRY (entry));
91 
92   gtk_entry_set_width_chars (GTK_ENTRY (priv->entry), 0);
93   gtk_entry_set_max_width_chars (GTK_ENTRY (priv->entry), 0);
94   gtk_stack_set_visible_child (GTK_STACK (self), GTK_WIDGET (priv->button));
95 
96   return GDK_EVENT_PROPAGATE;
97 }
98 
99 static void
dzl_suggestion_button_begin(DzlSuggestionButton * self)100 dzl_suggestion_button_begin (DzlSuggestionButton *self)
101 {
102   DzlSuggestionButtonPrivate *priv = dzl_suggestion_button_get_instance_private (self);
103   gint max_width_chars;
104 
105   g_assert (DZL_IS_SUGGESTION_BUTTON (self));
106 
107   max_width_chars = gtk_entry_get_max_width_chars (GTK_ENTRY (priv->entry));
108 
109   if (max_width_chars)
110     priv->max_width_chars = max_width_chars;
111 
112   gtk_entry_set_width_chars (GTK_ENTRY (priv->entry), 1);
113   gtk_entry_set_max_width_chars (GTK_ENTRY (priv->entry), priv->max_width_chars ?: 20);
114   gtk_stack_set_visible_child (GTK_STACK (self), GTK_WIDGET (priv->entry));
115   gtk_widget_grab_focus (GTK_WIDGET (priv->entry));
116 }
117 
118 static void
button_clicked_cb(DzlSuggestionButton * self,GtkButton * button)119 button_clicked_cb (DzlSuggestionButton *self,
120                    GtkButton           *button)
121 {
122   g_assert (DZL_IS_SUGGESTION_BUTTON (self));
123   g_assert (GTK_IS_BUTTON (button));
124 
125   dzl_suggestion_button_begin (self);
126 }
127 
128 static void
dzl_suggestion_button_get_preferred_width(GtkWidget * widget,gint * min_width,gint * nat_width)129 dzl_suggestion_button_get_preferred_width (GtkWidget *widget,
130                                            gint      *min_width,
131                                            gint      *nat_width)
132 {
133   DzlSuggestionButton *self = (DzlSuggestionButton *)widget;
134   DzlSuggestionButtonPrivate *priv = dzl_suggestion_button_get_instance_private (self);
135   gint entry_min_width = -1;
136   gint entry_nat_width = -1;
137 
138   g_assert (DZL_IS_SUGGESTION_BUTTON (self));
139   g_assert (min_width != NULL);
140   g_assert (nat_width != NULL);
141 
142   GTK_WIDGET_CLASS (dzl_suggestion_button_parent_class)->get_preferred_width (widget, min_width, nat_width);
143 
144   if (gtk_stack_get_transition_running (GTK_STACK (self)) ||
145       gtk_stack_get_visible_child (GTK_STACK (self)) == GTK_WIDGET (priv->entry))
146     {
147       gtk_widget_get_preferred_width (GTK_WIDGET (priv->entry), &entry_min_width, &entry_nat_width);
148       *min_width = MAX (*min_width, entry_min_width);
149       *nat_width = MAX (*nat_width, entry_min_width);
150     }
151 }
152 
153 static void
dzl_suggestion_button_grab_focus(GtkWidget * widget)154 dzl_suggestion_button_grab_focus (GtkWidget *widget)
155 {
156   DzlSuggestionButton *self = (DzlSuggestionButton *)widget;
157 
158   g_assert (DZL_IS_SUGGESTION_BUTTON (self));
159 
160   dzl_suggestion_button_begin (self);
161 }
162 
163 static void
dzl_suggestion_button_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)164 dzl_suggestion_button_get_property (GObject    *object,
165                                     guint       prop_id,
166                                     GValue     *value,
167                                     GParamSpec *pspec)
168 {
169   DzlSuggestionButton *self = DZL_SUGGESTION_BUTTON (object);
170 
171   switch (prop_id)
172     {
173     case PROP_BUTTON:
174       g_value_set_object (value, dzl_suggestion_button_get_button (self));
175       break;
176 
177     case PROP_ENTRY:
178       g_value_set_object (value, dzl_suggestion_button_get_entry (self));
179       break;
180 
181     default:
182       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
183     }
184 }
185 
186 static void
dzl_suggestion_button_class_init(DzlSuggestionButtonClass * klass)187 dzl_suggestion_button_class_init (DzlSuggestionButtonClass *klass)
188 {
189   GObjectClass *object_class = G_OBJECT_CLASS (klass);
190   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
191 
192   object_class->get_property = dzl_suggestion_button_get_property;
193 
194   widget_class->grab_focus = dzl_suggestion_button_grab_focus;
195   widget_class->get_preferred_width = dzl_suggestion_button_get_preferred_width;
196 
197   properties [PROP_BUTTON] =
198     g_param_spec_object ("button",
199                          "Button",
200                          "The button to be displayed",
201                          GTK_TYPE_BUTTON,
202                          (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
203 
204   properties [PROP_ENTRY] =
205     g_param_spec_object ("entry",
206                          "Entry",
207                          "The entry for user input",
208                          DZL_TYPE_SUGGESTION_ENTRY,
209                          (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
210 
211   g_object_class_install_properties (object_class, N_PROPS, properties);
212 }
213 
214 static void
dzl_suggestion_button_init(DzlSuggestionButton * self)215 dzl_suggestion_button_init (DzlSuggestionButton *self)
216 {
217   DzlSuggestionButtonPrivate *priv = dzl_suggestion_button_get_instance_private (self);
218 
219   gtk_stack_set_hhomogeneous (GTK_STACK (self), FALSE);
220   gtk_stack_set_interpolate_size (GTK_STACK (self), TRUE);
221   gtk_stack_set_transition_type (GTK_STACK (self), GTK_STACK_TRANSITION_TYPE_CROSSFADE);
222   gtk_stack_set_transition_duration (GTK_STACK (self), 200);
223 
224   dzl_gtk_widget_add_style_class (GTK_WIDGET (self), "suggestionbutton");
225 
226   priv->button = g_object_new (GTK_TYPE_BUTTON,
227                                "child", g_object_new (GTK_TYPE_IMAGE,
228                                                       "icon-name", "edit-find-symbolic",
229                                                       "halign", GTK_ALIGN_START,
230                                                       "visible", TRUE,
231                                                       NULL),
232                                "visible", TRUE,
233                                NULL);
234   g_signal_connect_object (priv->button,
235                            "clicked",
236                            G_CALLBACK (button_clicked_cb),
237                            self,
238                            G_CONNECT_SWAPPED);
239   gtk_container_add_with_properties (GTK_CONTAINER (self), GTK_WIDGET (priv->button),
240                                      "name", "button",
241                                      NULL);
242 
243   priv->entry = g_object_new (DZL_TYPE_SUGGESTION_ENTRY,
244                               "max-width-chars", 0,
245                               "placeholder-text", NULL,
246                               "primary-icon-name", "edit-find-symbolic",
247                               "visible", TRUE,
248                               "width-chars", 0,
249                               NULL);
250   g_signal_connect_object (priv->entry,
251                            "icon-press",
252                            G_CALLBACK (entry_icon_press_cb),
253                            self,
254                            G_CONNECT_SWAPPED);
255   g_signal_connect_object (priv->entry,
256                            "focus-in-event",
257                            G_CALLBACK (entry_focus_in_event_cb),
258                            self,
259                            G_CONNECT_SWAPPED);
260   g_signal_connect_object (priv->entry,
261                            "focus-out-event",
262                            G_CALLBACK (entry_focus_out_event_cb),
263                            self,
264                            G_CONNECT_SWAPPED);
265   gtk_container_add_with_properties (GTK_CONTAINER (self), GTK_WIDGET (priv->entry),
266                                      "name", "entry",
267                                      NULL);
268 }
269 
270 GtkWidget *
dzl_suggestion_button_new(void)271 dzl_suggestion_button_new (void)
272 {
273   return g_object_new (DZL_TYPE_SUGGESTION_BUTTON, NULL);
274 }
275 
276 /**
277  * dzl_suggestion_button_get_entry:
278  * @self: a #DzlSuggestionButton
279  *
280  * Returns: (transfer none): a #DzlSuggestionEntry
281  *
282  * Since: 3.34
283  */
284 DzlSuggestionEntry *
dzl_suggestion_button_get_entry(DzlSuggestionButton * self)285 dzl_suggestion_button_get_entry (DzlSuggestionButton *self)
286 {
287   DzlSuggestionButtonPrivate *priv = dzl_suggestion_button_get_instance_private (self);
288 
289   g_return_val_if_fail (DZL_IS_SUGGESTION_BUTTON (self), NULL);
290 
291   return priv->entry;
292 }
293 
294 /**
295  * dzl_suggestion_button_get_button:
296  * @self: a #DzlSuggestionButton
297  *
298  * Returns: (transfer none): a #GtkWidget
299  *
300  * Since: 3.34
301  */
302 GtkButton *
dzl_suggestion_button_get_button(DzlSuggestionButton * self)303 dzl_suggestion_button_get_button (DzlSuggestionButton *self)
304 {
305   DzlSuggestionButtonPrivate *priv = dzl_suggestion_button_get_instance_private (self);
306 
307   g_return_val_if_fail (DZL_IS_SUGGESTION_BUTTON (self), NULL);
308 
309   return priv->button;
310 }
311 
312 static GObject *
get_internal_child(GtkBuildable * buildable,GtkBuilder * builder,const gchar * childname)313 get_internal_child (GtkBuildable *buildable,
314                     GtkBuilder   *builder,
315                     const gchar  *childname)
316 {
317   DzlSuggestionButton *self = (DzlSuggestionButton *)buildable;
318   DzlSuggestionButtonPrivate *priv = dzl_suggestion_button_get_instance_private (self);
319 
320   g_assert (DZL_IS_SUGGESTION_BUTTON (self));
321 
322   if (g_strcmp0 (childname, "entry") == 0)
323     return G_OBJECT (priv->entry);
324   else if (g_strcmp0 (childname, "button") == 0)
325     return G_OBJECT (priv->button);
326   else
327     return NULL;
328 }
329 
330 static void
buildable_iface_init(GtkBuildableIface * iface)331 buildable_iface_init (GtkBuildableIface *iface)
332 {
333   iface->get_internal_child = get_internal_child;
334 }
335