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 | |
27 | struct _DemoTaggedEntry |
28 | { |
29 | GtkWidget parent_instance; |
30 | |
31 | GtkWidget *text; |
32 | }; |
33 | |
34 | struct _DemoTaggedEntryClass |
35 | { |
36 | GtkWidgetClass parent_class; |
37 | }; |
38 | |
39 | static void demo_tagged_entry_editable_init (GtkEditableInterface *iface); |
40 | |
41 | G_DEFINE_TYPE_WITH_CODE (DemoTaggedEntry, demo_tagged_entry, GTK_TYPE_WIDGET, |
42 | G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE, demo_tagged_entry_editable_init)) |
43 | |
44 | static void |
45 | demo_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 | |
66 | static void |
67 | demo_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 | |
83 | static void |
84 | demo_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 | |
95 | static void |
96 | demo_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 | |
107 | static gboolean |
108 | demo_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 | |
115 | static void |
116 | demo_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 | |
134 | static GtkEditable * |
135 | demo_tagged_entry_get_delegate (GtkEditable *editable) |
136 | { |
137 | return GTK_EDITABLE (DEMO_TAGGED_ENTRY (editable)->text); |
138 | } |
139 | |
140 | static void |
141 | demo_tagged_entry_editable_init (GtkEditableInterface *iface) |
142 | { |
143 | iface->get_delegate = demo_tagged_entry_get_delegate; |
144 | } |
145 | |
146 | GtkWidget * |
147 | demo_tagged_entry_new (void) |
148 | { |
149 | return GTK_WIDGET (g_object_new (DEMO_TYPE_TAGGED_ENTRY, NULL)); |
150 | } |
151 | |
152 | void |
153 | demo_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 | |
161 | void |
162 | demo_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 | |
171 | void |
172 | demo_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 | |
180 | struct _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 | |
192 | struct _DemoTaggedEntryTagClass |
193 | { |
194 | GtkWidgetClass parent_class; |
195 | }; |
196 | |
197 | enum { |
198 | PROP_0, |
199 | PROP_LABEL, |
200 | PROP_HAS_CLOSE_BUTTON, |
201 | }; |
202 | |
203 | enum { |
204 | SIGNAL_CLICKED, |
205 | SIGNAL_BUTTON_CLICKED, |
206 | LAST_SIGNAL |
207 | }; |
208 | |
209 | static guint signals[LAST_SIGNAL] = { 0, }; |
210 | |
211 | G_DEFINE_TYPE (DemoTaggedEntryTag, demo_tagged_entry_tag, GTK_TYPE_WIDGET) |
212 | |
213 | static void |
214 | on_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 | |
223 | static void |
224 | demo_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 | |
238 | static void |
239 | demo_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 | |
248 | static void |
249 | demo_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 | |
259 | static void |
260 | demo_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 | |
283 | static void |
284 | demo_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 | |
307 | static void |
308 | demo_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 | |
323 | static void |
324 | demo_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 | |
336 | static void |
337 | demo_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 | |
373 | DemoTaggedEntryTag * |
374 | demo_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 | |
381 | const char * |
382 | demo_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 | |
389 | void |
390 | demo_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 | |
398 | static void |
399 | on_button_clicked (GtkButton *button, |
400 | DemoTaggedEntryTag *tag) |
401 | { |
402 | g_signal_emit (instance: tag, signal_id: signals[SIGNAL_BUTTON_CLICKED], detail: 0); |
403 | } |
404 | |
405 | void |
406 | demo_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 | |
436 | gboolean |
437 | demo_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 | |