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