Set the expected accessible role on the tagged entry widget in the demo of the same name. Accessible tools may decide to ignore widgets that have the wrong role, so setting an appropriate role is important.
439 lines
13 KiB
C
439 lines
13 KiB
C
/* GTK - The GIMP Toolkit
|
|
* Copyright (C) 2019 Red Hat, Inc.
|
|
*
|
|
* Authors:
|
|
* - Matthias Clasen <mclasen@redhat.com>
|
|
*
|
|
* 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 "demotaggedentry.h"
|
|
|
|
#include <gtk/gtk.h>
|
|
|
|
struct _DemoTaggedEntry
|
|
{
|
|
GtkWidget parent_instance;
|
|
|
|
GtkWidget *text;
|
|
};
|
|
|
|
struct _DemoTaggedEntryClass
|
|
{
|
|
GtkWidgetClass parent_class;
|
|
};
|
|
|
|
static void demo_tagged_entry_editable_init (GtkEditableInterface *iface);
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (DemoTaggedEntry, demo_tagged_entry, GTK_TYPE_WIDGET,
|
|
G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE, demo_tagged_entry_editable_init))
|
|
|
|
static void
|
|
demo_tagged_entry_init (DemoTaggedEntry *entry)
|
|
{
|
|
entry->text = gtk_text_new ();
|
|
gtk_widget_set_hexpand (entry->text, TRUE);
|
|
gtk_widget_set_vexpand (entry->text, TRUE);
|
|
gtk_widget_set_parent (entry->text, GTK_WIDGET (entry));
|
|
gtk_editable_init_delegate (GTK_EDITABLE (entry));
|
|
}
|
|
|
|
static void
|
|
demo_tagged_entry_dispose (GObject *object)
|
|
{
|
|
DemoTaggedEntry *entry = DEMO_TAGGED_ENTRY (object);
|
|
GtkWidget *child;
|
|
|
|
if (entry->text)
|
|
gtk_editable_finish_delegate (GTK_EDITABLE (entry));
|
|
|
|
while ((child = gtk_widget_get_first_child (GTK_WIDGET (entry))))
|
|
gtk_widget_unparent (child);
|
|
|
|
entry->text = NULL;
|
|
|
|
G_OBJECT_CLASS (demo_tagged_entry_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
demo_tagged_entry_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
if (gtk_editable_delegate_set_property (object, prop_id, value, pspec))
|
|
return;
|
|
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
}
|
|
|
|
static void
|
|
demo_tagged_entry_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
if (gtk_editable_delegate_get_property (object, prop_id, value, pspec))
|
|
return;
|
|
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
}
|
|
|
|
static gboolean
|
|
demo_tagged_entry_grab_focus (GtkWidget *widget)
|
|
{
|
|
DemoTaggedEntry *entry = DEMO_TAGGED_ENTRY (widget);
|
|
|
|
return gtk_widget_grab_focus (entry->text);
|
|
}
|
|
|
|
static void
|
|
demo_tagged_entry_class_init (DemoTaggedEntryClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
|
|
|
object_class->dispose = demo_tagged_entry_dispose;
|
|
object_class->get_property = demo_tagged_entry_get_property;
|
|
object_class->set_property = demo_tagged_entry_set_property;
|
|
|
|
widget_class->grab_focus = demo_tagged_entry_grab_focus;
|
|
|
|
gtk_editable_install_properties (object_class, 1);
|
|
|
|
gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BOX_LAYOUT);
|
|
gtk_widget_class_set_css_name (widget_class, "entry");
|
|
gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_TEXT_BOX);
|
|
}
|
|
|
|
static GtkEditable *
|
|
demo_tagged_entry_get_delegate (GtkEditable *editable)
|
|
{
|
|
return GTK_EDITABLE (DEMO_TAGGED_ENTRY (editable)->text);
|
|
}
|
|
|
|
static void
|
|
demo_tagged_entry_editable_init (GtkEditableInterface *iface)
|
|
{
|
|
iface->get_delegate = demo_tagged_entry_get_delegate;
|
|
}
|
|
|
|
GtkWidget *
|
|
demo_tagged_entry_new (void)
|
|
{
|
|
return GTK_WIDGET (g_object_new (DEMO_TYPE_TAGGED_ENTRY, NULL));
|
|
}
|
|
|
|
void
|
|
demo_tagged_entry_add_tag (DemoTaggedEntry *entry,
|
|
GtkWidget *tag)
|
|
{
|
|
g_return_if_fail (DEMO_IS_TAGGED_ENTRY (entry));
|
|
|
|
gtk_widget_set_parent (tag, GTK_WIDGET (entry));
|
|
}
|
|
|
|
void
|
|
demo_tagged_entry_insert_tag_after (DemoTaggedEntry *entry,
|
|
GtkWidget *tag,
|
|
GtkWidget *sibling)
|
|
{
|
|
g_return_if_fail (DEMO_IS_TAGGED_ENTRY (entry));
|
|
|
|
gtk_widget_insert_after (tag, GTK_WIDGET (entry), sibling);
|
|
}
|
|
|
|
void
|
|
demo_tagged_entry_remove_tag (DemoTaggedEntry *entry,
|
|
GtkWidget *tag)
|
|
{
|
|
g_return_if_fail (DEMO_IS_TAGGED_ENTRY (entry));
|
|
|
|
gtk_widget_unparent (tag);
|
|
}
|
|
|
|
struct _DemoTaggedEntryTag
|
|
{
|
|
GtkWidget parent;
|
|
|
|
GtkWidget *box;
|
|
GtkWidget *label;
|
|
GtkWidget *button;
|
|
|
|
gboolean has_close_button;
|
|
char *style;
|
|
};
|
|
|
|
struct _DemoTaggedEntryTagClass
|
|
{
|
|
GtkWidgetClass parent_class;
|
|
};
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_LABEL,
|
|
PROP_HAS_CLOSE_BUTTON,
|
|
};
|
|
|
|
enum {
|
|
SIGNAL_CLICKED,
|
|
SIGNAL_BUTTON_CLICKED,
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
static guint signals[LAST_SIGNAL] = { 0, };
|
|
|
|
G_DEFINE_TYPE (DemoTaggedEntryTag, demo_tagged_entry_tag, GTK_TYPE_WIDGET)
|
|
|
|
static void
|
|
on_released (GtkGestureClick *gesture,
|
|
int n_press,
|
|
double x,
|
|
double y,
|
|
DemoTaggedEntryTag *tag)
|
|
{
|
|
g_signal_emit (tag, signals[SIGNAL_CLICKED], 0);
|
|
}
|
|
|
|
static void
|
|
demo_tagged_entry_tag_init (DemoTaggedEntryTag *tag)
|
|
{
|
|
GtkGesture *gesture;
|
|
GtkCssProvider *provider;
|
|
|
|
tag->box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
|
|
gtk_widget_set_parent (tag->box, GTK_WIDGET (tag));
|
|
tag->label = gtk_label_new ("");
|
|
gtk_box_append (GTK_BOX (tag->box), tag->label);
|
|
|
|
gesture = gtk_gesture_click_new ();
|
|
g_signal_connect (gesture, "released", G_CALLBACK (on_released), tag);
|
|
gtk_widget_add_controller (GTK_WIDGET (tag), GTK_EVENT_CONTROLLER (gesture));
|
|
|
|
provider = gtk_css_provider_new ();
|
|
gtk_css_provider_load_from_resource (provider, "/tagged_entry/tagstyle.css");
|
|
gtk_style_context_add_provider_for_display (gdk_display_get_default (),
|
|
GTK_STYLE_PROVIDER (provider),
|
|
800);
|
|
g_object_unref (provider);
|
|
}
|
|
|
|
static void
|
|
demo_tagged_entry_tag_dispose (GObject *object)
|
|
{
|
|
DemoTaggedEntryTag *tag = DEMO_TAGGED_ENTRY_TAG (object);
|
|
|
|
g_clear_pointer (&tag->box, gtk_widget_unparent);
|
|
|
|
G_OBJECT_CLASS (demo_tagged_entry_tag_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
demo_tagged_entry_tag_finalize (GObject *object)
|
|
{
|
|
DemoTaggedEntryTag *tag = DEMO_TAGGED_ENTRY_TAG (object);
|
|
|
|
g_clear_pointer (&tag->box, gtk_widget_unparent);
|
|
g_clear_pointer (&tag->style, g_free);
|
|
|
|
G_OBJECT_CLASS (demo_tagged_entry_tag_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
demo_tagged_entry_tag_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
DemoTaggedEntryTag *tag = DEMO_TAGGED_ENTRY_TAG (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_LABEL:
|
|
demo_tagged_entry_tag_set_label (tag, g_value_get_string (value));
|
|
break;
|
|
|
|
case PROP_HAS_CLOSE_BUTTON:
|
|
demo_tagged_entry_tag_set_has_close_button (tag, g_value_get_boolean (value));
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
demo_tagged_entry_tag_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
DemoTaggedEntryTag *tag = DEMO_TAGGED_ENTRY_TAG (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_LABEL:
|
|
g_value_set_string (value, demo_tagged_entry_tag_get_label (tag));
|
|
break;
|
|
|
|
case PROP_HAS_CLOSE_BUTTON:
|
|
g_value_set_boolean (value, demo_tagged_entry_tag_get_has_close_button (tag));
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
demo_tagged_entry_tag_measure (GtkWidget *widget,
|
|
GtkOrientation orientation,
|
|
int for_size,
|
|
int *minimum,
|
|
int *natural,
|
|
int *minimum_baseline,
|
|
int *natural_baseline)
|
|
{
|
|
DemoTaggedEntryTag *tag = DEMO_TAGGED_ENTRY_TAG (widget);
|
|
|
|
gtk_widget_measure (tag->box, orientation, for_size,
|
|
minimum, natural,
|
|
minimum_baseline, natural_baseline);
|
|
}
|
|
|
|
static void
|
|
demo_tagged_entry_tag_size_allocate (GtkWidget *widget,
|
|
int width,
|
|
int height,
|
|
int baseline)
|
|
{
|
|
DemoTaggedEntryTag *tag = DEMO_TAGGED_ENTRY_TAG (widget);
|
|
|
|
gtk_widget_size_allocate (tag->box,
|
|
&(GtkAllocation) { 0, 0, width, height },
|
|
baseline);
|
|
}
|
|
|
|
static void
|
|
demo_tagged_entry_tag_class_init (DemoTaggedEntryTagClass *class)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (class);
|
|
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
|
|
|
|
object_class->dispose = demo_tagged_entry_tag_dispose;
|
|
object_class->finalize = demo_tagged_entry_tag_finalize;
|
|
object_class->set_property = demo_tagged_entry_tag_set_property;
|
|
object_class->get_property = demo_tagged_entry_tag_get_property;
|
|
|
|
widget_class->measure = demo_tagged_entry_tag_measure;
|
|
widget_class->size_allocate = demo_tagged_entry_tag_size_allocate;
|
|
|
|
signals[SIGNAL_CLICKED] =
|
|
g_signal_new ("clicked",
|
|
DEMO_TYPE_TAGGED_ENTRY_TAG,
|
|
G_SIGNAL_RUN_FIRST,
|
|
0, NULL, NULL, NULL,
|
|
G_TYPE_NONE, 0);
|
|
signals[SIGNAL_BUTTON_CLICKED] =
|
|
g_signal_new ("button-clicked",
|
|
DEMO_TYPE_TAGGED_ENTRY_TAG,
|
|
G_SIGNAL_RUN_FIRST,
|
|
0, NULL, NULL, NULL,
|
|
G_TYPE_NONE, 0);
|
|
|
|
g_object_class_install_property (object_class, PROP_LABEL,
|
|
g_param_spec_string ("label", "Label", "Label",
|
|
NULL, G_PARAM_READWRITE));
|
|
g_object_class_install_property (object_class, PROP_HAS_CLOSE_BUTTON,
|
|
g_param_spec_boolean ("has-close-button", "Has close button", "Whether this tag has a close button",
|
|
FALSE, G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
|
|
|
|
gtk_widget_class_set_css_name (widget_class, "tag");
|
|
}
|
|
|
|
DemoTaggedEntryTag *
|
|
demo_tagged_entry_tag_new (const char *label)
|
|
{
|
|
return DEMO_TAGGED_ENTRY_TAG (g_object_new (DEMO_TYPE_TAGGED_ENTRY_TAG,
|
|
"label", label,
|
|
NULL));
|
|
}
|
|
|
|
const char *
|
|
demo_tagged_entry_tag_get_label (DemoTaggedEntryTag *tag)
|
|
{
|
|
g_return_val_if_fail (DEMO_IS_TAGGED_ENTRY_TAG (tag), NULL);
|
|
|
|
return gtk_label_get_label (GTK_LABEL (tag->label));
|
|
}
|
|
|
|
void
|
|
demo_tagged_entry_tag_set_label (DemoTaggedEntryTag *tag,
|
|
const char *label)
|
|
{
|
|
g_return_if_fail (DEMO_IS_TAGGED_ENTRY_TAG (tag));
|
|
|
|
gtk_label_set_label (GTK_LABEL (tag->label), label);
|
|
}
|
|
|
|
static void
|
|
on_button_clicked (GtkButton *button,
|
|
DemoTaggedEntryTag *tag)
|
|
{
|
|
g_signal_emit (tag, signals[SIGNAL_BUTTON_CLICKED], 0);
|
|
}
|
|
|
|
void
|
|
demo_tagged_entry_tag_set_has_close_button (DemoTaggedEntryTag *tag,
|
|
gboolean has_close_button)
|
|
{
|
|
g_return_if_fail (DEMO_IS_TAGGED_ENTRY_TAG (tag));
|
|
|
|
if ((tag->button != NULL) == has_close_button)
|
|
return;
|
|
|
|
if (!has_close_button && tag->button)
|
|
{
|
|
gtk_box_remove (GTK_BOX (tag->box), tag->button);
|
|
tag->button = NULL;
|
|
}
|
|
else if (has_close_button && tag->button == NULL)
|
|
{
|
|
GtkWidget *image;
|
|
|
|
image = gtk_image_new_from_icon_name ("window-close-symbolic");
|
|
tag->button = gtk_button_new ();
|
|
gtk_button_set_child (GTK_BUTTON (tag->button), image);
|
|
gtk_widget_set_halign (tag->button, GTK_ALIGN_CENTER);
|
|
gtk_widget_set_valign (tag->button, GTK_ALIGN_CENTER);
|
|
gtk_button_set_has_frame (GTK_BUTTON (tag->button), FALSE);
|
|
gtk_box_append (GTK_BOX (tag->box), tag->button);
|
|
g_signal_connect (tag->button, "clicked", G_CALLBACK (on_button_clicked), tag);
|
|
}
|
|
|
|
g_object_notify (G_OBJECT (tag), "has-close-button");
|
|
}
|
|
|
|
gboolean
|
|
demo_tagged_entry_tag_get_has_close_button (DemoTaggedEntryTag *tag)
|
|
{
|
|
g_return_val_if_fail (DEMO_IS_TAGGED_ENTRY_TAG (tag), FALSE);
|
|
|
|
return tag->button != NULL;
|
|
}
|