gtk2/gtk/gtkeditablelabel.c
Sophie Herold a546ae32d7 Remove all nicks and blurbs from param specs
Those property features don't seem to be in use anywhere.
They are redundant since the docs cover the same information
and more. They also created unnecessary translation work.

Closes #4904
2022-05-11 18:16:29 +02:00

528 lines
16 KiB
C

/* GTK - The GIMP Toolkit
* Copyright (C) 2020, Red Hat, Inc
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "gtkdragsource.h"
#include "gtkdroptarget.h"
#include "gtkeditablelabel.h"
#include "gtkeditable.h"
#include "gtklabel.h"
#include "gtkstack.h"
#include "gtktext.h"
#include "gtkbinlayout.h"
#include "gtkgestureclick.h"
#include "gtkprivate.h"
#include "gtkshortcut.h"
#include "gtkshortcuttrigger.h"
#include "gtkwidgetprivate.h"
#include "gtkeventcontrollerfocus.h"
#include "gtkintl.h"
/**
* GtkEditableLabel:
*
* A `GtkEditableLabel` is a label that allows users to
* edit the text by switching to an “edit mode”.
*
* ![An example GtkEditableLabel](editable-label.png)
*
* `GtkEditableLabel` does not have API of its own, but it
* implements the [iface@Gtk.Editable] interface.
*
* The default bindings for activating the edit mode is
* to click or press the Enter key. The default bindings
* for leaving the edit mode are the Enter key (to save
* the results) or the Escape key (to cancel the editing).
*
* # CSS nodes
*
* ```
* editablelabel[.editing]
* ╰── stack
* ├── label
* ╰── text
* ```
*
* `GtkEditableLabel` has a main node with the name editablelabel.
* When the entry is in editing mode, it gets the .editing style
* class.
*
* For all the subnodes added to the text node in various situations,
* see [class@Gtk.Text].
*/
struct _GtkEditableLabel
{
GtkWidget parent_instance;
GtkWidget *stack;
GtkWidget *label;
GtkWidget *entry;
};
struct _GtkEditableLabelClass
{
GtkWidgetClass parent_class;
};
enum
{
PROP_EDITING = 1,
NUM_PROPERTIES
};
static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
static GtkEditable *
gtk_editable_label_get_delegate (GtkEditable *editable)
{
GtkEditableLabel *self = GTK_EDITABLE_LABEL (editable);
return GTK_EDITABLE (self->entry);
}
static void
gtk_editable_label_editable_init (GtkEditableInterface *iface)
{
iface->get_delegate = gtk_editable_label_get_delegate;
}
G_DEFINE_TYPE_WITH_CODE (GtkEditableLabel, gtk_editable_label, GTK_TYPE_WIDGET,
G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE,
gtk_editable_label_editable_init))
static void
start_editing (GtkWidget *widget,
const char *action_name,
GVariant *parameters)
{
gtk_editable_label_start_editing (GTK_EDITABLE_LABEL (widget));
}
static void
stop_editing (GtkWidget *widget,
const char *action_name,
GVariant *parameters)
{
gtk_editable_label_stop_editing (GTK_EDITABLE_LABEL (widget),
g_variant_get_boolean (parameters));
}
static void
clicked_cb (GtkWidget *self)
{
gtk_widget_activate_action (self, "editing.start", NULL);
}
static void
activate_cb (GtkWidget *self)
{
gtk_widget_activate_action (self, "editing.stop", "b", TRUE);
}
static void
text_changed (GtkEditableLabel *self)
{
/* Sync the entry text to the label, unless we are editing.
*
* This is necessary to catch apis like gtk_editable_insert_text(),
* which don't go through the text property.
*/
if (!gtk_editable_label_get_editing (self))
{
const char *text = gtk_editable_get_text (GTK_EDITABLE (self->entry));
gtk_label_set_label (GTK_LABEL (self->label), text);
}
}
static gboolean
gtk_editable_label_drag_accept (GtkDropTarget *dest,
GdkDrop *drop,
GtkEditableLabel *self)
{
if (!gtk_editable_get_editable (GTK_EDITABLE (self)))
return FALSE;
if ((gdk_drop_get_actions (drop) & gtk_drop_target_get_actions (dest)) == 0)
return FALSE;
return gdk_content_formats_match (gtk_drop_target_get_formats (dest), gdk_drop_get_formats (drop));
}
static gboolean
gtk_editable_label_drag_drop (GtkDropTarget *dest,
const GValue *value,
double x,
double y,
GtkEditableLabel *self)
{
if (!gtk_editable_get_editable (GTK_EDITABLE (self)))
return FALSE;
gtk_editable_set_text (GTK_EDITABLE (self), g_value_get_string (value));
return TRUE;
}
static GdkContentProvider *
gtk_editable_label_prepare_drag (GtkDragSource *source,
double x,
double y,
GtkEditableLabel *self)
{
if (!gtk_editable_get_editable (GTK_EDITABLE (self)))
return NULL;
return gdk_content_provider_new_typed (G_TYPE_STRING,
gtk_label_get_label (GTK_LABEL (self->label)));
}
static void
gtk_editable_label_focus_out (GtkEventController *controller,
GtkEditableLabel *self)
{
gtk_editable_label_stop_editing (self, TRUE);
}
static void
gtk_editable_label_init (GtkEditableLabel *self)
{
GtkGesture *gesture;
GtkDropTarget *target;
GtkDragSource *source;
GtkEventController *controller;
gtk_widget_set_focusable (GTK_WIDGET (self), TRUE);
self->stack = gtk_stack_new ();
self->label = gtk_label_new ("");
gtk_label_set_xalign (GTK_LABEL (self->label), 0.0);
self->entry = gtk_text_new ();
gtk_stack_add_named (GTK_STACK (self->stack), self->label, "label");
gtk_stack_add_named (GTK_STACK (self->stack), self->entry, "entry");
gtk_widget_set_parent (self->stack, GTK_WIDGET (self));
gesture = gtk_gesture_click_new ();
g_signal_connect_swapped (gesture, "released", G_CALLBACK (clicked_cb), self);
gtk_widget_add_controller (self->label, GTK_EVENT_CONTROLLER (gesture));
g_signal_connect_swapped (self->entry, "activate", G_CALLBACK (activate_cb), self);
g_signal_connect_swapped (self->entry, "notify::text", G_CALLBACK (text_changed), self);
target = gtk_drop_target_new (G_TYPE_STRING, GDK_ACTION_COPY | GDK_ACTION_MOVE);
g_signal_connect (target, "accept", G_CALLBACK (gtk_editable_label_drag_accept), self);
g_signal_connect (target, "drop", G_CALLBACK (gtk_editable_label_drag_drop), self);
gtk_widget_add_controller (self->label, GTK_EVENT_CONTROLLER (target));
source = gtk_drag_source_new ();
g_signal_connect (source, "prepare", G_CALLBACK (gtk_editable_label_prepare_drag), self);
gtk_widget_add_controller (self->label, GTK_EVENT_CONTROLLER (source));
controller = gtk_event_controller_focus_new ();
g_signal_connect (controller, "leave", G_CALLBACK (gtk_editable_label_focus_out), self);
gtk_widget_add_controller (GTK_WIDGET (self), controller);
gtk_editable_init_delegate (GTK_EDITABLE (self));
}
static gboolean
gtk_editable_label_grab_focus (GtkWidget *widget)
{
GtkEditableLabel *self = GTK_EDITABLE_LABEL (widget);
if (gtk_editable_label_get_editing (self))
return gtk_widget_grab_focus (self->entry);
else
return gtk_widget_grab_focus_self (widget);
}
static void
gtk_editable_label_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkEditableLabel *self = GTK_EDITABLE_LABEL (object);
if (gtk_editable_delegate_set_property (object, prop_id, value, pspec))
{
switch (prop_id)
{
case NUM_PROPERTIES + GTK_EDITABLE_PROP_TEXT:
gtk_label_set_label (GTK_LABEL (self->label), g_value_get_string (value));
break;
case NUM_PROPERTIES + GTK_EDITABLE_PROP_WIDTH_CHARS:
gtk_label_set_width_chars (GTK_LABEL (self->label), g_value_get_int (value));
break;
case NUM_PROPERTIES + GTK_EDITABLE_PROP_MAX_WIDTH_CHARS:
gtk_label_set_max_width_chars (GTK_LABEL (self->label), g_value_get_int (value));
break;
case NUM_PROPERTIES + GTK_EDITABLE_PROP_XALIGN:
gtk_label_set_xalign (GTK_LABEL (self->label), g_value_get_float (value));
break;
case NUM_PROPERTIES + GTK_EDITABLE_PROP_EDITABLE:
{
gboolean editable;
editable = g_value_get_boolean (value);
if (!editable)
gtk_editable_label_stop_editing (self, FALSE);
gtk_widget_action_set_enabled (GTK_WIDGET (self), "editing.start", editable);
gtk_widget_action_set_enabled (GTK_WIDGET (self), "editing.stop", editable);
}
break;
default: ;
}
return;
}
switch (prop_id)
{
case PROP_EDITING:
if (g_value_get_boolean (value))
gtk_editable_label_start_editing (self);
else
gtk_editable_label_stop_editing (self, FALSE);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_editable_label_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkEditableLabel *self = GTK_EDITABLE_LABEL (object);
if (gtk_editable_delegate_get_property (object, prop_id, value, pspec))
return;
switch (prop_id)
{
case PROP_EDITING:
g_value_set_boolean (value, gtk_editable_label_get_editing (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_editable_label_dispose (GObject *object)
{
GtkEditableLabel *self = GTK_EDITABLE_LABEL (object);
gtk_editable_finish_delegate (GTK_EDITABLE (self));
g_clear_pointer (&self->stack, gtk_widget_unparent);
self->entry = NULL;
self->label = NULL;
G_OBJECT_CLASS (gtk_editable_label_parent_class)->dispose (object);
}
static void
gtk_editable_label_class_init (GtkEditableLabelClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
GtkShortcut *shortcut;
GtkShortcutTrigger *trigger;
GtkShortcutAction *action;
object_class->set_property = gtk_editable_label_set_property;
object_class->get_property = gtk_editable_label_get_property;
object_class->dispose = gtk_editable_label_dispose;
widget_class->grab_focus = gtk_editable_label_grab_focus;
/**
* GtkEditableLabel:editing: (attributes org.gtk.Property.get=gtk_editable_label_get_editing)
*
* This property is %TRUE while the widget is in edit mode.
*/
properties[PROP_EDITING] =
g_param_spec_boolean ("editing", NULL, NULL,
FALSE,
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (object_class, NUM_PROPERTIES, properties);
gtk_editable_install_properties (object_class, NUM_PROPERTIES);
/**
* GtkEditableLabel|editing.start:
*
* Switch the widget into editing mode, so that the
* user can make changes to the text.
*
* The default bindings for this action are clicking
* on the widget and the Enter key.
*
* This action is disabled when `GtkEditableLabel:editing`
* is %FALSE.
*/
gtk_widget_class_install_action (widget_class, "editing.start", NULL, start_editing);
/**
* GtkEditableLabel|editing.stop:
* @commit: Whether the make changes permanent
*
* Switch the widget out of editing mode. If @commit
* is %TRUE, then the results of the editing are taken
* as the new value of `GtkEditable:text`.
*
* The default binding for this action is the Escape
* key.
*
* This action is disabled when `GtkEditableLabel:editing`
* is %FALSE.
*/
gtk_widget_class_install_action (widget_class, "editing.stop", "b", stop_editing);
trigger = gtk_alternative_trigger_new (
gtk_alternative_trigger_new (
gtk_keyval_trigger_new (GDK_KEY_Return, 0),
gtk_keyval_trigger_new (GDK_KEY_ISO_Enter, 0)),
gtk_keyval_trigger_new (GDK_KEY_KP_Enter, 0));
action = gtk_named_action_new ("editing.start");
shortcut = gtk_shortcut_new (trigger, action);
gtk_widget_class_add_shortcut (widget_class, shortcut);
g_object_unref (shortcut);
gtk_widget_class_add_binding_action (widget_class,
GDK_KEY_Escape, 0,
"editing.stop",
"b", FALSE);
gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
gtk_widget_class_set_css_name (widget_class, "editablelabel");
}
/**
* gtk_editable_label_new:
* @str: the text for the label
*
* Creates a new `GtkEditableLabel` widget.
*
* Returns: the new `GtkEditableLabel`
*/
GtkWidget *
gtk_editable_label_new (const char *str)
{
return g_object_new (GTK_TYPE_EDITABLE_LABEL,
"text", str,
NULL);
}
/**
* gtk_editable_label_get_editing: (attributes org.gtk.Method.get_property=editing)
* @self: a `GtkEditableLabel`
*
* Returns whether the label is currently in “editing mode”.
*
* Returns: %TRUE if @self is currently in editing mode
*/
gboolean
gtk_editable_label_get_editing (GtkEditableLabel *self)
{
g_return_val_if_fail (GTK_IS_EDITABLE_LABEL (self), FALSE);
return gtk_stack_get_visible_child (GTK_STACK (self->stack)) == self->entry;
}
/**
* gtk_editable_label_start_editing:
* @self: a `GtkEditableLabel`
*
* Switches the label into “editing mode”.
*/
void
gtk_editable_label_start_editing (GtkEditableLabel *self)
{
g_return_if_fail (GTK_IS_EDITABLE_LABEL (self));
if (gtk_editable_label_get_editing (self))
return;
gtk_stack_set_visible_child_name (GTK_STACK (self->stack), "entry");
gtk_widget_grab_focus (self->entry);
gtk_widget_add_css_class (GTK_WIDGET (self), "editing");
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_EDITING]);
}
/**
* gtk_editable_label_stop_editing:
* @self: a `GtkEditableLabel`
* @commit: whether to set the edited text on the label
*
* Switches the label out of “editing mode”.
*
* If @commit is %TRUE, the resulting text is kept as the
* [property@Gtk.Editable:text] property value, otherwise the
* resulting text is discarded and the label will keep its
* previous [property@Gtk.Editable:text] property value.
*/
void
gtk_editable_label_stop_editing (GtkEditableLabel *self,
gboolean commit)
{
g_return_if_fail (GTK_IS_EDITABLE_LABEL (self));
if (!gtk_editable_label_get_editing (self))
return;
if (commit)
{
gtk_label_set_label (GTK_LABEL (self->label),
gtk_editable_get_text (GTK_EDITABLE (self->entry)));
gtk_stack_set_visible_child_name (GTK_STACK (self->stack), "label");
}
else
{
gtk_stack_set_visible_child_name (GTK_STACK (self->stack), "label");
gtk_editable_set_text (GTK_EDITABLE (self->entry),
gtk_label_get_label (GTK_LABEL (self->label)));
}
gtk_widget_grab_focus (GTK_WIDGET (self));
gtk_widget_remove_css_class (GTK_WIDGET (self), "editing");
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_EDITING]);
}