diff options
Diffstat (limited to 'gtk/gtklabel.c')
-rw-r--r-- | gtk/gtklabel.c | 3878 |
1 files changed, 1824 insertions, 2054 deletions
diff --git a/gtk/gtklabel.c b/gtk/gtklabel.c index 19b0386c05..1c8a31f231 100644 --- a/gtk/gtklabel.c +++ b/gtk/gtklabel.c @@ -407,168 +407,31 @@ static guint signals[LAST_SIGNAL] = { 0 }; static GQuark quark_mnemonics_visible_connected; -static void gtk_label_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec); -static void gtk_label_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec); -static void gtk_label_finalize (GObject *object); -static void gtk_label_dispose (GObject *object); -static void gtk_label_size_allocate (GtkWidget *widget, - int width, - int height, - int baseline); -static void gtk_label_state_flags_changed (GtkWidget *widget, - GtkStateFlags prev_state); -static void gtk_label_css_changed (GtkWidget *widget, - GtkCssStyleChange *change); -static void gtk_label_snapshot (GtkWidget *widget, - GtkSnapshot *snapshot); -static gboolean gtk_label_focus (GtkWidget *widget, - GtkDirectionType direction); - -static void gtk_label_unrealize (GtkWidget *widget); - -static void gtk_label_motion (GtkEventControllerMotion *controller, - double x, - double y, - gpointer data); -static void gtk_label_leave (GtkEventControllerMotion *controller, - gpointer data); - -static gboolean gtk_label_grab_focus (GtkWidget *widget); - -static gboolean gtk_label_query_tooltip (GtkWidget *widget, - int x, - int y, - gboolean keyboard_tip, - GtkTooltip *tooltip); - -static void gtk_label_set_text_internal (GtkLabel *self, - char *str); -static gboolean gtk_label_set_label_internal (GtkLabel *self, - const char *str); -static gboolean gtk_label_set_use_markup_internal (GtkLabel *self, - gboolean val); -static gboolean gtk_label_set_use_underline_internal (GtkLabel *self, - gboolean val); static void gtk_label_set_markup_internal (GtkLabel *self, - const char *str, - gboolean with_uline); + const char *str, + gboolean with_uline); static void gtk_label_recalculate (GtkLabel *self); -static void gtk_label_root (GtkWidget *widget); -static void gtk_label_unroot (GtkWidget *widget); -static void gtk_label_popup_menu (GtkWidget *widget, - const char *action_name, - GVariant *parameters); static void gtk_label_do_popup (GtkLabel *self, double x, double y); - static void gtk_label_ensure_select_info (GtkLabel *self); static void gtk_label_clear_select_info (GtkLabel *self); -static void gtk_label_update_cursor (GtkLabel *self); static void gtk_label_clear_layout (GtkLabel *self); static void gtk_label_ensure_layout (GtkLabel *self); static void gtk_label_select_region_index (GtkLabel *self, int anchor_index, int end_index); - static void gtk_label_update_active_link (GtkWidget *widget, double x, double y); - -static gboolean gtk_label_mnemonic_activate (GtkWidget *widget, - gboolean group_cycling); static void gtk_label_setup_mnemonic (GtkLabel *self); static void gtk_label_buildable_interface_init (GtkBuildableIface *iface); -static gboolean gtk_label_buildable_custom_tag_start (GtkBuildable *buildable, - GtkBuilder *builder, - GObject *child, - const char *tagname, - GtkBuildableParser *parser, - gpointer *data); - -static void gtk_label_buildable_custom_finished (GtkBuildable *buildable, - GtkBuilder *builder, - GObject *child, - const char *tagname, - gpointer user_data); - /* For selectable labels: */ static void gtk_label_move_cursor (GtkLabel *self, - GtkMovementStep step, - int count, - gboolean extend_selection); -static void gtk_label_copy_clipboard (GtkLabel *self); -static void gtk_label_select_all (GtkLabel *self); -static int gtk_label_move_forward_word (GtkLabel *self, - int start); -static int gtk_label_move_backward_word (GtkLabel *self, - int start); - -/* For links: */ -static void gtk_label_clear_links (GtkLabel *self); -static gboolean gtk_label_activate_link (GtkLabel *self, - const char *uri); -static void gtk_label_activate_current_link (GtkLabel *self); -static void emit_activate_link (GtkLabel *self, - GtkLabelLink *link); - -/* Event controller callbacks */ -static void gtk_label_click_gesture_pressed (GtkGestureClick *gesture, - int n_press, - double x, - double y, - GtkLabel *self); -static void gtk_label_click_gesture_released (GtkGestureClick *gesture, - int n_press, - double x, - double y, - GtkLabel *self); -static void gtk_label_drag_gesture_begin (GtkGestureDrag *gesture, - double start_x, - double start_y, - GtkLabel *self); -static void gtk_label_drag_gesture_update (GtkGestureDrag *gesture, - double offset_x, - double offset_y, - GtkLabel *self); - -/* Actions */ - -static void gtk_label_activate_clipboard_copy (GtkWidget *self, - const char *name, - GVariant *parameter); -static void gtk_label_activate_selection_select_all (GtkWidget *self, - const char *name, - GVariant *parameter); -static void gtk_label_activate_link_open (GtkWidget *self, - const char *name, - GVariant *parameter); -static void gtk_label_activate_link_copy (GtkWidget *self, - const char *name, - GVariant *parameter); -static void gtk_label_nop (GtkWidget *self, - const char *name, - GVariant *parameter); - -static void gtk_label_update_actions (GtkLabel *self); - -static GtkSizeRequestMode gtk_label_get_request_mode (GtkWidget *widget); -static void gtk_label_measure (GtkWidget *widget, - GtkOrientation orientation, - int for_size, - int *minimum, - int *natural, - int *minimum_baseline, - int *natural_baseline); - - + GtkMovementStep step, + int count, + gboolean extend_selection); static GtkBuildableIface *buildable_parent_iface = NULL; @@ -578,26 +441,1627 @@ G_DEFINE_TYPE_WITH_CODE (GtkLabel, gtk_label, GTK_TYPE_WIDGET, static void add_move_binding (GtkWidgetClass *widget_class, - guint keyval, - guint modmask, - GtkMovementStep step, - int count) + guint keyval, + guint modmask, + GtkMovementStep step, + int count) { g_return_if_fail ((modmask & GDK_SHIFT_MASK) == 0); - + gtk_widget_class_add_binding_signal (widget_class, keyval, modmask, - "move-cursor", + "move-cursor", "(iib)", step, count, FALSE); /* Selection-extending version */ gtk_widget_class_add_binding_signal (widget_class, keyval, modmask | GDK_SHIFT_MASK, - "move-cursor", + "move-cursor", "(iib)", step, count, TRUE); } static void +gtk_label_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkLabel *self = GTK_LABEL (object); + + switch (prop_id) + { + case PROP_LABEL: + gtk_label_set_label (self, g_value_get_string (value)); + break; + case PROP_ATTRIBUTES: + gtk_label_set_attributes (self, g_value_get_boxed (value)); + break; + case PROP_USE_MARKUP: + gtk_label_set_use_markup (self, g_value_get_boolean (value)); + break; + case PROP_USE_UNDERLINE: + gtk_label_set_use_underline (self, g_value_get_boolean (value)); + break; + case PROP_JUSTIFY: + gtk_label_set_justify (self, g_value_get_enum (value)); + break; + case PROP_WRAP: + gtk_label_set_wrap (self, g_value_get_boolean (value)); + break; + case PROP_WRAP_MODE: + gtk_label_set_wrap_mode (self, g_value_get_enum (value)); + break; + case PROP_SELECTABLE: + gtk_label_set_selectable (self, g_value_get_boolean (value)); + break; + case PROP_MNEMONIC_WIDGET: + gtk_label_set_mnemonic_widget (self, (GtkWidget*) g_value_get_object (value)); + break; + case PROP_ELLIPSIZE: + gtk_label_set_ellipsize (self, g_value_get_enum (value)); + break; + case PROP_WIDTH_CHARS: + gtk_label_set_width_chars (self, g_value_get_int (value)); + break; + case PROP_SINGLE_LINE_MODE: + gtk_label_set_single_line_mode (self, g_value_get_boolean (value)); + break; + case PROP_MAX_WIDTH_CHARS: + gtk_label_set_max_width_chars (self, g_value_get_int (value)); + break; + case PROP_LINES: + gtk_label_set_lines (self, g_value_get_int (value)); + break; + case PROP_XALIGN: + gtk_label_set_xalign (self, g_value_get_float (value)); + break; + case PROP_YALIGN: + gtk_label_set_yalign (self, g_value_get_float (value)); + break; + case PROP_EXTRA_MENU: + gtk_label_set_extra_menu (self, g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_label_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkLabel *self = GTK_LABEL (object); + + switch (prop_id) + { + case PROP_LABEL: + g_value_set_string (value, self->label); + break; + case PROP_ATTRIBUTES: + g_value_set_boxed (value, self->attrs); + break; + case PROP_USE_MARKUP: + g_value_set_boolean (value, self->use_markup); + break; + case PROP_USE_UNDERLINE: + g_value_set_boolean (value, self->use_underline); + break; + case PROP_JUSTIFY: + g_value_set_enum (value, self->jtype); + break; + case PROP_WRAP: + g_value_set_boolean (value, self->wrap); + break; + case PROP_WRAP_MODE: + g_value_set_enum (value, self->wrap_mode); + break; + case PROP_SELECTABLE: + g_value_set_boolean (value, gtk_label_get_selectable (self)); + break; + case PROP_MNEMONIC_KEYVAL: + g_value_set_uint (value, self->mnemonic_keyval); + break; + case PROP_MNEMONIC_WIDGET: + g_value_set_object (value, (GObject*) self->mnemonic_widget); + break; + case PROP_ELLIPSIZE: + g_value_set_enum (value, self->ellipsize); + break; + case PROP_WIDTH_CHARS: + g_value_set_int (value, gtk_label_get_width_chars (self)); + break; + case PROP_SINGLE_LINE_MODE: + g_value_set_boolean (value, gtk_label_get_single_line_mode (self)); + break; + case PROP_MAX_WIDTH_CHARS: + g_value_set_int (value, gtk_label_get_max_width_chars (self)); + break; + case PROP_LINES: + g_value_set_int (value, gtk_label_get_lines (self)); + break; + case PROP_XALIGN: + g_value_set_float (value, gtk_label_get_xalign (self)); + break; + case PROP_YALIGN: + g_value_set_float (value, gtk_label_get_yalign (self)); + break; + case PROP_EXTRA_MENU: + g_value_set_object (value, gtk_label_get_extra_menu (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_label_init (GtkLabel *self) +{ + self->width_chars = -1; + self->max_width_chars = -1; + self->label = g_strdup (""); + self->lines = -1; + + self->xalign = 0.5; + self->yalign = 0.5; + + self->jtype = GTK_JUSTIFY_LEFT; + self->wrap = FALSE; + self->wrap_mode = PANGO_WRAP_WORD; + self->ellipsize = PANGO_ELLIPSIZE_NONE; + + self->use_underline = FALSE; + self->use_markup = FALSE; + + self->mnemonic_keyval = GDK_KEY_VoidSymbol; + self->layout = NULL; + self->text = g_strdup (""); + self->attrs = NULL; + + self->mnemonic_widget = NULL; + + self->mnemonics_visible = FALSE; +} + +static const GtkBuildableParser pango_parser = +{ + gtk_pango_attribute_start_element, +}; + +static gboolean +gtk_label_buildable_custom_tag_start (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *child, + const char *tagname, + GtkBuildableParser *parser, + gpointer *data) +{ + if (buildable_parent_iface->custom_tag_start (buildable, builder, child, + tagname, parser, data)) + return TRUE; + + if (strcmp (tagname, "attributes") == 0) + { + GtkPangoAttributeParserData *parser_data; + + parser_data = g_slice_new0 (GtkPangoAttributeParserData); + parser_data->builder = g_object_ref (builder); + parser_data->object = (GObject *) g_object_ref (buildable); + *parser = pango_parser; + *data = parser_data; + return TRUE; + } + return FALSE; +} + +static void +gtk_label_buildable_custom_finished (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *child, + const char *tagname, + gpointer user_data) +{ + GtkPangoAttributeParserData *data = user_data; + + buildable_parent_iface->custom_finished (buildable, builder, child, + tagname, user_data); + + if (strcmp (tagname, "attributes") == 0) + { + if (data->attrs) + { + gtk_label_set_attributes (GTK_LABEL (buildable), data->attrs); + pango_attr_list_unref (data->attrs); + } + + g_object_unref (data->object); + g_object_unref (data->builder); + g_slice_free (GtkPangoAttributeParserData, data); + } +} + +static void +gtk_label_buildable_interface_init (GtkBuildableIface *iface) +{ + buildable_parent_iface = g_type_interface_peek_parent (iface); + + iface->custom_tag_start = gtk_label_buildable_custom_tag_start; + iface->custom_finished = gtk_label_buildable_custom_finished; +} + +static void +update_link_state (GtkLabel *self) +{ + GtkStateFlags state; + guint i; + + if (!self->select_info) + return; + + for (i = 0; i < self->select_info->n_links; i++) + { + const GtkLabelLink *link = &self->select_info->links[i]; + + state = gtk_widget_get_state_flags (GTK_WIDGET (self)); + if (link->visited) + state |= GTK_STATE_FLAG_VISITED; + else + state |= GTK_STATE_FLAG_LINK; + if (link == self->select_info->active_link) + { + if (self->select_info->link_clicked) + state |= GTK_STATE_FLAG_ACTIVE; + else + state |= GTK_STATE_FLAG_PRELIGHT; + } + gtk_css_node_set_state (link->cssnode, state); + } +} + +static void +gtk_label_update_cursor (GtkLabel *self) +{ + GtkWidget *widget = GTK_WIDGET (self); + + if (!self->select_info) + return; + + if (gtk_widget_is_sensitive (widget)) + { + if (self->select_info->active_link) + gtk_widget_set_cursor_from_name (widget, "pointer"); + else if (self->select_info->selectable) + gtk_widget_set_cursor_from_name (widget, "text"); + else + gtk_widget_set_cursor (widget, NULL); + } + else + gtk_widget_set_cursor (widget, NULL); +} + +static void +gtk_label_state_flags_changed (GtkWidget *widget, + GtkStateFlags prev_state) +{ + GtkLabel *self = GTK_LABEL (widget); + + if (self->select_info) + { + if (!gtk_widget_is_sensitive (widget)) + gtk_label_select_region (self, 0, 0); + + gtk_label_update_cursor (self); + update_link_state (self); + } + + if (GTK_WIDGET_CLASS (gtk_label_parent_class)->state_flags_changed) + GTK_WIDGET_CLASS (gtk_label_parent_class)->state_flags_changed (widget, prev_state); +} + +static void +gtk_label_update_layout_attributes (GtkLabel *self, + PangoAttrList *style_attrs) +{ + GtkWidget *widget = GTK_WIDGET (self); + GtkCssStyle *style; + PangoAttrList *attrs; + + if (self->layout == NULL) + { + pango_attr_list_unref (style_attrs); + return; + } + + if (self->select_info && self->select_info->links) + { + guint i; + + attrs = pango_attr_list_new (); + + for (i = 0; i < self->select_info->n_links; i++) + { + const GtkLabelLink *link = &self->select_info->links[i]; + const GdkRGBA *link_color; + PangoAttrList *link_attrs; + PangoAttribute *attr; + + style = gtk_css_node_get_style (link->cssnode); + link_attrs = gtk_css_style_get_pango_attributes (style); + if (link_attrs) + { + GSList *attributes = pango_attr_list_get_attributes (link_attrs); + GSList *l; + for (l = attributes; l; l = l->next) + { + attr = l->data; + + attr->start_index = link->start; + attr->end_index = link->end; + pango_attr_list_insert (attrs, attr); + } + g_slist_free (attributes); + } + + link_color = gtk_css_color_value_get_rgba (style->core->color); + attr = pango_attr_foreground_new (link_color->red * 65535, + link_color->green * 65535, + link_color->blue * 65535); + attr->start_index = link->start; + attr->end_index = link->end; + pango_attr_list_insert (attrs, attr); + + pango_attr_list_unref (link_attrs); + } + } + else + attrs = NULL; + + style = gtk_css_node_get_style (gtk_widget_get_css_node (widget)); + if (!style_attrs) + style_attrs = gtk_css_style_get_pango_attributes (style); + + if (style_attrs) + { + attrs = _gtk_pango_attr_list_merge (attrs, style_attrs); + pango_attr_list_unref (style_attrs); + } + + attrs = _gtk_pango_attr_list_merge (attrs, self->markup_attrs); + attrs = _gtk_pango_attr_list_merge (attrs, self->attrs); + + pango_layout_set_attributes (self->layout, attrs); + + pango_attr_list_unref (attrs); +} + +static void +gtk_label_css_changed (GtkWidget *widget, + GtkCssStyleChange *change) +{ + GtkLabel *self = GTK_LABEL (widget); + gboolean attrs_affected; + PangoAttrList *new_attrs = NULL; + + GTK_WIDGET_CLASS (gtk_label_parent_class)->css_changed (widget, change); + + if (gtk_css_style_change_affects (change, GTK_CSS_AFFECTS_TEXT_ATTRS)) + { + new_attrs = gtk_css_style_get_pango_attributes (gtk_css_style_change_get_new_style (change)); + attrs_affected = (self->layout && pango_layout_get_attributes (self->layout)) || + new_attrs; + } + else + attrs_affected = FALSE; + + if (change == NULL || attrs_affected || (self->select_info && self->select_info->links)) + { + gtk_label_update_layout_attributes (self, new_attrs); + + if (attrs_affected) + gtk_widget_queue_draw (widget); + } +} + +static PangoDirection +get_cursor_direction (GtkLabel *self) +{ + GSList *l; + + g_assert (self->select_info); + + gtk_label_ensure_layout (self); + + for (l = pango_layout_get_lines_readonly (self->layout); l; l = l->next) + { + PangoLayoutLine *line = l->data; + + /* If self->select_info->selection_end is at the very end of + * the line, we don't know if the cursor is on this line or + * the next without looking ahead at the next line. (End + * of paragraph is different from line break.) But it's + * definitely in this paragraph, which is good enough + * to figure out the resolved direction. + */ + if (line->start_index + line->length >= self->select_info->selection_end) + return line->resolved_dir; + } + + return PANGO_DIRECTION_LTR; +} + +static int +_gtk_label_get_link_at (GtkLabel *self, + int pos) +{ + if (self->select_info) + { + guint i; + + for (i = 0; i < self->select_info->n_links; i++) + { + const GtkLabelLink *link = &self->select_info->links[i]; + + if (link->start <= pos && pos < link->end) + return i; + } + } + + return -1; +} + +static GtkLabelLink * +gtk_label_get_focus_link (GtkLabel *self, + int *out_index) +{ + GtkLabelSelectionInfo *info = self->select_info; + int link_index; + + if (!info || + info->selection_anchor != info->selection_end) + goto nope; + + link_index = _gtk_label_get_link_at (self, info->selection_anchor); + + if (link_index != -1) + { + if (out_index) + *out_index = link_index; + + return &info->links[link_index]; + } + +nope: + if (out_index) + *out_index = -1; + return NULL; +} + +/** + * gtk_label_get_measuring_layout: + * @self: the label + * @existing_layout: %NULL or an existing layout already in use. + * @width: the width to measure with in pango units, or -1 for infinite + * + * Gets a layout that can be used for measuring sizes. The returned + * layout will be identical to the label’s layout except for the + * layout’s width, which will be set to @width. Do not modify the returned + * layout. + * + * Returns: a new reference to a pango layout + **/ +static PangoLayout * +gtk_label_get_measuring_layout (GtkLabel *self, + PangoLayout *existing_layout, + int width) +{ + PangoLayout *copy; + + if (existing_layout != NULL) + { + if (existing_layout != self->layout) + { + pango_layout_set_width (existing_layout, width); + return existing_layout; + } + + g_object_unref (existing_layout); + } + + gtk_label_ensure_layout (self); + + if (pango_layout_get_width (self->layout) == width) + { + g_object_ref (self->layout); + return self->layout; + } + + /* We can use the label's own layout if we're not allocated a size yet, + * because we don't need it to be properly setup at that point. + * This way we can make use of caching upon the label's creation. + */ + if (gtk_widget_get_width (GTK_WIDGET (self)) <= 1) + { + g_object_ref (self->layout); + pango_layout_set_width (self->layout, width); + return self->layout; + } + + /* oftentimes we want to measure a width that is far wider than the current width, + * even though the layout would not change if we made it wider. In that case, we + * can just return the current layout, because for measuring purposes, it will be + * identical. + */ + if (!pango_layout_is_wrapped (self->layout) && + !pango_layout_is_ellipsized (self->layout)) + { + PangoRectangle rect; + + if (width == -1) + return g_object_ref (self->layout); + + pango_layout_get_extents (self->layout, NULL, &rect); + if (rect.width <= width) + return g_object_ref (self->layout); + } + + copy = pango_layout_copy (self->layout); + pango_layout_set_width (copy, width); + return copy; +} + +static void +get_height_for_width (GtkLabel *self, + int width, + int *minimum_height, + int *natural_height, + int *minimum_baseline, + int *natural_baseline) +{ + PangoLayout *layout; + int text_height, baseline; + + layout = gtk_label_get_measuring_layout (self, NULL, width * PANGO_SCALE); + + pango_layout_get_pixel_size (layout, NULL, &text_height); + + *minimum_height = text_height; + *natural_height = text_height; + + baseline = pango_layout_get_baseline (layout) / PANGO_SCALE; + *minimum_baseline = baseline; + *natural_baseline = baseline; + + g_object_unref (layout); +} + +static int +get_char_pixels (GtkWidget *self, + PangoLayout *layout) +{ + PangoContext *context; + PangoFontMetrics *metrics; + int char_width, digit_width; + + context = pango_layout_get_context (layout); + metrics = pango_context_get_metrics (context, + pango_context_get_font_description (context), + pango_context_get_language (context)); + char_width = pango_font_metrics_get_approximate_char_width (metrics); + digit_width = pango_font_metrics_get_approximate_digit_width (metrics); + pango_font_metrics_unref (metrics); + + return MAX (char_width, digit_width);; +} + +static void +gtk_label_get_preferred_layout_size (GtkLabel *self, + PangoRectangle *smallest, + PangoRectangle *widest, + int *smallest_baseline, + int *widest_baseline) +{ + PangoLayout *layout; + int char_pixels; + + /* "width-chars" Hard-coded minimum width: + * - minimum size should be MAX (width-chars, strlen ("...")); + * - natural size should be MAX (width-chars, strlen (self->text)); + * + * "max-width-chars" User specified maximum size requisition + * - minimum size should be MAX (width-chars, 0) + * - natural size should be MIN (max-width-chars, strlen (self->text)) + * + * For ellipsizing labels; if max-width-chars is specified: either it is used as + * a minimum size or the label text as a minimum size (natural size still overflows). + * + * For wrapping labels; A reasonable minimum size is useful to naturally layout + * interfaces automatically. In this case if no "width-chars" is specified, the minimum + * width will default to the wrap guess that gtk_label_ensure_layout() does. + */ + + /* Start off with the pixel extents of an as-wide-as-possible layout */ + layout = gtk_label_get_measuring_layout (self, NULL, -1); + + if (self->width_chars > -1 || self->max_width_chars > -1) + char_pixels = get_char_pixels (GTK_WIDGET (self), layout); + else + char_pixels = 0; + + pango_layout_get_extents (layout, NULL, widest); + widest->width = MAX (widest->width, char_pixels * self->width_chars); + widest->x = widest->y = 0; + *widest_baseline = pango_layout_get_baseline (layout) / PANGO_SCALE; + + if (self->ellipsize || self->wrap) + { + /* a layout with width 0 will be as small as humanly possible */ + layout = gtk_label_get_measuring_layout (self, + layout, + self->width_chars > -1 ? char_pixels * self->width_chars + : 0); + + pango_layout_get_extents (layout, NULL, smallest); + smallest->width = MAX (smallest->width, char_pixels * self->width_chars); + smallest->x = smallest->y = 0; + + *smallest_baseline = pango_layout_get_baseline (layout) / PANGO_SCALE; + + if (self->max_width_chars > -1 && widest->width > char_pixels * self->max_width_chars) + { + layout = gtk_label_get_measuring_layout (self, + layout, + MAX (smallest->width, char_pixels * self->max_width_chars)); + pango_layout_get_extents (layout, NULL, widest); + widest->width = MAX (widest->width, char_pixels * self->width_chars); + widest->x = widest->y = 0; + + *widest_baseline = pango_layout_get_baseline (layout) / PANGO_SCALE; + } + + if (widest->width < smallest->width) + { + *smallest = *widest; + *smallest_baseline = *widest_baseline; + } + } + else + { + *smallest = *widest; + *smallest_baseline = *widest_baseline; + } + + g_object_unref (layout); +} + +static void +gtk_label_get_preferred_size (GtkWidget *widget, + GtkOrientation orientation, + int *minimum_size, + int *natural_size, + int *minimum_baseline, + int *natural_baseline) +{ + GtkLabel *self = GTK_LABEL (widget); + PangoRectangle widest_rect; + PangoRectangle smallest_rect; + int smallest_baseline; + int widest_baseline; + + gtk_label_get_preferred_layout_size (self, + &smallest_rect, &widest_rect, + &smallest_baseline, &widest_baseline); + + widest_rect.width = PANGO_PIXELS_CEIL (widest_rect.width); + widest_rect.height = PANGO_PIXELS_CEIL (widest_rect.height); + + smallest_rect.width = PANGO_PIXELS_CEIL (smallest_rect.width); + smallest_rect.height = PANGO_PIXELS_CEIL (smallest_rect.height); + + if (orientation == GTK_ORIENTATION_HORIZONTAL) + { + /* Normal desired width */ + *minimum_size = smallest_rect.width; + *natural_size = widest_rect.width; + + if (minimum_baseline) + *minimum_baseline = -1; + + if (natural_baseline) + *natural_baseline = -1; + } + else /* GTK_ORIENTATION_VERTICAL */ + { + if (smallest_rect.height < widest_rect.height) + { + *minimum_size = smallest_rect.height; + *natural_size = widest_rect.height; + if (minimum_baseline) + *minimum_baseline = smallest_baseline; + if (natural_baseline) + *natural_baseline = widest_baseline; + } + else + { + *minimum_size = widest_rect.height; + *natural_size = smallest_rect.height; + if (minimum_baseline) + *minimum_baseline = widest_baseline; + if (natural_baseline) + *natural_baseline = smallest_baseline; + } + } +} + + +static void +gtk_label_measure (GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + GtkLabel *self = GTK_LABEL (widget); + + if (orientation == GTK_ORIENTATION_VERTICAL && for_size != -1 && self->wrap) + { + gtk_label_clear_layout (self); + + get_height_for_width (self, for_size, minimum, natural, minimum_baseline, natural_baseline); + } + else + gtk_label_get_preferred_size (widget, orientation, minimum, natural, minimum_baseline, natural_baseline); +} + +static void +get_layout_location (GtkLabel *self, + int *xp, + int *yp) +{ + GtkWidget *widget = GTK_WIDGET (self); + int layout_width, layout_height, x, y; + float xalign, yalign; + PangoRectangle logical; + int baseline, layout_baseline, baseline_offset; + int widget_width, widget_height; + + xalign = self->xalign; + yalign = self->yalign; + + if (_gtk_widget_get_direction (widget) != GTK_TEXT_DIR_LTR) + xalign = 1.0 - xalign; + + pango_layout_get_pixel_extents (self->layout, NULL, &logical); + + layout_width = logical.width; + layout_height = logical.height; + + widget_width = gtk_widget_get_width (widget); + widget_height = gtk_widget_get_height (widget); + + baseline = gtk_widget_get_allocated_baseline (widget); + + x = floor ((xalign * (widget_width - layout_width)) - logical.x); + + baseline_offset = 0; + if (baseline != -1) + { + layout_baseline = pango_layout_get_baseline (self->layout) / PANGO_SCALE; + baseline_offset = baseline - layout_baseline; + yalign = 0.0; /* Can't support yalign while baseline aligning */ + } + + y = floor ((widget_height - layout_height) * yalign) + baseline_offset; + + if (xp) + *xp = x; + + if (yp) + *yp = y; +} + +static void +gtk_label_size_allocate (GtkWidget *widget, + int width, + int height, + int baseline) +{ + GtkLabel *self = GTK_LABEL (widget); + + if (self->layout) + { + if (self->ellipsize || self->wrap) + pango_layout_set_width (self->layout, width * PANGO_SCALE); + else + pango_layout_set_width (self->layout, -1); + } + + if (self->popup_menu) + gtk_popover_present (GTK_POPOVER (self->popup_menu)); +} + + + +#define GRAPHENE_RECT_FROM_RECT(_r) (GRAPHENE_RECT_INIT ((_r)->x, (_r)->y, (_r)->width, (_r)->height)) + +static void +gtk_label_snapshot (GtkWidget *widget, + GtkSnapshot *snapshot) +{ + GtkLabel *self = GTK_LABEL (widget); + GtkLabelSelectionInfo *info; + GtkStyleContext *context; + int lx, ly; + int width, height; + + if (!self->text || (*self->text == '\0')) + return; + + gtk_label_ensure_layout (self); + + context = _gtk_widget_get_style_context (widget); + get_layout_location (self, &lx, &ly); + + gtk_snapshot_render_layout (snapshot, context, lx, ly, self->layout); + + info = self->select_info; + if (!info) + return; + + width = gtk_widget_get_width (widget); + height = gtk_widget_get_height (widget); + + if (info->selection_anchor != info->selection_end) + { + int range[2]; + cairo_region_t *range_clip; + cairo_rectangle_int_t clip_rect; + int i; + + range[0] = MIN (info->selection_anchor, info->selection_end); + range[1] = MAX (info->selection_anchor, info->selection_end); + + gtk_style_context_save_to_node (context, info->selection_node); + + range_clip = gdk_pango_layout_get_clip_region (self->layout, lx, ly, range, 1); + for (i = 0; i < cairo_region_num_rectangles (range_clip); i++) + { + cairo_region_get_rectangle (range_clip, i, &clip_rect); + + gtk_snapshot_push_clip (snapshot, &GRAPHENE_RECT_FROM_RECT (&clip_rect)); + gtk_snapshot_render_background (snapshot, context, 0, 0, width, height); + gtk_snapshot_render_layout (snapshot, context, lx, ly, self->layout); + gtk_snapshot_pop (snapshot); + } + + cairo_region_destroy (range_clip); + + gtk_style_context_restore (context); + } + else + { + GtkLabelLink *focus_link; + GtkLabelLink *active_link; + int range[2]; + cairo_region_t *range_clip; + cairo_rectangle_int_t clip_rect; + int i; + GdkRectangle rect; + + if (info->selectable && + gtk_widget_has_focus (widget) && + gtk_widget_is_drawable (widget)) + { + PangoDirection cursor_direction; + + cursor_direction = get_cursor_direction (self); + gtk_snapshot_render_insertion_cursor (snapshot, context, + lx, ly, + self->layout, self->select_info->selection_end, + cursor_direction); + } + + focus_link = gtk_label_get_focus_link (self, NULL); + active_link = info->active_link; + + if (active_link) + { + range[0] = active_link->start; + range[1] = active_link->end; + + gtk_style_context_save_to_node (context, active_link->cssnode); + + range_clip = gdk_pango_layout_get_clip_region (self->layout, lx, ly, range, 1); + for (i = 0; i < cairo_region_num_rectangles (range_clip); i++) + { + cairo_region_get_rectangle (range_clip, i, &clip_rect); + + gtk_snapshot_push_clip (snapshot, &GRAPHENE_RECT_FROM_RECT (&clip_rect)); + gtk_snapshot_render_background (snapshot, context, 0, 0, width, height); + gtk_snapshot_render_layout (snapshot, context, lx, ly, self->layout); + gtk_snapshot_pop (snapshot); + } + + cairo_region_destroy (range_clip); + + gtk_style_context_restore (context); + } + + if (focus_link && gtk_widget_has_visible_focus (widget)) + { + range[0] = focus_link->start; + range[1] = focus_link->end; + + gtk_style_context_save_to_node (context, focus_link->cssnode); + + range_clip = gdk_pango_layout_get_clip_region (self->layout, lx, ly, range, 1); + cairo_region_get_extents (range_clip, &rect); + + gtk_snapshot_render_focus (snapshot, context, rect.x, rect.y, rect.width, rect.height); + + cairo_region_destroy (range_clip); + + gtk_style_context_restore (context); + } + } +} + +static GtkSizeRequestMode +gtk_label_get_request_mode (GtkWidget *widget) +{ + GtkLabel *self = GTK_LABEL (widget); + + if (self->wrap) + return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH; + + return GTK_SIZE_REQUEST_CONSTANT_SIZE; +} + +static void +gtk_label_dispose (GObject *object) +{ + GtkLabel *self = GTK_LABEL (object); + + gtk_label_set_mnemonic_widget (self, NULL); + + G_OBJECT_CLASS (gtk_label_parent_class)->dispose (object); +} + +static void +gtk_label_clear_links (GtkLabel *self) +{ + guint i; + + if (!self->select_info) + return; + + for (i = 0; i < self->select_info->n_links; i++) + { + const GtkLabelLink *link = &self->select_info->links[i]; + gtk_css_node_set_parent (link->cssnode, NULL); + g_free (link->uri); + g_free (link->title); + } + g_free (self->select_info->links); + self->select_info->links = NULL; + self->select_info->n_links = 0; + self->select_info->active_link = NULL; + gtk_widget_remove_css_class (GTK_WIDGET (self), "link"); +} + +static void +gtk_label_finalize (GObject *object) +{ + GtkLabel *self = GTK_LABEL (object); + + g_free (self->label); + g_free (self->text); + + g_clear_object (&self->layout); + g_clear_pointer (&self->attrs, pango_attr_list_unref); + g_clear_pointer (&self->markup_attrs, pango_attr_list_unref); + + if (self->select_info) + g_object_unref (self->select_info->provider); + + gtk_label_clear_links (self); + g_free (self->select_info); + + g_clear_pointer (&self->popup_menu, gtk_widget_unparent); + g_clear_object (&self->extra_menu); + + G_OBJECT_CLASS (gtk_label_parent_class)->finalize (object); +} + +static void +gtk_label_unrealize (GtkWidget *widget) +{ + GtkLabel *self = GTK_LABEL (widget); + + if (self->select_info && + self->select_info->provider) + { + GdkClipboard *clipboard = gtk_widget_get_primary_clipboard (widget); + + if (gdk_clipboard_get_content (clipboard) == self->select_info->provider) + gdk_clipboard_set_content (clipboard, NULL); + } + + GTK_WIDGET_CLASS (gtk_label_parent_class)->unrealize (widget); +} + +static gboolean +range_is_in_ellipsis_full (GtkLabel *self, + int range_start, + int range_end, + int *ellipsis_start, + int *ellipsis_end) +{ + PangoLayoutIter *iter; + gboolean in_ellipsis; + + if (!self->ellipsize) + return FALSE; + + gtk_label_ensure_layout (self); + + if (!pango_layout_is_ellipsized (self->layout)) + return FALSE; + + iter = pango_layout_get_iter (self->layout); + + in_ellipsis = FALSE; + + do { + PangoLayoutRun *run; + + run = pango_layout_iter_get_run_readonly (iter); + if (run) + { + PangoItem *item; + + item = ((PangoGlyphItem*)run)->item; + + if (item->offset <= range_start && range_end <= item->offset + item->length) + { + if (item->analysis.flags & PANGO_ANALYSIS_FLAG_IS_ELLIPSIS) + { + if (ellipsis_start) + *ellipsis_start = item->offset; + if (ellipsis_end) + *ellipsis_end = item->offset + item->length; + in_ellipsis = TRUE; + } + break; + } + else if (item->offset + item->length >= range_end) + break; + } + } while (pango_layout_iter_next_run (iter)); + + pango_layout_iter_free (iter); + + return in_ellipsis; +} + +static gboolean +range_is_in_ellipsis (GtkLabel *self, + int range_start, + int range_end) +{ + return range_is_in_ellipsis_full (self, range_start, range_end, NULL, NULL); +} + +static gboolean +gtk_label_grab_focus (GtkWidget *widget) +{ + GtkLabel *self = GTK_LABEL (widget); + gboolean select_on_focus; + GtkWidget *prev_focus; + + if (self->select_info == NULL) + return FALSE; + + prev_focus = gtk_root_get_focus (gtk_widget_get_root (widget)); + + if (!GTK_WIDGET_CLASS (gtk_label_parent_class)->grab_focus (widget)) + return FALSE; + + if (self->select_info->selectable) + { + g_object_get (gtk_widget_get_settings (widget), + "gtk-label-select-on-focus", + &select_on_focus, + NULL); + + if (select_on_focus && !self->in_click && + !(prev_focus && gtk_widget_is_ancestor (prev_focus, widget))) + gtk_label_select_region (self, 0, -1); + } + else + { + if (self->select_info->links && !self->in_click && + !(prev_focus && gtk_widget_is_ancestor (prev_focus, widget))) + { + guint i; + + for (i = 0; i < self->select_info->n_links; i++) + { + const GtkLabelLink *link = &self->select_info->links[i]; + + if (!range_is_in_ellipsis (self, link->start, link->end)) + { + self->select_info->selection_anchor = link->start; + self->select_info->selection_end = link->start; + break; + } + } + } + } + + return TRUE; +} + +static gboolean +get_layout_index (GtkLabel *self, + int x, + int y, + int *index) +{ + int trailing = 0; + const char *cluster; + const char *cluster_end; + gboolean inside; + int lx, ly; + + *index = 0; + + gtk_label_ensure_layout (self); + get_layout_location (self, &lx, &ly); + + /* Translate x/y to layout position */ + x -= lx; + y -= ly; + + x *= PANGO_SCALE; + y *= PANGO_SCALE; + + inside = pango_layout_xy_to_index (self->layout, + x, y, + index, &trailing); + + cluster = self->text + *index; + cluster_end = cluster; + while (trailing) + { + cluster_end = g_utf8_next_char (cluster_end); + --trailing; + } + + *index += (cluster_end - cluster); + + return inside; +} + +static gboolean +gtk_label_query_tooltip (GtkWidget *widget, + int x, + int y, + gboolean keyboard_tip, + GtkTooltip *tooltip) +{ + GtkLabel *self = GTK_LABEL (widget); + GtkLabelSelectionInfo *info = self->select_info; + int index = -1; + + if (info && info->links) + { + if (keyboard_tip) + { + if (info->selection_anchor == info->selection_end) + index = info->selection_anchor; + } + else + { + if (!get_layout_index (self, x, y, &index)) + index = -1; + } + + if (index != -1) + { + const int link_index = _gtk_label_get_link_at (self, index); + + if (link_index != -1) + { + const GtkLabelLink *link = &info->links[link_index]; + + if (link->title) + { + gtk_tooltip_set_markup (tooltip, link->title); + } + } + } + } + + return GTK_WIDGET_CLASS (gtk_label_parent_class)->query_tooltip (widget, + x, y, + keyboard_tip, + tooltip); +} + +static gboolean +gtk_label_focus (GtkWidget *widget, + GtkDirectionType direction) +{ + GtkLabel *self = GTK_LABEL (widget); + GtkLabelSelectionInfo *info = self->select_info; + GtkLabelLink *focus_link; + + if (!gtk_widget_is_focus (widget)) + { + gtk_widget_grab_focus (widget); + if (info) + { + focus_link = gtk_label_get_focus_link (self, NULL); + if (focus_link && direction == GTK_DIR_TAB_BACKWARD) + { + int i; + for (i = info->n_links - 1; i >= 0; i--) + { + focus_link = &info->links[i]; + if (!range_is_in_ellipsis (self, focus_link->start, focus_link->end)) + { + info->selection_anchor = focus_link->start; + info->selection_end = focus_link->start; + } + } + } + + return TRUE; + } + + return FALSE; + } + + if (!info) + return FALSE; + + if (info->selectable) + { + int index; + + if (info->selection_anchor != info->selection_end) + goto out; + + index = info->selection_anchor; + + if (direction == GTK_DIR_TAB_FORWARD) + { + guint i; + for (i = 0; i < info->n_links; i++) + { + const GtkLabelLink *link = &info->links[i]; + + if (link->start > index) + { + if (!range_is_in_ellipsis (self, link->start, link->end)) + { + gtk_label_select_region_index (self, link->start, link->start); + return TRUE; + } + } + } + } + else if (direction == GTK_DIR_TAB_BACKWARD) + { + int i; + for (i = info->n_links - 1; i >= 0; i--) + { + GtkLabelLink *link = &info->links[i]; + + if (link->end < index) + { + if (!range_is_in_ellipsis (self, link->start, link->end)) + { + gtk_label_select_region_index (self, link->start, link->start); + return TRUE; + } + } + } + } + + goto out; + } + else + { + int focus_link_index; + int new_index = -1; + int i; + + if (info->n_links == 0) + goto out; + + focus_link = gtk_label_get_focus_link (self, &focus_link_index); + + if (!focus_link) + goto out; + + switch (direction) + { + case GTK_DIR_TAB_FORWARD: + if (focus_link) + new_index = (focus_link_index + 1) % info->n_links; + else + new_index = 0; + + for (i = new_index; i < info->n_links; i++) + { + const GtkLabelLink *link = &info->links[i]; + if (!range_is_in_ellipsis (self, link->start, link->end)) + break; + } + break; + + case GTK_DIR_TAB_BACKWARD: + if (focus_link) + new_index = focus_link_index == 0 ? info->n_links - 1 : focus_link_index - 1; + else + new_index = info->n_links - 1; + + for (i = new_index; i >= 0; i--) + { + const GtkLabelLink *link = &info->links[i]; + if (!range_is_in_ellipsis (self, link->start, link->end)) + break; + } + break; + + default: + case GTK_DIR_UP: + case GTK_DIR_DOWN: + case GTK_DIR_LEFT: + case GTK_DIR_RIGHT: + goto out; + } + + if (new_index != -1) + { + focus_link = &info->links[new_index]; + info->selection_anchor = focus_link->start; + info->selection_end = focus_link->start; + gtk_widget_queue_draw (widget); + + return TRUE; + } + } + +out: + + return FALSE; +} + +static void +emit_activate_link (GtkLabel *self, + GtkLabelLink *link) +{ + gboolean handled; + + g_signal_emit (self, signals[ACTIVATE_LINK], 0, link->uri, &handled); + + /* signal handler might have invalidated the layout */ + if (!self->layout) + return; + + if (handled && !link->visited && + self->select_info && self->select_info->links) + { + link->visited = TRUE; + update_link_state (self); + } +} + +static void +gtk_label_activate_link_open (GtkWidget *widget, + const char *name, + GVariant *parameter) +{ + GtkLabel *self = GTK_LABEL (widget); + GtkLabelLink *link = self->select_info->context_link; + + if (link) + emit_activate_link (self, link); +} + +static void +gtk_label_activate_link_copy (GtkWidget *widget, + const char *name, + GVariant *parameter) +{ + GtkLabel *self = GTK_LABEL (widget); + GtkLabelLink *link = self->select_info->context_link; + + if (link) + { + GdkClipboard *clipboard; + + clipboard = gtk_widget_get_clipboard (widget); + gdk_clipboard_set_text (clipboard, link->uri); + } + else + g_print ("no link ?!\n"); +} + +static void +gtk_label_activate_clipboard_copy (GtkWidget *widget, + const char *name, + GVariant *parameter) +{ + g_signal_emit_by_name (widget, "copy-clipboard"); +} + +static void +gtk_label_select_all (GtkLabel *self) +{ + gtk_label_select_region_index (self, 0, strlen (self->text)); +} + +static void +gtk_label_activate_selection_select_all (GtkWidget *widget, + const char *name, + GVariant *parameter) +{ + gtk_label_select_all (GTK_LABEL (widget)); +} + +static void +gtk_label_nop (GtkWidget *widget, + const char *name, + GVariant *parameter) +{ +} + +static gboolean +gtk_label_mnemonic_activate (GtkWidget *widget, + gboolean group_cycling) +{ + GtkLabel *self = GTK_LABEL (widget); + GtkWidget *parent; + + if (self->mnemonic_widget) + return gtk_widget_mnemonic_activate (self->mnemonic_widget, group_cycling); + + /* Try to find the widget to activate by traversing the + * widget's ancestry. + */ + parent = gtk_widget_get_parent (widget); + + if (GTK_IS_NOTEBOOK (parent)) + return FALSE; + + while (parent) + { + if (gtk_widget_get_can_focus (parent) || + (!group_cycling && gtk_widget_can_activate (parent)) || + GTK_IS_NOTEBOOK (gtk_widget_get_parent (parent))) + return gtk_widget_mnemonic_activate (parent, group_cycling); + parent = gtk_widget_get_parent (parent); + } + + /* barf if there was nothing to activate */ + g_warning ("Couldn't find a target for a mnemonic activation."); + gtk_widget_error_bell (widget); + + return FALSE; +} + +static void +gtk_label_popup_menu (GtkWidget *widget, + const char *action_name, + GVariant *parameters) +{ + GtkLabel *self = GTK_LABEL (widget); + + gtk_label_do_popup (self, -1, -1); +} + +static void +gtk_label_root (GtkWidget *widget) +{ + GtkLabel *self = GTK_LABEL (widget); + + GTK_WIDGET_CLASS (gtk_label_parent_class)->root (widget); + + gtk_label_setup_mnemonic (self); + + /* The PangoContext is replaced when the display changes, so clear the layouts */ + gtk_label_clear_layout (GTK_LABEL (widget)); +} + +static void +gtk_label_unroot (GtkWidget *widget) +{ + GtkLabel *self = GTK_LABEL (widget); + + gtk_label_setup_mnemonic (self); + + GTK_WIDGET_CLASS (gtk_label_parent_class)->unroot (widget); +} + +static gboolean +gtk_label_activate_link (GtkLabel *self, + const char *uri) +{ + GtkWidget *widget = GTK_WIDGET (self); + GtkWidget *toplevel = GTK_WIDGET (gtk_widget_get_root (widget)); + + if (!GTK_IS_WINDOW (toplevel)) + return FALSE; + + gtk_show_uri (GTK_WINDOW (toplevel), uri, GDK_CURRENT_TIME); + + return TRUE; +} + +static void +gtk_label_activate_current_link (GtkLabel *self) +{ + GtkLabelLink *link; + GtkWidget *widget = GTK_WIDGET (self); + + link = gtk_label_get_focus_link (self, NULL); + + if (link) + emit_activate_link (self, link); + else + gtk_widget_activate_default (widget); +} + +static void +gtk_label_copy_clipboard (GtkLabel *self) +{ + if (self->text && self->select_info) + { + int start, end; + int len; + GdkClipboard *clipboard; + + start = MIN (self->select_info->selection_anchor, + self->select_info->selection_end); + end = MAX (self->select_info->selection_anchor, + self->select_info->selection_end); + + len = strlen (self->text); + + if (end > len) + end = len; + + if (start > len) + start = len; + + clipboard = gtk_widget_get_clipboard (GTK_WIDGET (self)); + + if (start != end) + { + char *str = g_strndup (self->text + start, end - start); + gdk_clipboard_set_text (clipboard, str); + g_free (str); + } + else + { + GtkLabelLink *link; + + link = gtk_label_get_focus_link (self, NULL); + if (link) + gdk_clipboard_set_text (clipboard, link->uri); + } + } +} + +static void gtk_label_class_init (GtkLabelClass *class) { GObjectClass *gobject_class = G_OBJECT_CLASS (class); @@ -651,17 +2115,17 @@ gtk_label_class_init (GtkLabelClass *class) * - Ctrl-arrow key combinations move by words/paragraphs * - Home/End keys move to the ends of the buffer */ - signals[MOVE_CURSOR] = + signals[MOVE_CURSOR] = g_signal_new (I_("move-cursor"), - G_OBJECT_CLASS_TYPE (gobject_class), - G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET (GtkLabelClass, move_cursor), - NULL, NULL, - _gtk_marshal_VOID__ENUM_INT_BOOLEAN, - G_TYPE_NONE, 3, - GTK_TYPE_MOVEMENT_STEP, - G_TYPE_INT, - G_TYPE_BOOLEAN); + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkLabelClass, move_cursor), + NULL, NULL, + _gtk_marshal_VOID__ENUM_INT_BOOLEAN, + G_TYPE_NONE, 3, + GTK_TYPE_MOVEMENT_STEP, + G_TYPE_INT, + G_TYPE_BOOLEAN); /** * GtkLabel::copy-clipboard: @@ -672,16 +2136,16 @@ gtk_label_class_init (GtkLabelClass *class) * which gets emitted to copy the selection to the clipboard. * * The default binding for this signal is Ctrl-c. - */ + */ signals[COPY_CLIPBOARD] = g_signal_new (I_("copy-clipboard"), - G_OBJECT_CLASS_TYPE (gobject_class), - G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET (GtkLabelClass, copy_clipboard), - NULL, NULL, - NULL, - G_TYPE_NONE, 0); - + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkLabelClass, copy_clipboard), + NULL, NULL, + NULL, + G_TYPE_NONE, 0); + /** * GtkLabel::activate-current-link: * @self: The label on which the signal was emitted @@ -959,7 +2423,7 @@ gtk_label_class_init (GtkLabelClass *class) /** * GtkLabel|menu.popup: * - * Opens the context menu. + * Opens the context menu. */ gtk_widget_class_install_action (widget_class, "menu.popup", NULL, gtk_label_popup_menu); @@ -978,34 +2442,34 @@ gtk_label_class_init (GtkLabelClass *class) /* Moving the insertion point */ add_move_binding (widget_class, GDK_KEY_Right, 0, - GTK_MOVEMENT_VISUAL_POSITIONS, 1); + GTK_MOVEMENT_VISUAL_POSITIONS, 1); add_move_binding (widget_class, GDK_KEY_Left, 0, - GTK_MOVEMENT_VISUAL_POSITIONS, -1); + GTK_MOVEMENT_VISUAL_POSITIONS, -1); add_move_binding (widget_class, GDK_KEY_KP_Right, 0, - GTK_MOVEMENT_VISUAL_POSITIONS, 1); - + GTK_MOVEMENT_VISUAL_POSITIONS, 1); + add_move_binding (widget_class, GDK_KEY_KP_Left, 0, - GTK_MOVEMENT_VISUAL_POSITIONS, -1); - + GTK_MOVEMENT_VISUAL_POSITIONS, -1); + add_move_binding (widget_class, GDK_KEY_f, GDK_CONTROL_MASK, - GTK_MOVEMENT_LOGICAL_POSITIONS, 1); - + GTK_MOVEMENT_LOGICAL_POSITIONS, 1); + add_move_binding (widget_class, GDK_KEY_b, GDK_CONTROL_MASK, - GTK_MOVEMENT_LOGICAL_POSITIONS, -1); - + GTK_MOVEMENT_LOGICAL_POSITIONS, -1); + add_move_binding (widget_class, GDK_KEY_Right, GDK_CONTROL_MASK, - GTK_MOVEMENT_WORDS, 1); + GTK_MOVEMENT_WORDS, 1); add_move_binding (widget_class, GDK_KEY_Left, GDK_CONTROL_MASK, - GTK_MOVEMENT_WORDS, -1); + GTK_MOVEMENT_WORDS, -1); add_move_binding (widget_class, GDK_KEY_KP_Right, GDK_CONTROL_MASK, - GTK_MOVEMENT_WORDS, 1); + GTK_MOVEMENT_WORDS, 1); add_move_binding (widget_class, GDK_KEY_KP_Left, GDK_CONTROL_MASK, - GTK_MOVEMENT_WORDS, -1); + GTK_MOVEMENT_WORDS, -1); /* select all */ gtk_widget_class_add_binding (widget_class, @@ -1020,61 +2484,61 @@ gtk_label_class_init (GtkLabelClass *class) /* unselect all */ gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_a, GDK_SHIFT_MASK | GDK_CONTROL_MASK, - "move-cursor", + "move-cursor", "(iib)", GTK_MOVEMENT_PARAGRAPH_ENDS, 0, FALSE); gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_backslash, GDK_CONTROL_MASK, - "move-cursor", + "move-cursor", "(iib)", GTK_MOVEMENT_PARAGRAPH_ENDS, 0, FALSE); add_move_binding (widget_class, GDK_KEY_f, GDK_ALT_MASK, - GTK_MOVEMENT_WORDS, 1); + GTK_MOVEMENT_WORDS, 1); add_move_binding (widget_class, GDK_KEY_b, GDK_ALT_MASK, - GTK_MOVEMENT_WORDS, -1); + GTK_MOVEMENT_WORDS, -1); add_move_binding (widget_class, GDK_KEY_Home, 0, - GTK_MOVEMENT_DISPLAY_LINE_ENDS, -1); + GTK_MOVEMENT_DISPLAY_LINE_ENDS, -1); add_move_binding (widget_class, GDK_KEY_End, 0, - GTK_MOVEMENT_DISPLAY_LINE_ENDS, 1); + GTK_MOVEMENT_DISPLAY_LINE_ENDS, 1); add_move_binding (widget_class, GDK_KEY_KP_Home, 0, - GTK_MOVEMENT_DISPLAY_LINE_ENDS, -1); + GTK_MOVEMENT_DISPLAY_LINE_ENDS, -1); add_move_binding (widget_class, GDK_KEY_KP_End, 0, - GTK_MOVEMENT_DISPLAY_LINE_ENDS, 1); - + GTK_MOVEMENT_DISPLAY_LINE_ENDS, 1); + add_move_binding (widget_class, GDK_KEY_Home, GDK_CONTROL_MASK, - GTK_MOVEMENT_BUFFER_ENDS, -1); + GTK_MOVEMENT_BUFFER_ENDS, -1); add_move_binding (widget_class, GDK_KEY_End, GDK_CONTROL_MASK, - GTK_MOVEMENT_BUFFER_ENDS, 1); + GTK_MOVEMENT_BUFFER_ENDS, 1); add_move_binding (widget_class, GDK_KEY_KP_Home, GDK_CONTROL_MASK, - GTK_MOVEMENT_BUFFER_ENDS, -1); + GTK_MOVEMENT_BUFFER_ENDS, -1); add_move_binding (widget_class, GDK_KEY_KP_End, GDK_CONTROL_MASK, - GTK_MOVEMENT_BUFFER_ENDS, 1); + GTK_MOVEMENT_BUFFER_ENDS, 1); /* copy */ gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_c, GDK_CONTROL_MASK, - "copy-clipboard", + "copy-clipboard", NULL); gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_Return, 0, - "activate-current-link", + "activate-current-link", NULL); gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_ISO_Enter, 0, - "activate-current-link", + "activate-current-link", NULL); gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_KP_Enter, 0, - "activate-current-link", + "activate-current-link", NULL); gtk_widget_class_set_css_name (widget_class, I_("label")); @@ -1140,240 +2604,6 @@ gtk_label_class_init (GtkLabelClass *class) gtk_label_activate_link_copy); } -static void -gtk_label_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - GtkLabel *self = GTK_LABEL (object); - - switch (prop_id) - { - case PROP_LABEL: - gtk_label_set_label (self, g_value_get_string (value)); - break; - case PROP_ATTRIBUTES: - gtk_label_set_attributes (self, g_value_get_boxed (value)); - break; - case PROP_USE_MARKUP: - gtk_label_set_use_markup (self, g_value_get_boolean (value)); - break; - case PROP_USE_UNDERLINE: - gtk_label_set_use_underline (self, g_value_get_boolean (value)); - break; - case PROP_JUSTIFY: - gtk_label_set_justify (self, g_value_get_enum (value)); - break; - case PROP_WRAP: - gtk_label_set_wrap (self, g_value_get_boolean (value)); - break; - case PROP_WRAP_MODE: - gtk_label_set_wrap_mode (self, g_value_get_enum (value)); - break; - case PROP_SELECTABLE: - gtk_label_set_selectable (self, g_value_get_boolean (value)); - break; - case PROP_MNEMONIC_WIDGET: - gtk_label_set_mnemonic_widget (self, (GtkWidget*) g_value_get_object (value)); - break; - case PROP_ELLIPSIZE: - gtk_label_set_ellipsize (self, g_value_get_enum (value)); - break; - case PROP_WIDTH_CHARS: - gtk_label_set_width_chars (self, g_value_get_int (value)); - break; - case PROP_SINGLE_LINE_MODE: - gtk_label_set_single_line_mode (self, g_value_get_boolean (value)); - break; - case PROP_MAX_WIDTH_CHARS: - gtk_label_set_max_width_chars (self, g_value_get_int (value)); - break; - case PROP_LINES: - gtk_label_set_lines (self, g_value_get_int (value)); - break; - case PROP_XALIGN: - gtk_label_set_xalign (self, g_value_get_float (value)); - break; - case PROP_YALIGN: - gtk_label_set_yalign (self, g_value_get_float (value)); - break; - case PROP_EXTRA_MENU: - gtk_label_set_extra_menu (self, g_value_get_object (value)); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -static void -gtk_label_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - GtkLabel *self = GTK_LABEL (object); - - switch (prop_id) - { - case PROP_LABEL: - g_value_set_string (value, self->label); - break; - case PROP_ATTRIBUTES: - g_value_set_boxed (value, self->attrs); - break; - case PROP_USE_MARKUP: - g_value_set_boolean (value, self->use_markup); - break; - case PROP_USE_UNDERLINE: - g_value_set_boolean (value, self->use_underline); - break; - case PROP_JUSTIFY: - g_value_set_enum (value, self->jtype); - break; - case PROP_WRAP: - g_value_set_boolean (value, self->wrap); - break; - case PROP_WRAP_MODE: - g_value_set_enum (value, self->wrap_mode); - break; - case PROP_SELECTABLE: - g_value_set_boolean (value, gtk_label_get_selectable (self)); - break; - case PROP_MNEMONIC_KEYVAL: - g_value_set_uint (value, self->mnemonic_keyval); - break; - case PROP_MNEMONIC_WIDGET: - g_value_set_object (value, (GObject*) self->mnemonic_widget); - break; - case PROP_ELLIPSIZE: - g_value_set_enum (value, self->ellipsize); - break; - case PROP_WIDTH_CHARS: - g_value_set_int (value, gtk_label_get_width_chars (self)); - break; - case PROP_SINGLE_LINE_MODE: - g_value_set_boolean (value, gtk_label_get_single_line_mode (self)); - break; - case PROP_MAX_WIDTH_CHARS: - g_value_set_int (value, gtk_label_get_max_width_chars (self)); - break; - case PROP_LINES: - g_value_set_int (value, gtk_label_get_lines (self)); - break; - case PROP_XALIGN: - g_value_set_float (value, gtk_label_get_xalign (self)); - break; - case PROP_YALIGN: - g_value_set_float (value, gtk_label_get_yalign (self)); - break; - case PROP_EXTRA_MENU: - g_value_set_object (value, gtk_label_get_extra_menu (self)); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -static void -gtk_label_init (GtkLabel *self) -{ - self->width_chars = -1; - self->max_width_chars = -1; - self->label = g_strdup (""); - self->lines = -1; - - self->xalign = 0.5; - self->yalign = 0.5; - - self->jtype = GTK_JUSTIFY_LEFT; - self->wrap = FALSE; - self->wrap_mode = PANGO_WRAP_WORD; - self->ellipsize = PANGO_ELLIPSIZE_NONE; - - self->use_underline = FALSE; - self->use_markup = FALSE; - - self->mnemonic_keyval = GDK_KEY_VoidSymbol; - self->layout = NULL; - self->text = g_strdup (""); - self->attrs = NULL; - - self->mnemonic_widget = NULL; - - self->mnemonics_visible = FALSE; -} - - -static void -gtk_label_buildable_interface_init (GtkBuildableIface *iface) -{ - buildable_parent_iface = g_type_interface_peek_parent (iface); - - iface->custom_tag_start = gtk_label_buildable_custom_tag_start; - iface->custom_finished = gtk_label_buildable_custom_finished; -} - -static const GtkBuildableParser pango_parser = -{ - gtk_pango_attribute_start_element, -}; - -static gboolean -gtk_label_buildable_custom_tag_start (GtkBuildable *buildable, - GtkBuilder *builder, - GObject *child, - const char *tagname, - GtkBuildableParser *parser, - gpointer *data) -{ - if (buildable_parent_iface->custom_tag_start (buildable, builder, child, - tagname, parser, data)) - return TRUE; - - if (strcmp (tagname, "attributes") == 0) - { - GtkPangoAttributeParserData *parser_data; - - parser_data = g_slice_new0 (GtkPangoAttributeParserData); - parser_data->builder = g_object_ref (builder); - parser_data->object = (GObject *) g_object_ref (buildable); - *parser = pango_parser; - *data = parser_data; - return TRUE; - } - return FALSE; -} - -static void -gtk_label_buildable_custom_finished (GtkBuildable *buildable, - GtkBuilder *builder, - GObject *child, - const char *tagname, - gpointer user_data) -{ - GtkPangoAttributeParserData *data = user_data; - - buildable_parent_iface->custom_finished (buildable, builder, child, - tagname, user_data); - - if (strcmp (tagname, "attributes") == 0) - { - if (data->attrs) - { - gtk_label_set_attributes (GTK_LABEL (buildable), data->attrs); - pango_attr_list_unref (data->attrs); - } - - g_object_unref (data->object); - g_object_unref (data->builder); - g_slice_free (GtkPangoAttributeParserData, data); - } -} - - /** * gtk_label_new: * @str: (nullable): The text of the label @@ -1405,15 +2635,15 @@ gtk_label_new (const char *str) * * If characters in @str are preceded by an underscore, they are * underlined. If you need a literal underscore character in a label, use - * '__' (two underscores). The first underlined character represents a - * keyboard accelerator called a mnemonic. The mnemonic key can be used + * '__' (two underscores). The first underlined character represents a + * keyboard accelerator called a mnemonic. The mnemonic key can be used * to activate another widget, chosen automatically, or explicitly using * gtk_label_set_mnemonic_widget(). - * - * If gtk_label_set_mnemonic_widget() is not called, then the first - * activatable ancestor of the #GtkLabel will be chosen as the mnemonic - * widget. For instance, if the label is inside a button or menu item, - * the button or menu item will automatically become the mnemonic widget + * + * If gtk_label_set_mnemonic_widget() is not called, then the first + * activatable ancestor of the #GtkLabel will be chosen as the mnemonic + * widget. For instance, if the label is inside a button or menu item, + * the button or menu item will automatically become the mnemonic widget * and be activated by the mnemonic. * * Returns: the new #GtkLabel @@ -1431,38 +2661,34 @@ gtk_label_new_with_mnemonic (const char *str) return GTK_WIDGET (self); } -static gboolean -gtk_label_mnemonic_activate (GtkWidget *widget, - gboolean group_cycling) +static void +_gtk_label_mnemonics_visible_apply_recursively (GtkWidget *widget, + gboolean visible) { - GtkLabel *self = GTK_LABEL (widget); - GtkWidget *parent; - - if (self->mnemonic_widget) - return gtk_widget_mnemonic_activate (self->mnemonic_widget, group_cycling); - - /* Try to find the widget to activate by traversing the - * widget's ancestry. - */ - parent = gtk_widget_get_parent (widget); - - if (GTK_IS_NOTEBOOK (parent)) - return FALSE; - - while (parent) + if (GTK_IS_LABEL (widget)) { - if (gtk_widget_get_can_focus (parent) || - (!group_cycling && gtk_widget_can_activate (parent)) || - GTK_IS_NOTEBOOK (gtk_widget_get_parent (parent))) - return gtk_widget_mnemonic_activate (parent, group_cycling); - parent = gtk_widget_get_parent (parent); + GtkLabel *self = GTK_LABEL (widget); + + if (self->mnemonics_visible != visible) + { + self->mnemonics_visible = visible; + gtk_label_recalculate (self); + } } + else + { + GtkWidget *child; - /* barf if there was nothing to activate */ - g_warning ("Couldn't find a target for a mnemonic activation."); - gtk_widget_error_bell (widget); + for (child = gtk_widget_get_first_child (widget); + child; + child = gtk_widget_get_next_sibling (child)) + { + if (GTK_IS_NATIVE (child)) + continue; - return FALSE; + _gtk_label_mnemonics_visible_apply_recursively (child, visible); + } + } } static void @@ -1521,8 +2747,8 @@ gtk_label_setup_mnemonic (GtkLabel *self) g_object_get (native, "mnemonics-visible", &mnemonics_visible, NULL); self->mnemonics_visible = mnemonics_visible; - connected = - GPOINTER_TO_INT (g_object_get_qdata (G_OBJECT (native), quark_mnemonics_visible_connected)); + connected = GPOINTER_TO_INT (g_object_get_qdata (G_OBJECT (native), + quark_mnemonics_visible_connected)); if (!connected) { @@ -1537,60 +2763,8 @@ gtk_label_setup_mnemonic (GtkLabel *self) } static void -gtk_label_root (GtkWidget *widget) -{ - GtkLabel *self = GTK_LABEL (widget); - - GTK_WIDGET_CLASS (gtk_label_parent_class)->root (widget); - - gtk_label_setup_mnemonic (self); - - /* The PangoContext is replaced when the display changes, so clear the layouts */ - gtk_label_clear_layout (GTK_LABEL (widget)); -} - -static void -gtk_label_unroot (GtkWidget *widget) -{ - GtkLabel *self = GTK_LABEL (widget); - - gtk_label_setup_mnemonic (self); - - GTK_WIDGET_CLASS (gtk_label_parent_class)->unroot (widget); -} - -void -_gtk_label_mnemonics_visible_apply_recursively (GtkWidget *widget, - gboolean visible) -{ - if (GTK_IS_LABEL (widget)) - { - GtkLabel *self = GTK_LABEL (widget); - - if (self->mnemonics_visible != visible) - { - self->mnemonics_visible = visible; - gtk_label_recalculate (self); - } - } - else - { - GtkWidget *child; - - for (child = gtk_widget_get_first_child (widget); - child; - child = gtk_widget_get_next_sibling (child)) - { - if (GTK_IS_NATIVE (child)) - continue; - - _gtk_label_mnemonics_visible_apply_recursively (child, visible); - } - } -} -static void label_mnemonic_widget_weak_notify (gpointer data, - GObject *where_the_object_was) + GObject *where_the_object_was) { GtkLabel *self = data; @@ -1613,14 +2787,14 @@ label_mnemonic_widget_weak_notify (gpointer data, * (i.e. when the target is a #GtkEntry next to the label) you need to * set it explicitly using this function. * - * The target widget will be accelerated by emitting the - * GtkWidget::mnemonic-activate signal on it. The default handler for - * this signal will activate the widget if there are no mnemonic collisions + * The target widget will be accelerated by emitting the + * GtkWidget::mnemonic-activate signal on it. The default handler for + * this signal will activate the widget if there are no mnemonic collisions * and toggle focus between the colliding widgets otherwise. **/ void gtk_label_set_mnemonic_widget (GtkLabel *self, - GtkWidget *widget) + GtkWidget *widget) { g_return_if_fail (GTK_IS_LABEL (self)); @@ -2106,14 +3280,6 @@ xml_isspace (char c) return (c == ' ' || c == '\t' || c == '\n' || c == '\r'); } -static void -link_free (GtkLabelLink *link) -{ - gtk_css_node_set_parent (link->cssnode, NULL); - g_free (link->uri); - g_free (link->title); -} - static gboolean parse_uri_markup (GtkLabel *self, const char *str, @@ -2364,7 +3530,6 @@ no_uline: if (text) gtk_label_set_text_internal (self, text); - g_clear_pointer (&self->markup_attrs, pango_attr_list_unref); self->markup_attrs = attrs; @@ -2470,11 +3635,11 @@ gtk_label_set_markup_with_mnemonic (GtkLabel *self, /** * gtk_label_get_text: * @self: a #GtkLabel - * + * * Fetches the text from a label widget, as displayed on the * screen. This does not include any embedded underlines * indicating mnemonics or Pango markup. (See gtk_label_get_label()) - * + * * Returns: the text in the label widget. This is the internal * string used by the label, and must not be modified. **/ @@ -2500,7 +3665,7 @@ gtk_label_get_text (GtkLabel *self) */ void gtk_label_set_justify (GtkLabel *self, - GtkJustification jtype) + GtkJustification jtype) { g_return_if_fail (GTK_IS_LABEL (self)); g_return_if_fail (jtype >= GTK_JUSTIFY_LEFT && jtype <= GTK_JUSTIFY_FILL); @@ -2511,7 +3676,7 @@ gtk_label_set_justify (GtkLabel *self, /* No real need to be this drastic, but easier than duplicating the code */ gtk_label_clear_layout (self); - + g_object_notify_by_pspec (G_OBJECT (self), label_props[PROP_JUSTIFY]); gtk_widget_queue_resize (GTK_WIDGET (self)); } @@ -2538,12 +3703,12 @@ gtk_label_get_justify (GtkLabel *self) * @self: a #GtkLabel * @mode: a #PangoEllipsizeMode * - * Sets the mode used to ellipsize (add an ellipsis: "...") to the text + * Sets the mode used to ellipsize (add an ellipsis: "...") to the text * if there is not enough space to render the entire string. **/ void gtk_label_set_ellipsize (GtkLabel *self, - PangoEllipsizeMode mode) + PangoEllipsizeMode mode) { g_return_if_fail (GTK_IS_LABEL (self)); g_return_if_fail (mode >= PANGO_ELLIPSIZE_NONE && mode <= PANGO_ELLIPSIZE_END); @@ -2580,12 +3745,12 @@ gtk_label_get_ellipsize (GtkLabel *self) * gtk_label_set_width_chars: * @self: a #GtkLabel * @n_chars: the new desired width, in characters. - * + * * Sets the desired width in characters of @label to @n_chars. **/ void gtk_label_set_width_chars (GtkLabel *self, - int n_chars) + int n_chars) { g_return_if_fail (GTK_IS_LABEL (self)); @@ -2600,10 +3765,10 @@ gtk_label_set_width_chars (GtkLabel *self, /** * gtk_label_get_width_chars: * @self: a #GtkLabel - * + * * Retrieves the desired width of @label, in characters. See * gtk_label_set_width_chars(). - * + * * Returns: the width of the label in characters. **/ int @@ -2618,12 +3783,12 @@ gtk_label_get_width_chars (GtkLabel *self) * gtk_label_set_max_width_chars: * @self: a #GtkLabel * @n_chars: the new desired maximum width, in characters. - * + * * Sets the desired maximum width in characters of @label to @n_chars. **/ void gtk_label_set_max_width_chars (GtkLabel *self, - int n_chars) + int n_chars) { g_return_if_fail (GTK_IS_LABEL (self)); @@ -2639,10 +3804,10 @@ gtk_label_set_max_width_chars (GtkLabel *self, /** * gtk_label_get_max_width_chars: * @self: a #GtkLabel - * + * * Retrieves the desired maximum width of @label, in characters. See * gtk_label_set_width_chars(). - * + * * Returns: the maximum width of the label in characters. **/ int @@ -2744,194 +3909,11 @@ gtk_label_get_wrap_mode (GtkLabel *self) } static void -gtk_label_dispose (GObject *object) -{ - GtkLabel *self = GTK_LABEL (object); - - gtk_label_set_mnemonic_widget (self, NULL); - - G_OBJECT_CLASS (gtk_label_parent_class)->dispose (object); -} - -static void -gtk_label_finalize (GObject *object) -{ - GtkLabel *self = GTK_LABEL (object); - - g_free (self->label); - g_free (self->text); - - g_clear_object (&self->layout); - g_clear_pointer (&self->attrs, pango_attr_list_unref); - g_clear_pointer (&self->markup_attrs, pango_attr_list_unref); - - if (self->select_info) - g_object_unref (self->select_info->provider); - - gtk_label_clear_links (self); - g_free (self->select_info); - - g_clear_pointer (&self->popup_menu, gtk_widget_unparent); - g_clear_object (&self->extra_menu); - - G_OBJECT_CLASS (gtk_label_parent_class)->finalize (object); -} - -static void gtk_label_clear_layout (GtkLabel *self) { g_clear_object (&self->layout); } -/** - * gtk_label_get_measuring_layout: - * @self: the label - * @existing_layout: %NULL or an existing layout already in use. - * @width: the width to measure with in pango units, or -1 for infinite - * - * Gets a layout that can be used for measuring sizes. The returned - * layout will be identical to the label’s layout except for the - * layout’s width, which will be set to @width. Do not modify the returned - * layout. - * - * Returns: a new reference to a pango layout - **/ -static PangoLayout * -gtk_label_get_measuring_layout (GtkLabel *self, - PangoLayout *existing_layout, - int width) -{ - PangoLayout *copy; - - if (existing_layout != NULL) - { - if (existing_layout != self->layout) - { - pango_layout_set_width (existing_layout, width); - return existing_layout; - } - - g_object_unref (existing_layout); - } - - gtk_label_ensure_layout (self); - - if (pango_layout_get_width (self->layout) == width) - { - g_object_ref (self->layout); - return self->layout; - } - - /* We can use the label's own layout if we're not allocated a size yet, - * because we don't need it to be properly setup at that point. - * This way we can make use of caching upon the label's creation. - */ - if (gtk_widget_get_width (GTK_WIDGET (self)) <= 1) - { - g_object_ref (self->layout); - pango_layout_set_width (self->layout, width); - return self->layout; - } - - /* oftentimes we want to measure a width that is far wider than the current width, - * even though the layout would not change if we made it wider. In that case, we - * can just return the current layout, because for measuring purposes, it will be - * identical. - */ - if (!pango_layout_is_wrapped (self->layout) && - !pango_layout_is_ellipsized (self->layout)) - { - PangoRectangle rect; - - if (width == -1) - return g_object_ref (self->layout); - - pango_layout_get_extents (self->layout, NULL, &rect); - if (rect.width <= width) - return g_object_ref (self->layout); - } - - copy = pango_layout_copy (self->layout); - pango_layout_set_width (copy, width); - return copy; -} - -static void -gtk_label_update_layout_attributes (GtkLabel *self, - PangoAttrList *style_attrs) -{ - GtkWidget *widget = GTK_WIDGET (self); - GtkCssStyle *style; - PangoAttrList *attrs; - - if (self->layout == NULL) - { - pango_attr_list_unref (style_attrs); - return; - } - - if (self->select_info && self->select_info->links) - { - guint i; - - attrs = pango_attr_list_new (); - - for (i = 0; i < self->select_info->n_links; i++) - { - const GtkLabelLink *link = &self->select_info->links[i]; - const GdkRGBA *link_color; - PangoAttrList *link_attrs; - PangoAttribute *attr; - - style = gtk_css_node_get_style (link->cssnode); - link_attrs = gtk_css_style_get_pango_attributes (style); - if (link_attrs) - { - GSList *attributes = pango_attr_list_get_attributes (link_attrs); - GSList *l; - for (l = attributes; l; l = l->next) - { - attr = l->data; - - attr->start_index = link->start; - attr->end_index = link->end; - pango_attr_list_insert (attrs, attr); - } - g_slist_free (attributes); - } - - link_color = gtk_css_color_value_get_rgba (style->core->color); - attr = pango_attr_foreground_new (link_color->red * 65535, - link_color->green * 65535, - link_color->blue * 65535); - attr->start_index = link->start; - attr->end_index = link->end; - pango_attr_list_insert (attrs, attr); - - pango_attr_list_unref (link_attrs); - } - } - else - attrs = NULL; - - style = gtk_css_node_get_style (gtk_widget_get_css_node (widget)); - if (!style_attrs) - style_attrs = gtk_css_style_get_pango_attributes (style); - - if (style_attrs) - { - attrs = _gtk_pango_attr_list_merge (attrs, style_attrs); - pango_attr_list_unref (style_attrs); - } - - attrs = _gtk_pango_attr_list_merge (attrs, self->markup_attrs); - attrs = _gtk_pango_attr_list_merge (attrs, self->attrs); - - pango_layout_set_attributes (self->layout, attrs); - - pango_attr_list_unref (attrs); -} - static void gtk_label_ensure_layout (GtkLabel *self) { @@ -2977,564 +3959,6 @@ gtk_label_ensure_layout (GtkLabel *self) pango_layout_set_width (self->layout, gtk_widget_get_width (GTK_WIDGET (self)) * PANGO_SCALE); } -static GtkSizeRequestMode -gtk_label_get_request_mode (GtkWidget *widget) -{ - GtkLabel *self = GTK_LABEL (widget); - - if (self->wrap) - return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH; - - return GTK_SIZE_REQUEST_CONSTANT_SIZE; -} - - -static void -get_height_for_width (GtkLabel *self, - int width, - int *minimum_height, - int *natural_height, - int *minimum_baseline, - int *natural_baseline) -{ - PangoLayout *layout; - int text_height, baseline; - - layout = gtk_label_get_measuring_layout (self, NULL, width * PANGO_SCALE); - - pango_layout_get_pixel_size (layout, NULL, &text_height); - - *minimum_height = text_height; - *natural_height = text_height; - - baseline = pango_layout_get_baseline (layout) / PANGO_SCALE; - *minimum_baseline = baseline; - *natural_baseline = baseline; - - g_object_unref (layout); -} - -static int -get_char_pixels (GtkWidget *self, - PangoLayout *layout) -{ - PangoContext *context; - PangoFontMetrics *metrics; - int char_width, digit_width; - - context = pango_layout_get_context (layout); - metrics = pango_context_get_metrics (context, - pango_context_get_font_description (context), - pango_context_get_language (context)); - char_width = pango_font_metrics_get_approximate_char_width (metrics); - digit_width = pango_font_metrics_get_approximate_digit_width (metrics); - pango_font_metrics_unref (metrics); - - return MAX (char_width, digit_width);; -} - -static void -gtk_label_get_preferred_layout_size (GtkLabel *self, - PangoRectangle *smallest, - PangoRectangle *widest, - int *smallest_baseline, - int *widest_baseline) -{ - PangoLayout *layout; - int char_pixels; - - /* "width-chars" Hard-coded minimum width: - * - minimum size should be MAX (width-chars, strlen ("...")); - * - natural size should be MAX (width-chars, strlen (self->text)); - * - * "max-width-chars" User specified maximum size requisition - * - minimum size should be MAX (width-chars, 0) - * - natural size should be MIN (max-width-chars, strlen (self->text)) - * - * For ellipsizing labels; if max-width-chars is specified: either it is used as - * a minimum size or the label text as a minimum size (natural size still overflows). - * - * For wrapping labels; A reasonable minimum size is useful to naturally layout - * interfaces automatically. In this case if no "width-chars" is specified, the minimum - * width will default to the wrap guess that gtk_label_ensure_layout() does. - */ - - /* Start off with the pixel extents of an as-wide-as-possible layout */ - layout = gtk_label_get_measuring_layout (self, NULL, -1); - - if (self->width_chars > -1 || self->max_width_chars > -1) - char_pixels = get_char_pixels (GTK_WIDGET (self), layout); - else - char_pixels = 0; - - pango_layout_get_extents (layout, NULL, widest); - widest->width = MAX (widest->width, char_pixels * self->width_chars); - widest->x = widest->y = 0; - *widest_baseline = pango_layout_get_baseline (layout) / PANGO_SCALE; - - if (self->ellipsize || self->wrap) - { - /* a layout with width 0 will be as small as humanly possible */ - layout = gtk_label_get_measuring_layout (self, - layout, - self->width_chars > -1 ? char_pixels * self->width_chars - : 0); - - pango_layout_get_extents (layout, NULL, smallest); - smallest->width = MAX (smallest->width, char_pixels * self->width_chars); - smallest->x = smallest->y = 0; - - *smallest_baseline = pango_layout_get_baseline (layout) / PANGO_SCALE; - - if (self->max_width_chars > -1 && widest->width > char_pixels * self->max_width_chars) - { - layout = gtk_label_get_measuring_layout (self, - layout, - MAX (smallest->width, char_pixels * self->max_width_chars)); - pango_layout_get_extents (layout, NULL, widest); - widest->width = MAX (widest->width, char_pixels * self->width_chars); - widest->x = widest->y = 0; - - *widest_baseline = pango_layout_get_baseline (layout) / PANGO_SCALE; - } - - if (widest->width < smallest->width) - { - *smallest = *widest; - *smallest_baseline = *widest_baseline; - } - } - else - { - *smallest = *widest; - *smallest_baseline = *widest_baseline; - } - - g_object_unref (layout); -} - -static void -gtk_label_get_preferred_size (GtkWidget *widget, - GtkOrientation orientation, - int *minimum_size, - int *natural_size, - int *minimum_baseline, - int *natural_baseline) -{ - GtkLabel *self = GTK_LABEL (widget); - PangoRectangle widest_rect; - PangoRectangle smallest_rect; - int smallest_baseline; - int widest_baseline; - - gtk_label_get_preferred_layout_size (self, - &smallest_rect, &widest_rect, - &smallest_baseline, &widest_baseline); - - widest_rect.width = PANGO_PIXELS_CEIL (widest_rect.width); - widest_rect.height = PANGO_PIXELS_CEIL (widest_rect.height); - - smallest_rect.width = PANGO_PIXELS_CEIL (smallest_rect.width); - smallest_rect.height = PANGO_PIXELS_CEIL (smallest_rect.height); - - if (orientation == GTK_ORIENTATION_HORIZONTAL) - { - /* Normal desired width */ - *minimum_size = smallest_rect.width; - *natural_size = widest_rect.width; - - if (minimum_baseline) - *minimum_baseline = -1; - - if (natural_baseline) - *natural_baseline = -1; - } - else /* GTK_ORIENTATION_VERTICAL */ - { - if (smallest_rect.height < widest_rect.height) - { - *minimum_size = smallest_rect.height; - *natural_size = widest_rect.height; - if (minimum_baseline) - *minimum_baseline = smallest_baseline; - if (natural_baseline) - *natural_baseline = widest_baseline; - } - else - { - *minimum_size = widest_rect.height; - *natural_size = smallest_rect.height; - if (minimum_baseline) - *minimum_baseline = widest_baseline; - if (natural_baseline) - *natural_baseline = smallest_baseline; - } - } -} - -static void -gtk_label_measure (GtkWidget *widget, - GtkOrientation orientation, - int for_size, - int *minimum, - int *natural, - int *minimum_baseline, - int *natural_baseline) -{ - GtkLabel *self = GTK_LABEL (widget); - - if (orientation == GTK_ORIENTATION_VERTICAL && for_size != -1 && self->wrap) - { - gtk_label_clear_layout (self); - - get_height_for_width (self, for_size, minimum, natural, minimum_baseline, natural_baseline); - } - else - gtk_label_get_preferred_size (widget, orientation, minimum, natural, minimum_baseline, natural_baseline); -} - -static void -get_layout_location (GtkLabel *self, - int *xp, - int *yp) -{ - GtkWidget *widget = GTK_WIDGET (self); - int layout_width, layout_height, x, y; - float xalign, yalign; - PangoRectangle logical; - int baseline, layout_baseline, baseline_offset; - int widget_width, widget_height; - - xalign = self->xalign; - yalign = self->yalign; - - if (_gtk_widget_get_direction (widget) != GTK_TEXT_DIR_LTR) - xalign = 1.0 - xalign; - - pango_layout_get_pixel_extents (self->layout, NULL, &logical); - - layout_width = logical.width; - layout_height = logical.height; - - widget_width = gtk_widget_get_width (widget); - widget_height = gtk_widget_get_height (widget); - - baseline = gtk_widget_get_allocated_baseline (widget); - - x = floor ((xalign * (widget_width - layout_width)) - logical.x); - - baseline_offset = 0; - if (baseline != -1) - { - layout_baseline = pango_layout_get_baseline (self->layout) / PANGO_SCALE; - baseline_offset = baseline - layout_baseline; - yalign = 0.0; /* Can't support yalign while baseline aligning */ - } - - y = floor ((widget_height - layout_height) * yalign) + baseline_offset; - - if (xp) - *xp = x; - - if (yp) - *yp = y; -} - -static void -gtk_label_size_allocate (GtkWidget *widget, - int width, - int height, - int baseline) -{ - GtkLabel *self = GTK_LABEL (widget); - - if (self->layout) - { - if (self->ellipsize || self->wrap) - pango_layout_set_width (self->layout, width * PANGO_SCALE); - else - pango_layout_set_width (self->layout, -1); - } - - if (self->popup_menu) - gtk_popover_present (GTK_POPOVER (self->popup_menu)); -} - -static void -gtk_label_update_cursor (GtkLabel *self) -{ - GtkWidget *widget = GTK_WIDGET (self); - - if (!self->select_info) - return; - - if (gtk_widget_is_sensitive (widget)) - { - if (self->select_info->active_link) - gtk_widget_set_cursor_from_name (widget, "pointer"); - else if (self->select_info->selectable) - gtk_widget_set_cursor_from_name (widget, "text"); - else - gtk_widget_set_cursor (widget, NULL); - } - else - gtk_widget_set_cursor (widget, NULL); -} - -static void -update_link_state (GtkLabel *self) -{ - GtkStateFlags state; - guint i; - - if (!self->select_info) - return; - - for (i = 0; i < self->select_info->n_links; i++) - { - const GtkLabelLink *link = &self->select_info->links[i]; - - state = gtk_widget_get_state_flags (GTK_WIDGET (self)); - if (link->visited) - state |= GTK_STATE_FLAG_VISITED; - else - state |= GTK_STATE_FLAG_LINK; - if (link == self->select_info->active_link) - { - if (self->select_info->link_clicked) - state |= GTK_STATE_FLAG_ACTIVE; - else - state |= GTK_STATE_FLAG_PRELIGHT; - } - gtk_css_node_set_state (link->cssnode, state); - } -} - -static void -gtk_label_state_flags_changed (GtkWidget *widget, - GtkStateFlags prev_state) -{ - GtkLabel *self = GTK_LABEL (widget); - - if (self->select_info) - { - if (!gtk_widget_is_sensitive (widget)) - gtk_label_select_region (self, 0, 0); - - gtk_label_update_cursor (self); - update_link_state (self); - } - - if (GTK_WIDGET_CLASS (gtk_label_parent_class)->state_flags_changed) - GTK_WIDGET_CLASS (gtk_label_parent_class)->state_flags_changed (widget, prev_state); -} - -static void -gtk_label_css_changed (GtkWidget *widget, - GtkCssStyleChange *change) -{ - GtkLabel *self = GTK_LABEL (widget); - gboolean attrs_affected; - PangoAttrList *new_attrs = NULL; - - GTK_WIDGET_CLASS (gtk_label_parent_class)->css_changed (widget, change); - - if (gtk_css_style_change_affects (change, GTK_CSS_AFFECTS_TEXT_ATTRS)) - { - new_attrs = gtk_css_style_get_pango_attributes (gtk_css_style_change_get_new_style (change)); - attrs_affected = (self->layout && pango_layout_get_attributes (self->layout)) || - new_attrs; - } - else - attrs_affected = FALSE; - - if (change == NULL || attrs_affected || (self->select_info && self->select_info->links)) - { - gtk_label_update_layout_attributes (self, new_attrs); - - if (attrs_affected) - gtk_widget_queue_draw (widget); - } -} - -static PangoDirection -get_cursor_direction (GtkLabel *self) -{ - GSList *l; - - g_assert (self->select_info); - - gtk_label_ensure_layout (self); - - for (l = pango_layout_get_lines_readonly (self->layout); l; l = l->next) - { - PangoLayoutLine *line = l->data; - - /* If self->select_info->selection_end is at the very end of - * the line, we don't know if the cursor is on this line or - * the next without looking ahead at the next line. (End - * of paragraph is different from line break.) But it's - * definitely in this paragraph, which is good enough - * to figure out the resolved direction. - */ - if (line->start_index + line->length >= self->select_info->selection_end) - return line->resolved_dir; - } - - return PANGO_DIRECTION_LTR; -} - -static GtkLabelLink * -gtk_label_get_focus_link (GtkLabel *self, - int *out_index) -{ - GtkLabelSelectionInfo *info = self->select_info; - int link_index; - - if (!info || - info->selection_anchor != info->selection_end) - goto nope; - - link_index = _gtk_label_get_link_at (self, info->selection_anchor); - - if (link_index != -1) - { - if (out_index) - *out_index = link_index; - - return &info->links[link_index]; - } - -nope: - if (out_index) - *out_index = -1; - return NULL; -} - -#define GRAPHENE_RECT_FROM_RECT(_r) (GRAPHENE_RECT_INIT ((_r)->x, (_r)->y, (_r)->width, (_r)->height)) - -static void -gtk_label_snapshot (GtkWidget *widget, - GtkSnapshot *snapshot) -{ - GtkLabel *self = GTK_LABEL (widget); - GtkLabelSelectionInfo *info; - GtkStyleContext *context; - int lx, ly; - int width, height; - - if (!self->text || (*self->text == '\0')) - return; - - gtk_label_ensure_layout (self); - - context = _gtk_widget_get_style_context (widget); - get_layout_location (self, &lx, &ly); - - gtk_snapshot_render_layout (snapshot, context, lx, ly, self->layout); - - info = self->select_info; - if (!info) - return; - - width = gtk_widget_get_width (widget); - height = gtk_widget_get_height (widget); - - if (info->selection_anchor != info->selection_end) - { - int range[2]; - cairo_region_t *range_clip; - cairo_rectangle_int_t clip_rect; - int i; - - range[0] = MIN (info->selection_anchor, info->selection_end); - range[1] = MAX (info->selection_anchor, info->selection_end); - - gtk_style_context_save_to_node (context, info->selection_node); - - range_clip = gdk_pango_layout_get_clip_region (self->layout, lx, ly, range, 1); - for (i = 0; i < cairo_region_num_rectangles (range_clip); i++) - { - cairo_region_get_rectangle (range_clip, i, &clip_rect); - - gtk_snapshot_push_clip (snapshot, &GRAPHENE_RECT_FROM_RECT (&clip_rect)); - gtk_snapshot_render_background (snapshot, context, 0, 0, width, height); - gtk_snapshot_render_layout (snapshot, context, lx, ly, self->layout); - gtk_snapshot_pop (snapshot); - } - - cairo_region_destroy (range_clip); - - gtk_style_context_restore (context); - } - else - { - GtkLabelLink *focus_link; - GtkLabelLink *active_link; - int range[2]; - cairo_region_t *range_clip; - cairo_rectangle_int_t clip_rect; - int i; - GdkRectangle rect; - - if (info->selectable && - gtk_widget_has_focus (widget) && - gtk_widget_is_drawable (widget)) - { - PangoDirection cursor_direction; - - cursor_direction = get_cursor_direction (self); - gtk_snapshot_render_insertion_cursor (snapshot, context, - lx, ly, - self->layout, self->select_info->selection_end, - cursor_direction); - } - - focus_link = gtk_label_get_focus_link (self, NULL); - active_link = info->active_link; - - if (active_link) - { - range[0] = active_link->start; - range[1] = active_link->end; - - gtk_style_context_save_to_node (context, active_link->cssnode); - - range_clip = gdk_pango_layout_get_clip_region (self->layout, lx, ly, range, 1); - for (i = 0; i < cairo_region_num_rectangles (range_clip); i++) - { - cairo_region_get_rectangle (range_clip, i, &clip_rect); - - gtk_snapshot_push_clip (snapshot, &GRAPHENE_RECT_FROM_RECT (&clip_rect)); - gtk_snapshot_render_background (snapshot, context, 0, 0, width, height); - gtk_snapshot_render_layout (snapshot, context, lx, ly, self->layout); - gtk_snapshot_pop (snapshot); - } - - cairo_region_destroy (range_clip); - - gtk_style_context_restore (context); - } - - if (focus_link && gtk_widget_has_visible_focus (widget)) - { - range[0] = focus_link->start; - range[1] = focus_link->end; - - gtk_style_context_save_to_node (context, focus_link->cssnode); - - range_clip = gdk_pango_layout_get_clip_region (self->layout, lx, ly, range, 1); - cairo_region_get_extents (range_clip, &rect); - - gtk_snapshot_render_focus (snapshot, context, rect.x, rect.y, rect.width, rect.height); - - cairo_region_destroy (range_clip); - - gtk_style_context_restore (context); - } - } -} - /** * gtk_label_set_text_with_mnemonic: * @self: a #GtkLabel @@ -3567,124 +3991,55 @@ gtk_label_set_text_with_mnemonic (GtkLabel *self, g_object_thaw_notify (G_OBJECT (self)); } -static void -gtk_label_unrealize (GtkWidget *widget) +static int +gtk_label_move_forward_word (GtkLabel *self, + int start) { - GtkLabel *self = GTK_LABEL (widget); + int new_pos = g_utf8_pointer_to_offset (self->text, self->text + start); + int length; - if (self->select_info && - self->select_info->provider) + length = g_utf8_strlen (self->text, -1); + if (new_pos < length) { - GdkClipboard *clipboard = gtk_widget_get_primary_clipboard (widget); - - if (gdk_clipboard_get_content (clipboard) == self->select_info->provider) - gdk_clipboard_set_content (clipboard, NULL); - } - - GTK_WIDGET_CLASS (gtk_label_parent_class)->unrealize (widget); -} - -static gboolean -get_layout_index (GtkLabel *self, - int x, - int y, - int *index) -{ - int trailing = 0; - const char *cluster; - const char *cluster_end; - gboolean inside; - int lx, ly; - - *index = 0; - - gtk_label_ensure_layout (self); - get_layout_location (self, &lx, &ly); - - /* Translate x/y to layout position */ - x -= lx; - y -= ly; + const PangoLogAttr *log_attrs; + int n_attrs; - x *= PANGO_SCALE; - y *= PANGO_SCALE; + gtk_label_ensure_layout (self); - inside = pango_layout_xy_to_index (self->layout, - x, y, - index, &trailing); + log_attrs = pango_layout_get_log_attrs_readonly (self->layout, &n_attrs); - cluster = self->text + *index; - cluster_end = cluster; - while (trailing) - { - cluster_end = g_utf8_next_char (cluster_end); - --trailing; + /* Find the next word end */ + new_pos++; + while (new_pos < n_attrs && !log_attrs[new_pos].is_word_end) + new_pos++; } - *index += (cluster_end - cluster); - - return inside; + return g_utf8_offset_to_pointer (self->text, new_pos) - self->text; } -static gboolean -range_is_in_ellipsis_full (GtkLabel *self, - int range_start, - int range_end, - int *ellipsis_start, - int *ellipsis_end) +static int +gtk_label_move_backward_word (GtkLabel *self, + int start) { - PangoLayoutIter *iter; - gboolean in_ellipsis; - - if (!self->ellipsize) - return FALSE; - - gtk_label_ensure_layout (self); + int new_pos = g_utf8_pointer_to_offset (self->text, self->text + start); - if (!pango_layout_is_ellipsized (self->layout)) - return FALSE; - - iter = pango_layout_get_iter (self->layout); - - in_ellipsis = FALSE; - - do { - PangoLayoutRun *run; - - run = pango_layout_iter_get_run_readonly (iter); - if (run) - { - PangoItem *item; + if (new_pos > 0) + { + const PangoLogAttr *log_attrs; + int n_attrs; - item = ((PangoGlyphItem*)run)->item; + gtk_label_ensure_layout (self); - if (item->offset <= range_start && range_end <= item->offset + item->length) - { - if (item->analysis.flags & PANGO_ANALYSIS_FLAG_IS_ELLIPSIS) - { - if (ellipsis_start) - *ellipsis_start = item->offset; - if (ellipsis_end) - *ellipsis_end = item->offset + item->length; - in_ellipsis = TRUE; - } - break; - } - else if (item->offset + item->length >= range_end) - break; - } - } while (pango_layout_iter_next_run (iter)); + log_attrs = pango_layout_get_log_attrs_readonly (self->layout, &n_attrs); - pango_layout_iter_free (iter); + new_pos -= 1; - return in_ellipsis; -} + /* Find the previous word beginning */ + while (new_pos > 0 && !log_attrs[new_pos].is_word_start) + new_pos--; + } -static gboolean -range_is_in_ellipsis (GtkLabel *self, - int range_start, - int range_end) -{ - return range_is_in_ellipsis_full (self, range_start, range_end, NULL, NULL); + return g_utf8_offset_to_pointer (self->text, new_pos) - self->text; } static void @@ -3696,9 +4051,9 @@ gtk_label_select_word (GtkLabel *self) int end_index = gtk_label_move_forward_word (self, self->select_info->selection_end); min = MIN (self->select_info->selection_anchor, - self->select_info->selection_end); + self->select_info->selection_end); max = MAX (self->select_info->selection_anchor, - self->select_info->selection_end); + self->select_info->selection_end); min = MIN (min, start_index); max = MAX (max, end_index); @@ -3706,207 +4061,6 @@ gtk_label_select_word (GtkLabel *self) gtk_label_select_region_index (self, min, max); } -static gboolean -gtk_label_grab_focus (GtkWidget *widget) -{ - GtkLabel *self = GTK_LABEL (widget); - gboolean select_on_focus; - GtkWidget *prev_focus; - - if (self->select_info == NULL) - return FALSE; - - prev_focus = gtk_root_get_focus (gtk_widget_get_root (widget)); - - if (!GTK_WIDGET_CLASS (gtk_label_parent_class)->grab_focus (widget)) - return FALSE; - - if (self->select_info->selectable) - { - g_object_get (gtk_widget_get_settings (widget), - "gtk-label-select-on-focus", - &select_on_focus, - NULL); - - if (select_on_focus && !self->in_click && - !(prev_focus && gtk_widget_is_ancestor (prev_focus, widget))) - gtk_label_select_region (self, 0, -1); - } - else - { - if (self->select_info->links && !self->in_click && - !(prev_focus && gtk_widget_is_ancestor (prev_focus, widget))) - { - guint i; - - for (i = 0; i < self->select_info->n_links; i++) - { - const GtkLabelLink *link = &self->select_info->links[i]; - - if (!range_is_in_ellipsis (self, link->start, link->end)) - { - self->select_info->selection_anchor = link->start; - self->select_info->selection_end = link->start; - break; - } - } - } - } - - return TRUE; -} - -static gboolean -gtk_label_focus (GtkWidget *widget, - GtkDirectionType direction) -{ - GtkLabel *self = GTK_LABEL (widget); - GtkLabelSelectionInfo *info = self->select_info; - GtkLabelLink *focus_link; - - if (!gtk_widget_is_focus (widget)) - { - gtk_widget_grab_focus (widget); - if (info) - { - focus_link = gtk_label_get_focus_link (self, NULL); - if (focus_link && direction == GTK_DIR_TAB_BACKWARD) - { - int i; - for (i = info->n_links - 1; i >= 0; i--) - { - focus_link = &info->links[i]; - if (!range_is_in_ellipsis (self, focus_link->start, focus_link->end)) - { - info->selection_anchor = focus_link->start; - info->selection_end = focus_link->start; - } - } - } - - return TRUE; - } - - return FALSE; - } - - if (!info) - return FALSE; - - if (info->selectable) - { - int index; - - if (info->selection_anchor != info->selection_end) - goto out; - - index = info->selection_anchor; - - if (direction == GTK_DIR_TAB_FORWARD) - { - guint i; - for (i = 0; i < info->n_links; i++) - { - const GtkLabelLink *link = &info->links[i]; - - if (link->start > index) - { - if (!range_is_in_ellipsis (self, link->start, link->end)) - { - gtk_label_select_region_index (self, link->start, link->start); - return TRUE; - } - } - } - } - else if (direction == GTK_DIR_TAB_BACKWARD) - { - int i; - for (i = info->n_links - 1; i >= 0; i--) - { - GtkLabelLink *link = &info->links[i]; - - if (link->end < index) - { - if (!range_is_in_ellipsis (self, link->start, link->end)) - { - gtk_label_select_region_index (self, link->start, link->start); - return TRUE; - } - } - } - } - - goto out; - } - else - { - int focus_link_index; - int new_index = -1; - int i; - - if (info->n_links == 0) - goto out; - - focus_link = gtk_label_get_focus_link (self, &focus_link_index); - - if (!focus_link) - goto out; - - switch (direction) - { - case GTK_DIR_TAB_FORWARD: - if (focus_link) - new_index = (focus_link_index + 1) % info->n_links; - else - new_index = 0; - - for (i = new_index; i < info->n_links; i++) - { - const GtkLabelLink *link = &info->links[i]; - if (!range_is_in_ellipsis (self, link->start, link->end)) - break; - } - break; - - case GTK_DIR_TAB_BACKWARD: - if (focus_link) - new_index = focus_link_index == 0 ? info->n_links - 1 : focus_link_index - 1; - else - new_index = info->n_links - 1; - - for (i = new_index; i >= 0; i--) - { - const GtkLabelLink *link = &info->links[i]; - if (!range_is_in_ellipsis (self, link->start, link->end)) - break; - } - break; - - default: - case GTK_DIR_UP: - case GTK_DIR_DOWN: - case GTK_DIR_LEFT: - case GTK_DIR_RIGHT: - goto out; - } - - if (new_index != -1) - { - focus_link = &info->links[new_index]; - info->selection_anchor = focus_link->start; - info->selection_end = focus_link->start; - gtk_widget_queue_draw (widget); - - return TRUE; - } - } - -out: - - return FALSE; -} - static void gtk_label_click_gesture_pressed (GtkGestureClick *gesture, int n_press, @@ -4139,7 +4293,7 @@ gtk_label_drag_gesture_update (GtkGestureDrag *gesture, if (info->in_drag) { if (gtk_drag_check_threshold (widget, info->drag_start_x, info->drag_start_y, x, y)) - { + { GdkDrag *drag; GdkSurface *surface; GdkDevice *device; @@ -4157,9 +4311,8 @@ gtk_label_drag_gesture_update (GtkGestureDrag *gesture, gtk_drag_icon_set_from_paintable (drag, get_selection_paintable (self), 0, 0); g_object_unref (drag); - - info->in_drag = FALSE; - } + info->in_drag = FALSE; + } } else { @@ -4212,6 +4365,34 @@ gtk_label_drag_gesture_update (GtkGestureDrag *gesture, } static void +gtk_label_update_actions (GtkLabel *self) +{ + GtkWidget *widget = GTK_WIDGET (self); + gboolean has_selection; + GtkLabelLink *link; + + if (self->select_info) + { + has_selection = self->select_info->selection_anchor != self->select_info->selection_end; + link = self->select_info->active_link; + } + else + { + has_selection = FALSE; + link = gtk_label_get_focus_link (self, NULL); + } + + gtk_widget_action_set_enabled (widget, "clipboard.cut", FALSE); + gtk_widget_action_set_enabled (widget, "clipboard.copy", has_selection); + gtk_widget_action_set_enabled (widget, "clipboard.paste", FALSE); + gtk_widget_action_set_enabled (widget, "selection.select-all", + gtk_label_get_selectable (self)); + gtk_widget_action_set_enabled (widget, "selection.delete", FALSE); + gtk_widget_action_set_enabled (widget, "link.open", !has_selection && link); + gtk_widget_action_set_enabled (widget, "link.copy", !has_selection && link); +} + +static void gtk_label_update_active_link (GtkWidget *widget, double x, double y) @@ -4530,9 +4711,9 @@ gtk_label_set_selectable (GtkLabel *self, /** * gtk_label_get_selectable: * @self: a #GtkLabel - * + * * Gets the value set by gtk_label_set_selectable(). - * + * * Returns: %TRUE if the user can copy text from the label **/ gboolean @@ -4678,10 +4859,10 @@ gtk_label_select_region (GtkLabel *self, { if (start_offset < 0) start_offset = g_utf8_strlen (self->text, -1); - + if (end_offset < 0) end_offset = g_utf8_strlen (self->text, -1); - + gtk_label_select_region_index (self, g_utf8_offset_to_pointer (self->text, start_offset) - self->text, g_utf8_offset_to_pointer (self->text, end_offset) - self->text); @@ -4693,10 +4874,10 @@ gtk_label_select_region (GtkLabel *self, * @self: a #GtkLabel * @start: (out): return location for start of selection, as a character offset * @end: (out): return location for end of selection, as a character offset - * + * * Gets the selected range of characters in the label, returning %TRUE * if there’s a selection. - * + * * Returns: %TRUE if selection is non-empty **/ gboolean @@ -4721,7 +4902,7 @@ gtk_label_get_selection_bounds (GtkLabel *self, int start_index, end_index; int start_offset, end_offset; int len; - + start_index = MIN (self->select_info->selection_anchor, self->select_info->selection_end); end_index = MAX (self->select_info->selection_anchor, @@ -4734,7 +4915,7 @@ gtk_label_get_selection_bounds (GtkLabel *self, if (start_index > len) start_index = len; - + start_offset = g_utf8_strlen (self->text, start_index); end_offset = g_utf8_strlen (self->text, end_index); @@ -4744,7 +4925,7 @@ gtk_label_get_selection_bounds (GtkLabel *self, start_offset = end_offset; end_offset = tmp; } - + if (start) *start = start_offset; @@ -4759,7 +4940,7 @@ gtk_label_get_selection_bounds (GtkLabel *self, /** * gtk_label_get_layout: * @self: a #GtkLabel - * + * * Gets the #PangoLayout used to display the label. * The layout is useful to e.g. convert text positions to * pixel positions, in combination with gtk_label_get_layout_offsets(). @@ -4989,11 +5170,10 @@ get_better_cursor (GtkLabel *self, static int gtk_label_move_logically (GtkLabel *self, - int start, - int count) + int start, + int count) { - int offset = g_utf8_pointer_to_offset (self->text, - self->text + start); + int offset = g_utf8_pointer_to_offset (self->text, self->text + start); if (self->text) { @@ -5008,21 +5188,21 @@ gtk_label_move_logically (GtkLabel *self, log_attrs = pango_layout_get_log_attrs_readonly (self->layout, &n_attrs); while (count > 0 && offset < length) - { - do - offset++; - while (offset < length && !log_attrs[offset].is_cursor_position); - - count--; - } + { + do + offset++; + while (offset < length && !log_attrs[offset].is_cursor_position); + + count--; + } while (count < 0 && offset > 0) - { - do - offset--; - while (offset > 0 && !log_attrs[offset].is_cursor_position); - - count++; - } + { + do + offset--; + while (offset > 0 && !log_attrs[offset].is_cursor_position); + + count++; + } } return g_utf8_offset_to_pointer (self->text, offset) - self->text; @@ -5030,13 +5210,13 @@ gtk_label_move_logically (GtkLabel *self, static int gtk_label_move_visually (GtkLabel *self, - int start, - int count) + int start, + int count) { int index; index = start; - + while (count != 0) { int new_index, new_trailing; @@ -5046,8 +5226,8 @@ gtk_label_move_visually (GtkLabel *self, gtk_label_ensure_layout (self); g_object_get (gtk_widget_get_settings (GTK_WIDGET (self)), - "gtk-split-cursor", &split_cursor, - NULL); + "gtk-split-cursor", &split_cursor, + NULL); if (split_cursor) strong = TRUE; @@ -5071,80 +5251,26 @@ gtk_label_move_visually (GtkLabel *self, } if (count > 0) - { - pango_layout_move_cursor_visually (self->layout, strong, index, 0, 1, &new_index, &new_trailing); - count--; - } + { + pango_layout_move_cursor_visually (self->layout, strong, index, 0, 1, &new_index, &new_trailing); + count--; + } else - { - pango_layout_move_cursor_visually (self->layout, strong, index, 0, -1, &new_index, &new_trailing); - count++; - } + { + pango_layout_move_cursor_visually (self->layout, strong, index, 0, -1, &new_index, &new_trailing); + count++; + } if (new_index < 0 || new_index == G_MAXINT) - break; + break; index = new_index; - - while (new_trailing--) - index = g_utf8_next_char (self->text + new_index) - self->text; - } - - return index; -} -static int -gtk_label_move_forward_word (GtkLabel *self, - int start) -{ - int new_pos = g_utf8_pointer_to_offset (self->text, - self->text + start); - int length; - - length = g_utf8_strlen (self->text, -1); - if (new_pos < length) - { - const PangoLogAttr *log_attrs; - int n_attrs; - - gtk_label_ensure_layout (self); - - log_attrs = pango_layout_get_log_attrs_readonly (self->layout, &n_attrs); - - /* Find the next word end */ - new_pos++; - while (new_pos < n_attrs && !log_attrs[new_pos].is_word_end) - new_pos++; - } - - return g_utf8_offset_to_pointer (self->text, new_pos) - self->text; -} - - -static int -gtk_label_move_backward_word (GtkLabel *self, - int start) -{ - int new_pos = g_utf8_pointer_to_offset (self->text, - self->text + start); - - if (new_pos > 0) - { - const PangoLogAttr *log_attrs; - int n_attrs; - - gtk_label_ensure_layout (self); - - log_attrs = pango_layout_get_log_attrs_readonly (self->layout, &n_attrs); - - new_pos -= 1; - - /* Find the previous word beginning */ - while (new_pos > 0 && !log_attrs[new_pos].is_word_start) - new_pos--; + while (new_trailing--) + index = g_utf8_next_char (self->text + new_index) - self->text; } - return g_utf8_offset_to_pointer (self->text, new_pos) - self->text; + return index; } static void @@ -5275,135 +5401,6 @@ gtk_label_move_cursor (GtkLabel *self, gtk_label_select_region_index (self, new_pos, new_pos); } -static void -gtk_label_copy_clipboard (GtkLabel *self) -{ - if (self->text && self->select_info) - { - int start, end; - int len; - GdkClipboard *clipboard; - - start = MIN (self->select_info->selection_anchor, - self->select_info->selection_end); - end = MAX (self->select_info->selection_anchor, - self->select_info->selection_end); - - len = strlen (self->text); - - if (end > len) - end = len; - - if (start > len) - start = len; - - clipboard = gtk_widget_get_clipboard (GTK_WIDGET (self)); - - if (start != end) - { - char *str = g_strndup (self->text + start, end - start); - gdk_clipboard_set_text (clipboard, str); - g_free (str); - } - else - { - GtkLabelLink *link; - - link = gtk_label_get_focus_link (self, NULL); - if (link) - gdk_clipboard_set_text (clipboard, link->uri); - } - } -} - -static void -gtk_label_select_all (GtkLabel *self) -{ - gtk_label_select_region_index (self, 0, strlen (self->text)); -} - -static void -gtk_label_activate_link_open (GtkWidget *widget, - const char *name, - GVariant *parameter) -{ - GtkLabel *self = GTK_LABEL (widget); - GtkLabelLink *link = self->select_info->context_link; - - if (link) - emit_activate_link (self, link); -} - -static void -gtk_label_activate_link_copy (GtkWidget *widget, - const char *name, - GVariant *parameter) -{ - GtkLabel *self = GTK_LABEL (widget); - GtkLabelLink *link = self->select_info->context_link; - - if (link) - { - GdkClipboard *clipboard; - - clipboard = gtk_widget_get_clipboard (widget); - gdk_clipboard_set_text (clipboard, link->uri); - } - else - g_print ("no link ?!\n"); -} - -static void -gtk_label_activate_clipboard_copy (GtkWidget *widget, - const char *name, - GVariant *parameter) -{ - g_signal_emit_by_name (widget, "copy-clipboard"); -} - -static void -gtk_label_activate_selection_select_all (GtkWidget *widget, - const char *name, - GVariant *parameter) -{ - gtk_label_select_all (GTK_LABEL (widget)); -} - -static void -gtk_label_nop (GtkWidget *widget, - const char *name, - GVariant *parameter) -{ -} - -static void -gtk_label_update_actions (GtkLabel *self) -{ - GtkWidget *widget = GTK_WIDGET (self); - gboolean has_selection; - GtkLabelLink *link; - - if (self->select_info) - { - has_selection = self->select_info->selection_anchor != self->select_info->selection_end; - link = self->select_info->active_link; - } - else - { - has_selection = FALSE; - link = gtk_label_get_focus_link (self, NULL); - } - - gtk_widget_action_set_enabled (widget, "clipboard.cut", FALSE); - gtk_widget_action_set_enabled (widget, "clipboard.copy", has_selection); - gtk_widget_action_set_enabled (widget, "clipboard.paste", FALSE); - gtk_widget_action_set_enabled (widget, "selection.select-all", - gtk_label_get_selectable (self)); - gtk_widget_action_set_enabled (widget, "selection.delete", FALSE); - gtk_widget_action_set_enabled (widget, "link.open", !has_selection && link); - gtk_widget_action_set_enabled (widget, "link.copy", !has_selection && link); -} - static GMenuModel * gtk_label_get_menu_model (GtkLabel *self) { @@ -5484,82 +5481,6 @@ gtk_label_do_popup (GtkLabel *self, gtk_popover_popup (GTK_POPOVER (self->popup_menu)); } -static void -gtk_label_popup_menu (GtkWidget *widget, - const char *action_name, - GVariant *parameters) -{ - GtkLabel *self = GTK_LABEL (widget); - - gtk_label_do_popup (self, -1, -1); -} - -static void -gtk_label_clear_links (GtkLabel *self) -{ - guint i; - - if (!self->select_info) - return; - - for (i = 0; i < self->select_info->n_links; i++) - link_free (&self->select_info->links[i]); - g_free (self->select_info->links); - self->select_info->links = NULL; - self->select_info->n_links = 0; - self->select_info->active_link = NULL; - gtk_widget_remove_css_class (GTK_WIDGET (self), "link"); -} - -static gboolean -gtk_label_activate_link (GtkLabel *self, - const char *uri) -{ - GtkWidget *widget = GTK_WIDGET (self); - GtkWidget *toplevel = GTK_WIDGET (gtk_widget_get_root (widget)); - - if (!GTK_IS_WINDOW (toplevel)) - return FALSE; - - gtk_show_uri (GTK_WINDOW (toplevel), uri, GDK_CURRENT_TIME); - - return TRUE; -} - -static void -emit_activate_link (GtkLabel *self, - GtkLabelLink *link) -{ - gboolean handled; - - g_signal_emit (self, signals[ACTIVATE_LINK], 0, link->uri, &handled); - - /* signal handler might have invalidated the layout */ - if (!self->layout) - return; - - if (handled && !link->visited && - self->select_info && self->select_info->links) - { - link->visited = TRUE; - update_link_state (self); - } -} - -static void -gtk_label_activate_current_link (GtkLabel *self) -{ - GtkLabelLink *link; - GtkWidget *widget = GTK_WIDGET (self); - - link = gtk_label_get_focus_link (self, NULL); - - if (link) - emit_activate_link (self, link); - else - gtk_widget_activate_default (widget); -} - /** * gtk_label_get_current_uri: * @self: a #GtkLabel @@ -5596,52 +5517,6 @@ gtk_label_get_current_uri (GtkLabel *self) return NULL; } -static gboolean -gtk_label_query_tooltip (GtkWidget *widget, - int x, - int y, - gboolean keyboard_tip, - GtkTooltip *tooltip) -{ - GtkLabel *self = GTK_LABEL (widget); - GtkLabelSelectionInfo *info = self->select_info; - int index = -1; - - if (info && info->links) - { - if (keyboard_tip) - { - if (info->selection_anchor == info->selection_end) - index = info->selection_anchor; - } - else - { - if (!get_layout_index (self, x, y, &index)) - index = -1; - } - - if (index != -1) - { - const int link_index = _gtk_label_get_link_at (self, index); - - if (link_index != -1) - { - const GtkLabelLink *link = &info->links[link_index]; - - if (link->title) - { - gtk_tooltip_set_markup (tooltip, link->title); - } - } - } - } - - return GTK_WIDGET_CLASS (gtk_label_parent_class)->query_tooltip (widget, - x, y, - keyboard_tip, - tooltip); -} - int _gtk_label_get_cursor_position (GtkLabel *self) { @@ -5704,111 +5579,6 @@ gtk_label_get_lines (GtkLabel *self) return self->lines; } -int -_gtk_label_get_n_links (GtkLabel *self) -{ - if (self->select_info) - return self->select_info->n_links; - - return 0; -} - -const char * -_gtk_label_get_link_uri (GtkLabel *self, - int idx) -{ - if (self->select_info) - return self->select_info->links[idx].uri; - - return NULL; -} - -void -_gtk_label_get_link_extent (GtkLabel *self, - int idx, - int *start, - int *end) -{ - if (self->select_info) - { - const GtkLabelLink *link = &self->select_info->links[idx]; - - *start = link->start; - *end = link->end; - } - else - { - *start = -1; - *end = -1; - } -} - -int -_gtk_label_get_link_at (GtkLabel *self, - int pos) -{ - if (self->select_info) - { - guint i; - - for (i = 0; i < self->select_info->n_links; i++) - { - const GtkLabelLink *link = &self->select_info->links[i]; - - if (link->start <= pos && pos < link->end) - return i; - } - } - - return -1; -} - -void -_gtk_label_activate_link (GtkLabel *self, - int idx) -{ - if (self->select_info) - { - GtkLabelLink *link = &self->select_info->links[idx]; - - emit_activate_link (self, link); - } -} - -gboolean -_gtk_label_get_link_visited (GtkLabel *self, - int idx) -{ - if (self->select_info) - return self->select_info->links[idx].visited; - - return FALSE; -} - -gboolean -_gtk_label_get_link_focused (GtkLabel *self, - int idx) -{ - GtkLabelSelectionInfo *info = self->select_info; - - if (!info) - return FALSE; - - if (info->selection_anchor != info->selection_end) - return FALSE; - - if (idx >= 0 && idx < info->n_links) - { - const GtkLabelLink *link = &info->links[idx]; - - if (link->start <= info->selection_anchor && - info->selection_anchor <= link->end) - return TRUE; - } - - return FALSE; -} - /** * gtk_label_set_xalign: * @self: a #GtkLabel @@ -5862,7 +5632,7 @@ gtk_label_set_yalign (GtkLabel *self, { g_return_if_fail (GTK_IS_LABEL (self)); - yalign = CLAMP (yalign, 0.0, 1.0); + yalign = CLAMP (yalign, 0.0, 1.0); if (self->yalign == yalign) return; |