diff options
Diffstat (limited to 'gtk/gtklabel.c')
-rw-r--r-- | gtk/gtklabel.c | 686 |
1 files changed, 637 insertions, 49 deletions
diff --git a/gtk/gtklabel.c b/gtk/gtklabel.c index d8ff48516e..bef3322efc 100644 --- a/gtk/gtklabel.c +++ b/gtk/gtklabel.c @@ -27,9 +27,16 @@ #include <string.h> #include "gtklabel.h" #include "gdk/gdkkeysyms.h" +#include "gtkclipboard.h" #include "gdk/gdki18n.h" #include <pango/pango.h> +struct _GtkLabelSelectionInfo +{ + GdkWindow *window; + gint selection_anchor; + gint selection_end; +}; enum { ARG_0, @@ -50,6 +57,8 @@ static void gtk_label_get_arg (GtkObject *object, static void gtk_label_finalize (GObject *object); static void gtk_label_size_request (GtkWidget *widget, GtkRequisition *requisition); +static void gtk_label_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); static void gtk_label_style_set (GtkWidget *widget, GtkStyle *previous_style); static void gtk_label_direction_changed (GtkWidget *widget, @@ -57,7 +66,29 @@ static void gtk_label_direction_changed (GtkWidget *widget, static gint gtk_label_expose (GtkWidget *widget, GdkEventExpose *event); -static GtkMiscClass *parent_class = NULL; +static void gtk_label_realize (GtkWidget *widget); +static void gtk_label_unrealize (GtkWidget *widget); +static void gtk_label_map (GtkWidget *widget); +static void gtk_label_unmap (GtkWidget *widget); +static gint gtk_label_button_press (GtkWidget *widget, + GdkEventButton *event); +static gint gtk_label_button_release (GtkWidget *widget, + GdkEventButton *event); +static gint gtk_label_motion (GtkWidget *widget, + GdkEventMotion *event); + +static void gtk_label_create_window (GtkLabel *label); +static void gtk_label_destroy_window (GtkLabel *label); +static void gtk_label_clear_layout (GtkLabel *label); +static void gtk_label_ensure_layout (GtkLabel *label, + gint *widthp, + gint *heightp); +static void gtk_label_select_region_index (GtkLabel *label, + gint anchor_index, + gint end_index); + + +GtkMiscClass *parent_class = NULL; GtkType gtk_label_get_type (void) @@ -108,9 +139,17 @@ gtk_label_class_init (GtkLabelClass *class) object_class->get_arg = gtk_label_get_arg; widget_class->size_request = gtk_label_size_request; + widget_class->size_allocate = gtk_label_size_allocate; widget_class->style_set = gtk_label_style_set; widget_class->direction_changed = gtk_label_direction_changed; widget_class->expose_event = gtk_label_expose; + widget_class->realize = gtk_label_realize; + widget_class->unrealize = gtk_label_unrealize; + widget_class->map = gtk_label_map; + widget_class->unmap = gtk_label_unmap; + widget_class->button_press_event = gtk_label_button_press; + widget_class->button_release_event = gtk_label_button_release; + widget_class->motion_notify_event = gtk_label_motion; } static void @@ -207,11 +246,10 @@ gtk_label_set_text_internal (GtkLabel *label, g_free (label->label); label->label = str; - if (label->layout) - { - g_object_unref (G_OBJECT (label->layout)); - label->layout = NULL; - } + + gtk_label_clear_layout (label); + + gtk_label_select_region_index (label, 0, 0); gtk_widget_queue_resize (GTK_WIDGET (label)); } @@ -426,6 +464,8 @@ gtk_label_finalize (GObject *object) if (label->attrs) pango_attr_list_unref (label->attrs); + + g_free (label->select_info); G_OBJECT_CLASS (parent_class)->finalize (object); } @@ -469,16 +509,25 @@ gtk_label_pattern_to_attrs (GtkLabel *label, } static void -gtk_label_size_request (GtkWidget *widget, - GtkRequisition *requisition) +gtk_label_clear_layout (GtkLabel *label) { - GtkLabel *label; + if (label->layout) + { + g_object_unref (G_OBJECT (label->layout)); + label->layout = NULL; + } +} + +static void +gtk_label_ensure_layout (GtkLabel *label, + gint *widthp, + gint *heightp) +{ + GtkWidget *widget; PangoRectangle logical_rect; - - g_return_if_fail (GTK_IS_LABEL (widget)); - g_return_if_fail (requisition != NULL); - - label = GTK_LABEL (widget); + gint width, height; + + widget = GTK_WIDGET (label); /* * There are a number of conditions which will necessitate re-filling @@ -501,8 +550,8 @@ gtk_label_size_request (GtkWidget *widget, * don't think it's really that slow. */ - requisition->width = label->misc.xpad * 2; - requisition->height = label->misc.ypad * 2; + width = label->misc.xpad * 2; + height = label->misc.ypad * 2; if (!label->layout) { @@ -512,7 +561,7 @@ gtk_label_size_request (GtkWidget *widget, label->layout = gtk_widget_create_pango_layout (widget, label->label); /* FIXME move to a model where the pattern isn't stored - * permanently, and just modifes or creates the AttrList + * permanently, and just modifies or creates the AttrList */ if (label->attrs) attrs = pango_attr_list_copy (label->attrs); @@ -521,7 +570,7 @@ gtk_label_size_request (GtkWidget *widget, if (label->pattern) gtk_label_pattern_to_attrs (label, attrs); - + if (attrs) { pango_layout_set_attributes (label->layout, attrs); @@ -564,8 +613,8 @@ gtk_label_size_request (GtkWidget *widget, pango_layout_set_width (label->layout, aux_info->width * PANGO_SCALE); pango_layout_get_extents (label->layout, NULL, &logical_rect); - requisition->width += aux_info->width; - requisition->height += PANGO_PIXELS (logical_rect.height); + width += aux_info->width; + height += PANGO_PIXELS (logical_rect.height); } else { @@ -632,8 +681,8 @@ gtk_label_size_request (GtkWidget *widget, } pango_layout_set_width (label->layout, width); - requisition->width += PANGO_PIXELS (real_width); - requisition->height += PANGO_PIXELS (height); + width += PANGO_PIXELS (real_width); + height += PANGO_PIXELS (height); } } else /* !label->wrap */ @@ -641,8 +690,52 @@ gtk_label_size_request (GtkWidget *widget, pango_layout_set_width (label->layout, -1); pango_layout_get_extents (label->layout, NULL, &logical_rect); - requisition->width += PANGO_PIXELS (logical_rect.width); - requisition->height += PANGO_PIXELS (logical_rect.height); + width += PANGO_PIXELS (logical_rect.width); + height += PANGO_PIXELS (logical_rect.height); + } + + if (widthp) + *widthp = width; + + if (heightp) + *heightp = height; +} + +static void +gtk_label_size_request (GtkWidget *widget, + GtkRequisition *requisition) +{ + GtkLabel *label; + gint width, height; + + g_return_if_fail (GTK_IS_LABEL (widget)); + g_return_if_fail (requisition != NULL); + + label = GTK_LABEL (widget); + + gtk_label_ensure_layout (label, &width, &height); + + requisition->width = width; + requisition->height = height; +} + +static void +gtk_label_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GtkLabel *label; + + label = GTK_LABEL (widget); + + (* GTK_WIDGET_CLASS (parent_class)->size_allocate) (widget, allocation); + + if (label->select_info && label->select_info->window) + { + gdk_window_move_resize (label->select_info->window, + allocation->x, + allocation->y, + allocation->width, + allocation->height); } } @@ -651,13 +744,13 @@ gtk_label_style_set (GtkWidget *widget, GtkStyle *previous_style) { GtkLabel *label; - + g_return_if_fail (GTK_IS_LABEL (widget)); label = GTK_LABEL (widget); - if (previous_style && label->layout) - pango_layout_context_changed (label->layout); + /* We have to clear the layout, fonts etc. may have changed */ + gtk_label_clear_layout (label); } static void @@ -703,42 +796,58 @@ gtk_label_paint_word (GtkLabel *label, } #endif +static void +get_layout_location (GtkLabel *label, + gint *xp, + gint *yp) +{ + GtkMisc *misc; + GtkWidget *widget; + gfloat xalign; + gint x, y; + + misc = GTK_MISC (label); + widget = GTK_WIDGET (label); + + if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR) + xalign = misc->xalign; + else + xalign = 1.0 - misc->xalign; + + x = floor (widget->allocation.x + (gint)misc->xpad + + ((widget->allocation.width - widget->requisition.width) * xalign) + + 0.5); + + y = floor (widget->allocation.y + (gint)misc->ypad + + ((widget->allocation.height - widget->requisition.height) * misc->yalign) + + 0.5); + + + if (xp) + *xp = x; + + if (yp) + *yp = y; +} + static gint gtk_label_expose (GtkWidget *widget, GdkEventExpose *event) { GtkLabel *label; - GtkMisc *misc; gint x, y; - gfloat xalign; g_return_val_if_fail (GTK_IS_LABEL (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); label = GTK_LABEL (widget); - /* if label->layout is NULL it means we got a set_text since - * our last size request, so a resize should be queued, - * which means a full expose is in the queue anyway. - */ + gtk_label_ensure_layout (label, NULL, NULL); + if (GTK_WIDGET_VISIBLE (widget) && GTK_WIDGET_MAPPED (widget) && - label->layout && label->label && (*label->label != '\0')) + label->label && (*label->label != '\0')) { - misc = GTK_MISC (widget); - - if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR) - xalign = misc->xalign; - else - xalign = 1. - misc->xalign; - - x = floor (widget->allocation.x + (gint)misc->xpad - + ((widget->allocation.width - widget->requisition.width) * xalign) - + 0.5); - - y = floor (widget->allocation.y + (gint)misc->ypad - + ((widget->allocation.height - widget->requisition.height) * misc->yalign) - + 0.5); - + get_layout_location (label, &x, &y); gtk_paint_layout (widget->style, widget->window, @@ -748,6 +857,45 @@ gtk_label_expose (GtkWidget *widget, "label", x, y, label->layout); + + if (label->select_info && + (label->select_info->selection_anchor != + label->select_info->selection_end)) + { + gint range[2]; + GdkRegion *clip; + + range[0] = label->select_info->selection_anchor; + range[1] = label->select_info->selection_end; + + if (range[0] > range[1]) + { + gint tmp = range[0]; + range[0] = range[1]; + range[1] = tmp; + } + + clip = gdk_pango_layout_get_clip_region (label->layout, + x, y, + range, + 1); + + /* FIXME should use gtk_paint, but it can't use a clip + * region + */ + + gdk_gc_set_clip_region (widget->style->white_gc, clip); + + gdk_draw_layout_with_colors (widget->window, + widget->style->white_gc, + x, y, + label->layout, + &widget->style->fg[GTK_STATE_SELECTED], + &widget->style->bg[GTK_STATE_SELECTED]); + + gdk_gc_set_clip_region (widget->style->white_gc, NULL); + gdk_region_destroy (clip); + } } return TRUE; @@ -836,3 +984,443 @@ gtk_label_parse_uline (GtkLabel *label, return accel_key; } + +static void +gtk_label_realize (GtkWidget *widget) +{ + GtkLabel *label; + + label = GTK_LABEL (widget); + + (* GTK_WIDGET_CLASS (parent_class)->realize) (widget); + + if (label->select_info) + gtk_label_create_window (label); +} + +static void +gtk_label_unrealize (GtkWidget *widget) +{ + GtkLabel *label; + + label = GTK_LABEL (widget); + + if (label->select_info) + gtk_label_destroy_window (label); + + (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget); +} + +static void +gtk_label_map (GtkWidget *widget) +{ + GtkLabel *label; + + label = GTK_LABEL (widget); + + (* GTK_WIDGET_CLASS (parent_class)->map) (widget); + + if (label->select_info) + gdk_window_show (label->select_info->window); +} + +static void +gtk_label_unmap (GtkWidget *widget) +{ + GtkLabel *label; + + label = GTK_LABEL (widget); + + if (label->select_info) + gdk_window_hide (label->select_info->window); + + (* GTK_WIDGET_CLASS (parent_class)->unmap) (widget); +} + +static void +window_to_layout_coords (GtkLabel *label, + gint *x, + gint *y) +{ + gint lx, ly; + GtkWidget *widget; + + widget = GTK_WIDGET (label); + + /* get layout location in widget->window coords */ + get_layout_location (label, &lx, &ly); + + if (x) + { + *x += widget->allocation.x; /* go to widget->window */ + *x -= lx; /* go to layout */ + } + + if (y) + { + *y += widget->allocation.y; /* go to widget->window */ + *y -= ly; /* go to layout */ + } +} + +static void +layout_to_window_coords (GtkLabel *label, + gint *x, + gint *y) +{ + gint lx, ly; + GtkWidget *widget; + + widget = GTK_WIDGET (label); + + /* get layout location in widget->window coords */ + get_layout_location (label, &lx, &ly); + + if (x) + { + *x += lx; /* go to widget->window */ + *x -= widget->allocation.x; /* go to selection window */ + } + + if (y) + { + *y += ly; /* go to widget->window */ + *y -= widget->allocation.y; /* go to selection window */ + } +} + +static void +get_layout_index (GtkLabel *label, + gint x, + gint y, + gint *index) +{ + gint trailing = 0; + const gchar *cluster; + const gchar *cluster_end; + + *index = 0; + + gtk_label_ensure_layout (label, NULL, NULL); + + window_to_layout_coords (label, &x, &y); + + x *= PANGO_SCALE; + y *= PANGO_SCALE; + + pango_layout_xy_to_index (label->layout, + x, y, + index, &trailing); + + + cluster = label->label + *index; + cluster_end = cluster; + while (trailing) + { + cluster_end = g_utf8_next_char (cluster_end); + --trailing; + } + + *index += (cluster_end - cluster); +} + +static gint +gtk_label_button_press (GtkWidget *widget, + GdkEventButton *event) +{ + GtkLabel *label; + gint index = 0; + + label = GTK_LABEL (widget); + + if (label->select_info == NULL) + return FALSE; + + if (event->button != 1) + return FALSE; + + get_layout_index (label, event->x, event->y, &index); + + if ((label->select_info->selection_anchor != + label->select_info->selection_end) && + (event->state & GDK_SHIFT_MASK)) + { + /* extend (same as motion) */ + if (index < label->select_info->selection_end) + gtk_label_select_region_index (label, + index, + label->select_info->selection_end); + else + gtk_label_select_region_index (label, + label->select_info->selection_anchor, + index); + + /* ensure the anchor is opposite index */ + if (index == label->select_info->selection_anchor) + { + gint tmp = label->select_info->selection_end; + label->select_info->selection_end = label->select_info->selection_anchor; + label->select_info->selection_anchor = tmp; + } + } + else + { + /* start a replacement */ + gtk_label_select_region_index (label, index, index); + } + + return TRUE; +} + +static gint +gtk_label_button_release (GtkWidget *widget, + GdkEventButton *event) + +{ + GtkLabel *label; + + label = GTK_LABEL (widget); + + if (label->select_info == NULL) + return FALSE; + + if (event->button != 1) + return FALSE; + + /* The goal here is to return TRUE iff we ate the + * button press to start selecting. + */ + + return TRUE; +} + +static gint +gtk_label_motion (GtkWidget *widget, + GdkEventMotion *event) +{ + GtkLabel *label; + gint index; + gint x, y; + + label = GTK_LABEL (widget); + + if (label->select_info == NULL) + return FALSE; + + if ((event->state & GDK_BUTTON1_MASK) == 0) + return FALSE; + + gdk_window_get_pointer (label->select_info->window, + &x, &y, NULL); + + get_layout_index (label, x, y, &index); + + gtk_label_select_region_index (label, + label->select_info->selection_anchor, + index); + + return TRUE; +} + +static void +gtk_label_create_window (GtkLabel *label) +{ + GtkWidget *widget; + GdkWindowAttr attributes; + gint attributes_mask; + + g_assert (label->select_info); + g_assert (GTK_WIDGET_REALIZED (label)); + + if (label->select_info->window) + return; + + widget = GTK_WIDGET (label); + + attributes.x = widget->allocation.x; + attributes.y = widget->allocation.y; + attributes.width = widget->allocation.width; + attributes.height = widget->allocation.height; + attributes.window_type = GDK_WINDOW_TEMP; + attributes.wclass = GDK_INPUT_ONLY; + attributes.override_redirect = TRUE; + attributes.event_mask = gtk_widget_get_events (widget) | + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_BUTTON_MOTION_MASK; + + attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_NOREDIR; + + label->select_info->window = gdk_window_new (widget->window, + &attributes, attributes_mask); + gdk_window_set_user_data (label->select_info->window, widget); +} + +static void +gtk_label_destroy_window (GtkLabel *label) +{ + g_assert (label->select_info); + + if (label->select_info->window == NULL) + return; + + gdk_window_set_user_data (label->select_info->window, NULL); + gdk_window_destroy (label->select_info->window); + label->select_info->window = NULL; +} + +void +gtk_label_set_selectable (GtkLabel *label, + gboolean setting) +{ + g_return_if_fail (GTK_IS_LABEL (label)); + + setting = setting != FALSE; + + if (setting) + { + if (label->select_info == NULL) + { + label->select_info = g_new (GtkLabelSelectionInfo, 1); + + label->select_info->window = NULL; + label->select_info->selection_anchor = 0; + label->select_info->selection_end = 0; + + if (GTK_WIDGET_REALIZED (label)) + gtk_label_create_window (label); + + if (GTK_WIDGET_MAPPED (label)) + gdk_window_show (label->select_info->window); + } + } + else + { + if (label->select_info) + { + if (label->select_info->window) + gtk_label_destroy_window (label); + + g_free (label->select_info); + + label->select_info = NULL; + } + } +} + +gboolean +gtk_label_get_selectable (GtkLabel *label) +{ + g_return_val_if_fail (GTK_IS_LABEL (label), FALSE); + + return label->select_info != NULL; +} + +static void +get_text_callback (GtkClipboard *clipboard, + GtkSelectionData *selection_data, + guint info, + gpointer user_data_or_owner) +{ + GtkLabel *label; + gchar *str; + + label = GTK_LABEL (user_data_or_owner); + + if ((label->select_info->selection_anchor != + label->select_info->selection_end) && + label->label) + { + gint start, end; + + start = MIN (label->select_info->selection_anchor, + label->select_info->selection_end); + end = MAX (label->select_info->selection_anchor, + label->select_info->selection_end); + + str = g_strndup (label->label + start, + end - start); + + gtk_selection_data_set_text (selection_data, + str); + + g_free (str); + } +} + +static void +clear_text_callback (GtkClipboard *clipboard, + gpointer user_data_or_owner) +{ + GtkLabel *label; + + label = GTK_LABEL (user_data_or_owner); + + if (label->select_info) + { + label->select_info->selection_anchor = 0; + label->select_info->selection_end = 0; + + gtk_label_clear_layout (label); + gtk_widget_queue_draw (GTK_WIDGET (label)); + } +} + +static void +gtk_label_select_region_index (GtkLabel *label, + gint anchor_index, + gint end_index) +{ + static const GtkTargetEntry targets[] = { + { "STRING", 0, 0 }, + { "TEXT", 0, 0 }, + { "COMPOUND_TEXT", 0, 0 }, + { "UTF8_STRING", 0, 0 } + }; + + g_return_if_fail (GTK_IS_LABEL (label)); + + if (label->select_info) + { + GtkClipboard *clipboard; + + label->select_info->selection_anchor = anchor_index; + label->select_info->selection_end = end_index; + + clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY); + + gtk_clipboard_set_with_owner (clipboard, + targets, + G_N_ELEMENTS (targets), + get_text_callback, + clear_text_callback, + G_OBJECT (label)); + + gtk_label_clear_layout (label); + gtk_widget_queue_draw (GTK_WIDGET (label)); + } +} + +void +gtk_label_select_region (GtkLabel *label, + gint start_offset, + gint end_offset) +{ + g_return_if_fail (GTK_IS_LABEL (label)); + + if (label->label && label->select_info) + { + GtkClipboard *clipboard; + + if (start_offset < 0) + start_offset = 0; + + if (end_offset < 0) + end_offset = g_utf8_strlen (label->label, -1); + + gtk_label_select_region_index (label, + g_utf8_offset_to_pointer (label->label, start_offset) - label->label, + g_utf8_offset_to_pointer (label->label, end_offset) - label->label); + } +} + |