1/* GTK - The GIMP Toolkit
2 * Copyright (C) 2019 Red Hat, Inc.
3 *
4 * Authors:
5 * - Matthias Clasen <mclasen@redhat.com>
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21#include "config.h"
22
23#include "demotaggedentry.h"
24
25#include <gtk/gtk.h>
26
27struct _DemoTaggedEntry
28{
29 GtkWidget parent_instance;
30
31 GtkWidget *text;
32};
33
34struct _DemoTaggedEntryClass
35{
36 GtkWidgetClass parent_class;
37};
38
39static void demo_tagged_entry_editable_init (GtkEditableInterface *iface);
40
41G_DEFINE_TYPE_WITH_CODE (DemoTaggedEntry, demo_tagged_entry, GTK_TYPE_WIDGET,
42 G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE, demo_tagged_entry_editable_init))
43
44static void
45demo_tagged_entry_init (DemoTaggedEntry *entry)
46{
47 GtkCssProvider *provider;
48
49 entry->text = gtk_text_new ();
50 gtk_widget_set_hexpand (widget: entry->text, TRUE);
51 gtk_widget_set_vexpand (widget: entry->text, TRUE);
52 gtk_widget_set_parent (widget: entry->text, GTK_WIDGET (entry));
53 gtk_editable_init_delegate (GTK_EDITABLE (entry));
54 gtk_editable_set_width_chars (GTK_EDITABLE (entry->text), n_chars: 6);
55 gtk_editable_set_max_width_chars (GTK_EDITABLE (entry->text), n_chars: 6);
56 gtk_widget_add_css_class (GTK_WIDGET (entry), css_class: "tagged");
57
58 provider = gtk_css_provider_new ();
59 gtk_css_provider_load_from_resource (css_provider: provider, resource_path: "/tagged_entry/tagstyle.css");
60 gtk_style_context_add_provider_for_display (display: gdk_display_get_default (),
61 GTK_STYLE_PROVIDER (provider),
62 priority: 800);
63 g_object_unref (object: provider);
64}
65
66static void
67demo_tagged_entry_dispose (GObject *object)
68{
69 DemoTaggedEntry *entry = DEMO_TAGGED_ENTRY (ptr: object);
70 GtkWidget *child;
71
72 if (entry->text)
73 gtk_editable_finish_delegate (GTK_EDITABLE (entry));
74
75 while ((child = gtk_widget_get_first_child (GTK_WIDGET (entry))))
76 gtk_widget_unparent (widget: child);
77
78 entry->text = NULL;
79
80 G_OBJECT_CLASS (demo_tagged_entry_parent_class)->dispose (object);
81}
82
83static void
84demo_tagged_entry_set_property (GObject *object,
85 guint prop_id,
86 const GValue *value,
87 GParamSpec *pspec)
88{
89 if (gtk_editable_delegate_set_property (object, prop_id, value, pspec))
90 return;
91
92 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
93}
94
95static void
96demo_tagged_entry_get_property (GObject *object,
97 guint prop_id,
98 GValue *value,
99 GParamSpec *pspec)
100{
101 if (gtk_editable_delegate_get_property (object, prop_id, value, pspec))
102 return;
103
104 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
105}
106
107static gboolean
108demo_tagged_entry_grab_focus (GtkWidget *widget)
109{
110 DemoTaggedEntry *entry = DEMO_TAGGED_ENTRY (ptr: widget);
111
112 return gtk_widget_grab_focus (widget: entry->text);
113}
114
115static void
116demo_tagged_entry_class_init (DemoTaggedEntryClass *klass)
117{
118 GObjectClass *object_class = G_OBJECT_CLASS (klass);
119 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
120
121 object_class->dispose = demo_tagged_entry_dispose;
122 object_class->get_property = demo_tagged_entry_get_property;
123 object_class->set_property = demo_tagged_entry_set_property;
124
125 widget_class->grab_focus = demo_tagged_entry_grab_focus;
126
127 gtk_editable_install_properties (object_class, first_prop: 1);
128
129 gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BOX_LAYOUT);
130 gtk_widget_class_set_css_name (widget_class, name: "entry");
131 gtk_widget_class_set_accessible_role (widget_class, accessible_role: GTK_ACCESSIBLE_ROLE_TEXT_BOX);
132}
133
134static GtkEditable *
135demo_tagged_entry_get_delegate (GtkEditable *editable)
136{
137 return GTK_EDITABLE (DEMO_TAGGED_ENTRY (editable)->text);
138}
139
140static void
141demo_tagged_entry_editable_init (GtkEditableInterface *iface)
142{
143 iface->get_delegate = demo_tagged_entry_get_delegate;
144}
145
146GtkWidget *
147demo_tagged_entry_new (void)
148{
149 return GTK_WIDGET (g_object_new (DEMO_TYPE_TAGGED_ENTRY, NULL));
150}
151
152void
153demo_tagged_entry_add_tag (DemoTaggedEntry *entry,
154 GtkWidget *tag)
155{
156 g_return_if_fail (DEMO_IS_TAGGED_ENTRY (entry));
157
158 gtk_widget_set_parent (widget: tag, GTK_WIDGET (entry));
159}
160
161void
162demo_tagged_entry_insert_tag_after (DemoTaggedEntry *entry,
163 GtkWidget *tag,
164 GtkWidget *sibling)
165{
166 g_return_if_fail (DEMO_IS_TAGGED_ENTRY (entry));
167
168 gtk_widget_insert_after (widget: tag, GTK_WIDGET (entry), previous_sibling: sibling);
169}
170
171void
172demo_tagged_entry_remove_tag (DemoTaggedEntry *entry,
173 GtkWidget *tag)
174{
175 g_return_if_fail (DEMO_IS_TAGGED_ENTRY (entry));
176
177 gtk_widget_unparent (widget: tag);
178}
179
180struct _DemoTaggedEntryTag
181{
182 GtkWidget parent;
183
184 GtkWidget *box;
185 GtkWidget *label;
186 GtkWidget *button;
187
188 gboolean has_close_button;
189 char *style;
190};
191
192struct _DemoTaggedEntryTagClass
193{
194 GtkWidgetClass parent_class;
195};
196
197enum {
198 PROP_0,
199 PROP_LABEL,
200 PROP_HAS_CLOSE_BUTTON,
201};
202
203enum {
204 SIGNAL_CLICKED,
205 SIGNAL_BUTTON_CLICKED,
206 LAST_SIGNAL
207};
208
209static guint signals[LAST_SIGNAL] = { 0, };
210
211G_DEFINE_TYPE (DemoTaggedEntryTag, demo_tagged_entry_tag, GTK_TYPE_WIDGET)
212
213static void
214on_released (GtkGestureClick *gesture,
215 int n_press,
216 double x,
217 double y,
218 DemoTaggedEntryTag *tag)
219{
220 g_signal_emit (instance: tag, signal_id: signals[SIGNAL_CLICKED], detail: 0);
221}
222
223static void
224demo_tagged_entry_tag_init (DemoTaggedEntryTag *tag)
225{
226 GtkGesture *gesture;
227
228 tag->box = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 0);
229 gtk_widget_set_parent (widget: tag->box, GTK_WIDGET (tag));
230 tag->label = gtk_label_new (str: "");
231 gtk_box_append (GTK_BOX (tag->box), child: tag->label);
232
233 gesture = gtk_gesture_click_new ();
234 g_signal_connect (gesture, "released", G_CALLBACK (on_released), tag);
235 gtk_widget_add_controller (GTK_WIDGET (tag), GTK_EVENT_CONTROLLER (gesture));
236}
237
238static void
239demo_tagged_entry_tag_dispose (GObject *object)
240{
241 DemoTaggedEntryTag *tag = DEMO_TAGGED_ENTRY_TAG (ptr: object);
242
243 g_clear_pointer (&tag->box, gtk_widget_unparent);
244
245 G_OBJECT_CLASS (demo_tagged_entry_tag_parent_class)->dispose (object);
246}
247
248static void
249demo_tagged_entry_tag_finalize (GObject *object)
250{
251 DemoTaggedEntryTag *tag = DEMO_TAGGED_ENTRY_TAG (ptr: object);
252
253 g_clear_pointer (&tag->box, gtk_widget_unparent);
254 g_clear_pointer (&tag->style, g_free);
255
256 G_OBJECT_CLASS (demo_tagged_entry_tag_parent_class)->finalize (object);
257}
258
259static void
260demo_tagged_entry_tag_set_property (GObject *object,
261 guint prop_id,
262 const GValue *value,
263 GParamSpec *pspec)
264{
265 DemoTaggedEntryTag *tag = DEMO_TAGGED_ENTRY_TAG (ptr: object);
266
267 switch (prop_id)
268 {
269 case PROP_LABEL:
270 demo_tagged_entry_tag_set_label (tag, label: g_value_get_string (value));
271 break;
272
273 case PROP_HAS_CLOSE_BUTTON:
274 demo_tagged_entry_tag_set_has_close_button (tag, has_close_button: g_value_get_boolean (value));
275 break;
276
277 default:
278 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
279 break;
280 }
281}
282
283static void
284demo_tagged_entry_tag_get_property (GObject *object,
285 guint prop_id,
286 GValue *value,
287 GParamSpec *pspec)
288{
289 DemoTaggedEntryTag *tag = DEMO_TAGGED_ENTRY_TAG (ptr: object);
290
291 switch (prop_id)
292 {
293 case PROP_LABEL:
294 g_value_set_string (value, v_string: demo_tagged_entry_tag_get_label (tag));
295 break;
296
297 case PROP_HAS_CLOSE_BUTTON:
298 g_value_set_boolean (value, v_boolean: demo_tagged_entry_tag_get_has_close_button (tag));
299 break;
300
301 default:
302 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
303 break;
304 }
305}
306
307static void
308demo_tagged_entry_tag_measure (GtkWidget *widget,
309 GtkOrientation orientation,
310 int for_size,
311 int *minimum,
312 int *natural,
313 int *minimum_baseline,
314 int *natural_baseline)
315{
316 DemoTaggedEntryTag *tag = DEMO_TAGGED_ENTRY_TAG (ptr: widget);
317
318 gtk_widget_measure (widget: tag->box, orientation, for_size,
319 minimum, natural,
320 minimum_baseline, natural_baseline);
321}
322
323static void
324demo_tagged_entry_tag_size_allocate (GtkWidget *widget,
325 int width,
326 int height,
327 int baseline)
328{
329 DemoTaggedEntryTag *tag = DEMO_TAGGED_ENTRY_TAG (ptr: widget);
330
331 gtk_widget_size_allocate (widget: tag->box,
332 allocation: &(GtkAllocation) { 0, 0, width, height },
333 baseline);
334}
335
336static void
337demo_tagged_entry_tag_class_init (DemoTaggedEntryTagClass *class)
338{
339 GObjectClass *object_class = G_OBJECT_CLASS (class);
340 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
341
342 object_class->dispose = demo_tagged_entry_tag_dispose;
343 object_class->finalize = demo_tagged_entry_tag_finalize;
344 object_class->set_property = demo_tagged_entry_tag_set_property;
345 object_class->get_property = demo_tagged_entry_tag_get_property;
346
347 widget_class->measure = demo_tagged_entry_tag_measure;
348 widget_class->size_allocate = demo_tagged_entry_tag_size_allocate;
349
350 signals[SIGNAL_CLICKED] =
351 g_signal_new (signal_name: "clicked",
352 DEMO_TYPE_TAGGED_ENTRY_TAG,
353 signal_flags: G_SIGNAL_RUN_FIRST,
354 class_offset: 0, NULL, NULL, NULL,
355 G_TYPE_NONE, n_params: 0);
356 signals[SIGNAL_BUTTON_CLICKED] =
357 g_signal_new (signal_name: "button-clicked",
358 DEMO_TYPE_TAGGED_ENTRY_TAG,
359 signal_flags: G_SIGNAL_RUN_FIRST,
360 class_offset: 0, NULL, NULL, NULL,
361 G_TYPE_NONE, n_params: 0);
362
363 g_object_class_install_property (oclass: object_class, property_id: PROP_LABEL,
364 pspec: g_param_spec_string (name: "label", nick: "Label", blurb: "Label",
365 NULL, flags: G_PARAM_READWRITE));
366 g_object_class_install_property (oclass: object_class, property_id: PROP_HAS_CLOSE_BUTTON,
367 pspec: g_param_spec_boolean (name: "has-close-button", nick: "Has close button", blurb: "Whether this tag has a close button",
368 FALSE, flags: G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
369
370 gtk_widget_class_set_css_name (widget_class, name: "tag");
371}
372
373DemoTaggedEntryTag *
374demo_tagged_entry_tag_new (const char *label)
375{
376 return DEMO_TAGGED_ENTRY_TAG (ptr: g_object_new (DEMO_TYPE_TAGGED_ENTRY_TAG,
377 first_property_name: "label", label,
378 NULL));
379}
380
381const char *
382demo_tagged_entry_tag_get_label (DemoTaggedEntryTag *tag)
383{
384 g_return_val_if_fail (DEMO_IS_TAGGED_ENTRY_TAG (tag), NULL);
385
386 return gtk_label_get_label (GTK_LABEL (tag->label));
387}
388
389void
390demo_tagged_entry_tag_set_label (DemoTaggedEntryTag *tag,
391 const char *label)
392{
393 g_return_if_fail (DEMO_IS_TAGGED_ENTRY_TAG (tag));
394
395 gtk_label_set_label (GTK_LABEL (tag->label), str: label);
396}
397
398static void
399on_button_clicked (GtkButton *button,
400 DemoTaggedEntryTag *tag)
401{
402 g_signal_emit (instance: tag, signal_id: signals[SIGNAL_BUTTON_CLICKED], detail: 0);
403}
404
405void
406demo_tagged_entry_tag_set_has_close_button (DemoTaggedEntryTag *tag,
407 gboolean has_close_button)
408{
409 g_return_if_fail (DEMO_IS_TAGGED_ENTRY_TAG (tag));
410
411 if ((tag->button != NULL) == has_close_button)
412 return;
413
414 if (!has_close_button && tag->button)
415 {
416 gtk_box_remove (GTK_BOX (tag->box), child: tag->button);
417 tag->button = NULL;
418 }
419 else if (has_close_button && tag->button == NULL)
420 {
421 GtkWidget *image;
422
423 image = gtk_image_new_from_icon_name (icon_name: "window-close-symbolic");
424 tag->button = gtk_button_new ();
425 gtk_button_set_child (GTK_BUTTON (tag->button), child: image);
426 gtk_widget_set_halign (widget: tag->button, align: GTK_ALIGN_CENTER);
427 gtk_widget_set_valign (widget: tag->button, align: GTK_ALIGN_CENTER);
428 gtk_button_set_has_frame (GTK_BUTTON (tag->button), FALSE);
429 gtk_box_append (GTK_BOX (tag->box), child: tag->button);
430 g_signal_connect (tag->button, "clicked", G_CALLBACK (on_button_clicked), tag);
431 }
432
433 g_object_notify (G_OBJECT (tag), property_name: "has-close-button");
434}
435
436gboolean
437demo_tagged_entry_tag_get_has_close_button (DemoTaggedEntryTag *tag)
438{
439 g_return_val_if_fail (DEMO_IS_TAGGED_ENTRY_TAG (tag), FALSE);
440
441 return tag->button != NULL;
442}
443

source code of gtk/demos/gtk-demo/demotaggedentry.c