1/* GTK - The GIMP Toolkit
2 * Copyright (C) 2020, Red Hat, Inc
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "config.h"
19
20#include "gtkdragsource.h"
21#include "gtkdroptarget.h"
22#include "gtkeditablelabel.h"
23#include "gtkeditable.h"
24#include "gtklabel.h"
25#include "gtkstack.h"
26#include "gtktext.h"
27#include "gtkbinlayout.h"
28#include "gtkgestureclick.h"
29#include "gtkprivate.h"
30#include "gtkshortcut.h"
31#include "gtkshortcuttrigger.h"
32#include "gtkwidgetprivate.h"
33#include "gtkeventcontrollerfocus.h"
34#include "gtkintl.h"
35
36/**
37 * GtkEditableLabel:
38 *
39 * A `GtkEditableLabel` is a label that allows users to
40 * edit the text by switching to an “edit mode”.
41 *
42 * ![An example GtkEditableLabel](editable-label.png)
43 *
44 * `GtkEditableLabel` does not have API of its own, but it
45 * implements the [iface@Gtk.Editable] interface.
46 *
47 * The default bindings for activating the edit mode is
48 * to click or press the Enter key. The default bindings
49 * for leaving the edit mode are the Enter key (to save
50 * the results) or the Escape key (to cancel the editing).
51 *
52 * # CSS nodes
53 *
54 * ```
55 * editablelabel[.editing]
56 * ╰── stack
57 * ├── label
58 * ╰── text
59 * ```
60 *
61 * `GtkEditableLabel` has a main node with the name editablelabel.
62 * When the entry is in editing mode, it gets the .editing style
63 * class.
64 *
65 * For all the subnodes added to the text node in various situations,
66 * see [class@Gtk.Text].
67 */
68
69struct _GtkEditableLabel
70{
71 GtkWidget parent_instance;
72
73 GtkWidget *stack;
74 GtkWidget *label;
75 GtkWidget *entry;
76};
77
78struct _GtkEditableLabelClass
79{
80 GtkWidgetClass parent_class;
81};
82
83enum
84{
85 PROP_EDITING = 1,
86 NUM_PROPERTIES
87};
88
89static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
90
91static GtkEditable *
92gtk_editable_label_get_delegate (GtkEditable *editable)
93{
94 GtkEditableLabel *self = GTK_EDITABLE_LABEL (ptr: editable);
95
96 return GTK_EDITABLE (self->entry);
97}
98
99static void
100gtk_editable_label_editable_init (GtkEditableInterface *iface)
101{
102 iface->get_delegate = gtk_editable_label_get_delegate;
103}
104
105
106G_DEFINE_TYPE_WITH_CODE (GtkEditableLabel, gtk_editable_label, GTK_TYPE_WIDGET,
107 G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE,
108 gtk_editable_label_editable_init))
109
110static void
111start_editing (GtkWidget *widget,
112 const char *action_name,
113 GVariant *parameters)
114{
115 gtk_editable_label_start_editing (self: GTK_EDITABLE_LABEL (ptr: widget));
116}
117
118static void
119stop_editing (GtkWidget *widget,
120 const char *action_name,
121 GVariant *parameters)
122{
123 gtk_editable_label_stop_editing (self: GTK_EDITABLE_LABEL (ptr: widget),
124 commit: g_variant_get_boolean (value: parameters));
125}
126
127static void
128clicked_cb (GtkWidget *self)
129{
130 gtk_widget_activate_action (widget: self, name: "editing.start", NULL);
131}
132
133static void
134activate_cb (GtkWidget *self)
135{
136 gtk_widget_activate_action (widget: self, name: "editing.stop", format_string: "b", TRUE);
137}
138
139static void
140text_changed (GtkEditableLabel *self)
141{
142 /* Sync the entry text to the label, unless we are editing.
143 *
144 * This is necessary to catch apis like gtk_editable_insert_text(),
145 * which don't go through the text property.
146 */
147 if (!gtk_editable_label_get_editing (self))
148 {
149 const char *text = gtk_editable_get_text (GTK_EDITABLE (self->entry));
150 gtk_label_set_label (GTK_LABEL (self->label), str: text);
151 }
152}
153
154static gboolean
155gtk_editable_label_drag_accept (GtkDropTarget *dest,
156 GdkDrop *drop,
157 GtkEditableLabel *self)
158{
159 if (!gtk_editable_get_editable (GTK_EDITABLE (self)))
160 return FALSE;
161
162 if ((gdk_drop_get_actions (self: drop) & gtk_drop_target_get_actions (self: dest)) == 0)
163 return FALSE;
164
165 return gdk_content_formats_match (first: gtk_drop_target_get_formats (self: dest), second: gdk_drop_get_formats (self: drop));
166}
167
168static gboolean
169gtk_editable_label_drag_drop (GtkDropTarget *dest,
170 const GValue *value,
171 double x,
172 double y,
173 GtkEditableLabel *self)
174{
175 if (!gtk_editable_get_editable (GTK_EDITABLE (self)))
176 return FALSE;
177
178 gtk_editable_set_text (GTK_EDITABLE (self), text: g_value_get_string (value));
179
180 return TRUE;
181}
182
183static GdkContentProvider *
184gtk_editable_label_prepare_drag (GtkDragSource *source,
185 double x,
186 double y,
187 GtkEditableLabel *self)
188{
189 if (!gtk_editable_get_editable (GTK_EDITABLE (self)))
190 return NULL;
191
192 return gdk_content_provider_new_typed (G_TYPE_STRING,
193 gtk_label_get_label (GTK_LABEL (self->label)));
194}
195
196static gboolean
197stop_editing_soon (gpointer data)
198{
199 GtkEventController *controller = data;
200 GtkWidget *widget = gtk_event_controller_get_widget (controller);
201
202 if (!gtk_event_controller_focus_contains_focus (GTK_EVENT_CONTROLLER_FOCUS (controller)))
203 gtk_editable_label_stop_editing (self: GTK_EDITABLE_LABEL (ptr: widget), TRUE);
204
205 return FALSE;
206}
207
208static void
209gtk_editable_label_focus_out (GtkEventController *controller,
210 GtkEditableLabel *self)
211{
212 g_timeout_add (interval: 100, function: stop_editing_soon, data: controller);
213}
214
215static void
216gtk_editable_label_init (GtkEditableLabel *self)
217{
218 GtkGesture *gesture;
219 GtkDropTarget *target;
220 GtkDragSource *source;
221 GtkEventController *controller;
222
223 gtk_widget_set_focusable (GTK_WIDGET (self), TRUE);
224
225 self->stack = gtk_stack_new ();
226 self->label = gtk_label_new (str: "");
227 gtk_label_set_xalign (GTK_LABEL (self->label), xalign: 0.0);
228 self->entry = gtk_text_new ();
229
230 gtk_stack_add_named (GTK_STACK (self->stack), child: self->label, name: "label");
231 gtk_stack_add_named (GTK_STACK (self->stack), child: self->entry, name: "entry");
232
233 gtk_widget_set_parent (widget: self->stack, GTK_WIDGET (self));
234
235 gesture = gtk_gesture_click_new ();
236 g_signal_connect_swapped (gesture, "released", G_CALLBACK (clicked_cb), self);
237 gtk_widget_add_controller (widget: self->label, GTK_EVENT_CONTROLLER (gesture));
238
239 g_signal_connect_swapped (self->entry, "activate", G_CALLBACK (activate_cb), self);
240 g_signal_connect_swapped (self->entry, "notify::text", G_CALLBACK (text_changed), self);
241
242 target = gtk_drop_target_new (G_TYPE_STRING, actions: GDK_ACTION_COPY | GDK_ACTION_MOVE);
243 g_signal_connect (target, "accept", G_CALLBACK (gtk_editable_label_drag_accept), self);
244 g_signal_connect (target, "drop", G_CALLBACK (gtk_editable_label_drag_drop), self);
245 gtk_widget_add_controller (widget: self->label, GTK_EVENT_CONTROLLER (target));
246
247 source = gtk_drag_source_new ();
248 g_signal_connect (source, "prepare", G_CALLBACK (gtk_editable_label_prepare_drag), self);
249 gtk_widget_add_controller (widget: self->label, GTK_EVENT_CONTROLLER (source));
250
251 controller = gtk_event_controller_focus_new ();
252 g_signal_connect (controller, "leave", G_CALLBACK (gtk_editable_label_focus_out), self);
253 gtk_widget_add_controller (GTK_WIDGET (self), controller);
254
255 gtk_editable_init_delegate (GTK_EDITABLE (self));
256}
257
258static gboolean
259gtk_editable_label_grab_focus (GtkWidget *widget)
260{
261 GtkEditableLabel *self = GTK_EDITABLE_LABEL (ptr: widget);
262
263 if (gtk_editable_label_get_editing (self))
264 return gtk_widget_grab_focus (widget: self->entry);
265 else
266 return gtk_widget_grab_focus_self (widget);
267}
268
269static void
270gtk_editable_label_set_property (GObject *object,
271 guint prop_id,
272 const GValue *value,
273 GParamSpec *pspec)
274{
275 GtkEditableLabel *self = GTK_EDITABLE_LABEL (ptr: object);
276
277 if (gtk_editable_delegate_set_property (object, prop_id, value, pspec))
278 {
279 switch (prop_id)
280 {
281 case NUM_PROPERTIES + GTK_EDITABLE_PROP_TEXT:
282 gtk_label_set_label (GTK_LABEL (self->label), str: g_value_get_string (value));
283 break;
284
285 case NUM_PROPERTIES + GTK_EDITABLE_PROP_WIDTH_CHARS:
286 gtk_label_set_width_chars (GTK_LABEL (self->label), n_chars: g_value_get_int (value));
287 break;
288
289 case NUM_PROPERTIES + GTK_EDITABLE_PROP_MAX_WIDTH_CHARS:
290 gtk_label_set_max_width_chars (GTK_LABEL (self->label), n_chars: g_value_get_int (value));
291 break;
292
293 case NUM_PROPERTIES + GTK_EDITABLE_PROP_XALIGN:
294 gtk_label_set_xalign (GTK_LABEL (self->label), xalign: g_value_get_float (value));
295 break;
296
297 case NUM_PROPERTIES + GTK_EDITABLE_PROP_EDITABLE:
298 {
299 gboolean editable;
300
301 editable = g_value_get_boolean (value);
302 if (!editable)
303 gtk_editable_label_stop_editing (self, FALSE);
304
305 gtk_widget_action_set_enabled (GTK_WIDGET (self), action_name: "editing.start", enabled: editable);
306 gtk_widget_action_set_enabled (GTK_WIDGET (self), action_name: "editing.stop", enabled: editable);
307 }
308 break;
309
310 default: ;
311 }
312 return;
313 }
314
315 switch (prop_id)
316 {
317 default:
318 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
319 break;
320 }
321}
322
323static void
324gtk_editable_label_get_property (GObject *object,
325 guint prop_id,
326 GValue *value,
327 GParamSpec *pspec)
328{
329 GtkEditableLabel *self = GTK_EDITABLE_LABEL (ptr: object);
330
331 if (gtk_editable_delegate_get_property (object, prop_id, value, pspec))
332 return;
333
334 switch (prop_id)
335 {
336 case PROP_EDITING:
337 g_value_set_boolean (value, v_boolean: gtk_editable_label_get_editing (self));
338 break;
339
340 default:
341 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
342 break;
343 }
344}
345
346static void
347gtk_editable_label_dispose (GObject *object)
348{
349 GtkEditableLabel *self = GTK_EDITABLE_LABEL (ptr: object);
350
351 gtk_editable_finish_delegate (GTK_EDITABLE (self));
352
353 g_clear_pointer (&self->stack, gtk_widget_unparent);
354
355 self->entry = NULL;
356 self->label = NULL;
357
358 G_OBJECT_CLASS (gtk_editable_label_parent_class)->dispose (object);
359}
360
361static void
362gtk_editable_label_class_init (GtkEditableLabelClass *class)
363{
364 GObjectClass *object_class = G_OBJECT_CLASS (class);
365 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
366 GtkShortcut *shortcut;
367 GtkShortcutTrigger *trigger;
368 GtkShortcutAction *action;
369
370 object_class->set_property = gtk_editable_label_set_property;
371 object_class->get_property = gtk_editable_label_get_property;
372 object_class->dispose = gtk_editable_label_dispose;
373
374 widget_class->grab_focus = gtk_editable_label_grab_focus;
375
376 /**
377 * GtkEditableLabel:editing: (attributes org.gtk.Property.get=gtk_editable_label_get_editing)
378 *
379 * This property is %TRUE while the widget is in edit mode.
380 */
381 properties[PROP_EDITING] =
382 g_param_spec_boolean (name: "editing",
383 P_("Editing"),
384 P_("Whether the widget is in editing mode"),
385 FALSE,
386 GTK_PARAM_READABLE);
387
388 g_object_class_install_properties (oclass: object_class, n_pspecs: NUM_PROPERTIES, pspecs: properties);
389
390 gtk_editable_install_properties (object_class, first_prop: NUM_PROPERTIES);
391
392 /**
393 * GtkEditableLabel|editing.start:
394 *
395 * Switch the widget into editing mode, so that the
396 * user can make changes to the text.
397 *
398 * The default bindings for this action are clicking
399 * on the widget and the Enter key.
400 *
401 * This action is disabled when `GtkEditableLabel:editing`
402 * is %FALSE.
403 */
404 gtk_widget_class_install_action (widget_class, action_name: "editing.start", NULL, activate: start_editing);
405
406 /**
407 * GtkEditableLabel|editing.stop:
408 * @commit: Whether the make changes permanent
409 *
410 * Switch the widget out of editing mode. If @commit
411 * is %TRUE, then the results of the editing are taken
412 * as the new value of `GtkEditable:text`.
413 *
414 * The default binding for this action is the Escape
415 * key.
416 *
417 * This action is disabled when `GtkEditableLabel:editing`
418 * is %FALSE.
419 */
420 gtk_widget_class_install_action (widget_class, action_name: "editing.stop", parameter_type: "b", activate: stop_editing);
421
422 trigger = gtk_alternative_trigger_new (
423 first: gtk_alternative_trigger_new (
424 first: gtk_keyval_trigger_new (GDK_KEY_Return, modifiers: 0),
425 second: gtk_keyval_trigger_new (GDK_KEY_ISO_Enter, modifiers: 0)),
426 second: gtk_keyval_trigger_new (GDK_KEY_KP_Enter, modifiers: 0));
427 action = gtk_named_action_new (name: "editing.start");
428 shortcut = gtk_shortcut_new (trigger, action);
429 gtk_widget_class_add_shortcut (widget_class, shortcut);
430 g_object_unref (object: shortcut);
431
432 gtk_widget_class_add_binding_action (widget_class,
433 GDK_KEY_Escape, mods: 0,
434 action_name: "editing.stop",
435 format_string: "b", FALSE);
436
437 gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
438 gtk_widget_class_set_css_name (widget_class, name: "editablelabel");
439}
440
441/**
442 * gtk_editable_label_new:
443 * @str: the text for the label
444 *
445 * Creates a new `GtkEditableLabel` widget.
446 *
447 * Returns: the new `GtkEditableLabel`
448 */
449GtkWidget *
450gtk_editable_label_new (const char *str)
451{
452 return g_object_new (GTK_TYPE_EDITABLE_LABEL,
453 first_property_name: "text", str,
454 NULL);
455}
456
457/**
458 * gtk_editable_label_get_editing: (attributes org.gtk.Method.get_property=editing)
459 * @self: a `GtkEditableLabel`
460 *
461 * Returns whether the label is currently in “editing mode”.
462 *
463 * Returns: %TRUE if @self is currently in editing mode
464 */
465gboolean
466gtk_editable_label_get_editing (GtkEditableLabel *self)
467{
468 g_return_val_if_fail (GTK_IS_EDITABLE_LABEL (self), FALSE);
469
470 return gtk_stack_get_visible_child (GTK_STACK (self->stack)) == self->entry;
471}
472
473/**
474 * gtk_editable_label_start_editing:
475 * @self: a `GtkEditableLabel`
476 *
477 * Switches the label into “editing mode”.
478 */
479void
480gtk_editable_label_start_editing (GtkEditableLabel *self)
481{
482 g_return_if_fail (GTK_IS_EDITABLE_LABEL (self));
483
484 if (gtk_editable_label_get_editing (self))
485 return;
486
487 gtk_stack_set_visible_child_name (GTK_STACK (self->stack), name: "entry");
488 gtk_widget_grab_focus (widget: self->entry);
489
490 gtk_widget_add_css_class (GTK_WIDGET (self), css_class: "editing");
491
492 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_EDITING]);
493}
494
495/**
496 * gtk_editable_label_stop_editing:
497 * @self: a `GtkEditableLabel`
498 * @commit: whether to set the edited text on the label
499 *
500 * Switches the label out of “editing mode”.
501 *
502 * If @commit is %TRUE, the resulting text is kept as the
503 * [property@Gtk.Editable:text] property value, otherwise the
504 * resulting text is discarded and the label will keep its
505 * previous [property@Gtk.Editable:text] property value.
506 */
507void
508gtk_editable_label_stop_editing (GtkEditableLabel *self,
509 gboolean commit)
510{
511 g_return_if_fail (GTK_IS_EDITABLE_LABEL (self));
512
513 if (!gtk_editable_label_get_editing (self))
514 return;
515
516 if (commit)
517 {
518 gtk_label_set_label (GTK_LABEL (self->label),
519 str: gtk_editable_get_text (GTK_EDITABLE (self->entry)));
520 gtk_stack_set_visible_child_name (GTK_STACK (self->stack), name: "label");
521 }
522 else
523 {
524 gtk_stack_set_visible_child_name (GTK_STACK (self->stack), name: "label");
525 gtk_editable_set_text (GTK_EDITABLE (self->entry),
526 text: gtk_label_get_label (GTK_LABEL (self->label)));
527 }
528
529 gtk_widget_grab_focus (GTK_WIDGET (self));
530
531 gtk_widget_remove_css_class (GTK_WIDGET (self), css_class: "editing");
532
533 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_EDITING]);
534}
535

source code of gtk/gtk/gtkeditablelabel.c