X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtklabel.c;h=c294c4afcb0716076b8ace0adff23bd47e5d90ee;hb=329c090ec18cf162db9d09b98f007f8979238116;hp=64f85330edbce50b5d5f3752a93d527ffe56ac35;hpb=de985fb58e80be74a181fee5e6f2d85d390c9140;p=~andy%2Fgtk diff --git a/gtk/gtklabel.c b/gtk/gtklabel.c index 64f85330e..c294c4afc 100644 --- a/gtk/gtklabel.c +++ b/gtk/gtklabel.c @@ -26,23 +26,34 @@ #include #include #include "gtklabel.h" -#include "gtksignal.h" +#include "gtkmain.h" +#include "gtkmarshalers.h" #include "gtkwindow.h" #include "gdk/gdkkeysyms.h" #include "gtkclipboard.h" -#include "gdk/gdki18n.h" #include +#include "gtkimagemenuitem.h" #include "gtkintl.h" +#include "gtkseparatormenuitem.h" #include "gtkmenuitem.h" #include "gtknotebook.h" +#include "gtkstock.h" +#include "gtkbindings.h" struct _GtkLabelSelectionInfo { GdkWindow *window; gint selection_anchor; gint selection_end; + GtkWidget *popup_menu; }; +enum { + MOVE_CURSOR, + COPY_CLIPBOARD, + POPULATE_POPUP, + LAST_SIGNAL +}; enum { PROP_0, @@ -55,9 +66,13 @@ enum { PROP_WRAP, PROP_SELECTABLE, PROP_MNEMONIC_KEYVAL, - PROP_MNEMONIC_WIDGET + PROP_MNEMONIC_WIDGET, + PROP_CURSOR_POSITION, + PROP_SELECTION_BOUND }; +static guint signals[LAST_SIGNAL] = { 0 }; + static void gtk_label_class_init (GtkLabelClass *klass); static void gtk_label_init (GtkLabel *label); static void gtk_label_set_property (GObject *object, @@ -115,30 +130,46 @@ static void set_markup (GtkLabel *label, static void gtk_label_recalculate (GtkLabel *label); static void gtk_label_hierarchy_changed (GtkWidget *widget, GtkWidget *old_toplevel); +static void gtk_label_screen_changed (GtkWidget *widget, + GdkScreen *old_screen); 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_ensure_layout (GtkLabel *label); static void gtk_label_select_region_index (GtkLabel *label, gint anchor_index, gint end_index); -static gboolean gtk_label_mnemonic_activate (GtkWidget *widget, - gboolean group_cycling); -static void gtk_label_setup_mnemonic (GtkLabel *label, - guint last_key); - +static gboolean gtk_label_mnemonic_activate (GtkWidget *widget, + gboolean group_cycling); +static void gtk_label_setup_mnemonic (GtkLabel *label, + guint last_key); +static gboolean gtk_label_focus (GtkWidget *widget, + GtkDirectionType direction); + +/* For selectable lables: */ +static void gtk_label_move_cursor (GtkLabel *label, + GtkMovementStep step, + gint count, + gboolean extend_selection); +static void gtk_label_copy_clipboard (GtkLabel *label); +static void gtk_label_select_all (GtkLabel *label); +static void gtk_label_do_popup (GtkLabel *label, + GdkEventButton *event); + +static gint gtk_label_move_forward_word (GtkLabel *label, + gint start); +static gint gtk_label_move_backward_word (GtkLabel *label, + gint start); static GtkMiscClass *parent_class = NULL; -GtkType +GType gtk_label_get_type (void) { - static GtkType label_type = 0; + static GType label_type = 0; if (!label_type) { @@ -155,20 +186,45 @@ gtk_label_get_type (void) (GInstanceInitFunc) gtk_label_init, }; - label_type = g_type_register_static (GTK_TYPE_MISC, "GtkLabel", &label_info, 0); + label_type = g_type_register_static (GTK_TYPE_MISC, "GtkLabel", + &label_info, 0); } return label_type; } +static void +add_move_binding (GtkBindingSet *binding_set, + guint keyval, + guint modmask, + GtkMovementStep step, + gint count) +{ + g_return_if_fail ((modmask & GDK_SHIFT_MASK) == 0); + + gtk_binding_entry_add_signal (binding_set, keyval, modmask, + "move_cursor", 3, + G_TYPE_ENUM, step, + G_TYPE_INT, count, + G_TYPE_BOOLEAN, FALSE); + + /* Selection-extending version */ + gtk_binding_entry_add_signal (binding_set, keyval, modmask | GDK_SHIFT_MASK, + "move_cursor", 3, + G_TYPE_ENUM, step, + G_TYPE_INT, count, + G_TYPE_BOOLEAN, TRUE); +} + static void gtk_label_class_init (GtkLabelClass *class) { GObjectClass *gobject_class = G_OBJECT_CLASS (class); GtkObjectClass *object_class = GTK_OBJECT_CLASS (class); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); + GtkBindingSet *binding_set; - parent_class = gtk_type_class (GTK_TYPE_MISC); + parent_class = g_type_class_peek_parent (class); gobject_class->set_property = gtk_label_set_property; gobject_class->get_property = gtk_label_get_property; @@ -190,27 +246,63 @@ gtk_label_class_init (GtkLabelClass *class) widget_class->button_release_event = gtk_label_button_release; widget_class->motion_notify_event = gtk_label_motion; widget_class->hierarchy_changed = gtk_label_hierarchy_changed; + widget_class->screen_changed = gtk_label_screen_changed; widget_class->mnemonic_activate = gtk_label_mnemonic_activate; + widget_class->focus = gtk_label_focus; + + class->move_cursor = gtk_label_move_cursor; + class->copy_clipboard = gtk_label_copy_clipboard; + + signals[MOVE_CURSOR] = + g_signal_new ("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); + + signals[COPY_CLIPBOARD] = + g_signal_new ("copy_clipboard", + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkLabelClass, copy_clipboard), + NULL, NULL, + _gtk_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[POPULATE_POPUP] = + g_signal_new ("populate_popup", + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtkLabelClass, populate_popup), + NULL, NULL, + _gtk_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + GTK_TYPE_MENU); - g_object_class_install_property (G_OBJECT_CLASS(object_class), + g_object_class_install_property (gobject_class, PROP_LABEL, g_param_spec_string ("label", _("Label"), - _("The text of the label."), + _("The text of the label"), NULL, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_ATTRIBUTES, g_param_spec_boxed ("attributes", _("Attributes"), - _("A list of style attributes to apply to the text of the label."), + _("A list of style attributes to apply to the text of the label"), PANGO_TYPE_ATTR_LIST, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_USE_MARKUP, g_param_spec_boolean ("use_markup", _("Use markup"), - _("The text of the label includes XML markup. See pango_parse_markup()."), + _("The text of the label includes XML markup. See pango_parse_markup()"), FALSE, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, @@ -225,7 +317,7 @@ gtk_label_class_init (GtkLabelClass *class) PROP_JUSTIFY, g_param_spec_enum ("justify", _("Justification"), - _("The alignment of the lines in the text of the label relative to each other. This does NOT affect the alignment of the label within its allocation. See GtkMisc::xalign for that."), + _("The alignment of the lines in the text of the label relative to each other. This does NOT affect the alignment of the label within its allocation. See GtkMisc::xalign for that"), GTK_TYPE_JUSTIFICATION, GTK_JUSTIFY_LEFT, G_PARAM_READWRITE)); @@ -234,7 +326,7 @@ gtk_label_class_init (GtkLabelClass *class) PROP_PATTERN, g_param_spec_string ("pattern", _("Pattern"), - _("A string with _ characters in positions correspond to characters in the text to underline."), + _("A string with _ characters in positions correspond to characters in the text to underline"), NULL, G_PARAM_WRITABLE)); @@ -242,21 +334,21 @@ gtk_label_class_init (GtkLabelClass *class) PROP_WRAP, g_param_spec_boolean ("wrap", _("Line wrap"), - _("If set, wrap lines if the text becomes too wide."), - TRUE, + _("If set, wrap lines if the text becomes too wide"), + FALSE, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_SELECTABLE, g_param_spec_boolean ("selectable", _("Selectable"), - _("Whether the label text can be selected with the mouse."), + _("Whether the label text can be selected with the mouse"), FALSE, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_MNEMONIC_KEYVAL, g_param_spec_uint ("mnemonic_keyval", _("Mnemonic key"), - _("The mnemonic accelerator key for this label."), + _("The mnemonic accelerator key for this label"), 0, G_MAXUINT, GDK_VoidSymbol, @@ -266,9 +358,106 @@ gtk_label_class_init (GtkLabelClass *class) g_param_spec_object ("mnemonic_widget", _("Mnemonic widget"), _("The widget to be activated when the label's mnemonic " - "key is pressed."), + "key is pressed"), GTK_TYPE_WIDGET, G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, + PROP_CURSOR_POSITION, + g_param_spec_int ("cursor_position", + _("Cursor Position"), + _("The current position of the insertion cursor in chars"), + 0, + G_MAXINT, + 0, + G_PARAM_READABLE)); + + g_object_class_install_property (gobject_class, + PROP_SELECTION_BOUND, + g_param_spec_int ("selection_bound", + _("Selection Bound"), + _("The position of the opposite end of the selection from the cursor in chars"), + 0, + G_MAXINT, + 0, + G_PARAM_READABLE)); + + /* + * Key bindings + */ + + binding_set = gtk_binding_set_by_class (class); + + /* Moving the insertion point */ + add_move_binding (binding_set, GDK_Right, 0, + GTK_MOVEMENT_VISUAL_POSITIONS, 1); + + add_move_binding (binding_set, GDK_Left, 0, + GTK_MOVEMENT_VISUAL_POSITIONS, -1); + + add_move_binding (binding_set, GDK_KP_Right, 0, + GTK_MOVEMENT_VISUAL_POSITIONS, 1); + + add_move_binding (binding_set, GDK_KP_Left, 0, + GTK_MOVEMENT_VISUAL_POSITIONS, -1); + + add_move_binding (binding_set, GDK_f, GDK_CONTROL_MASK, + GTK_MOVEMENT_LOGICAL_POSITIONS, 1); + + add_move_binding (binding_set, GDK_b, GDK_CONTROL_MASK, + GTK_MOVEMENT_LOGICAL_POSITIONS, -1); + + add_move_binding (binding_set, GDK_Right, GDK_CONTROL_MASK, + GTK_MOVEMENT_WORDS, 1); + + add_move_binding (binding_set, GDK_Left, GDK_CONTROL_MASK, + GTK_MOVEMENT_WORDS, -1); + + add_move_binding (binding_set, GDK_KP_Right, GDK_CONTROL_MASK, + GTK_MOVEMENT_WORDS, 1); + + add_move_binding (binding_set, GDK_KP_Left, GDK_CONTROL_MASK, + GTK_MOVEMENT_WORDS, -1); + + add_move_binding (binding_set, GDK_a, GDK_CONTROL_MASK, + GTK_MOVEMENT_PARAGRAPH_ENDS, -1); + + add_move_binding (binding_set, GDK_e, GDK_CONTROL_MASK, + GTK_MOVEMENT_PARAGRAPH_ENDS, 1); + + add_move_binding (binding_set, GDK_f, GDK_MOD1_MASK, + GTK_MOVEMENT_WORDS, 1); + + add_move_binding (binding_set, GDK_b, GDK_MOD1_MASK, + GTK_MOVEMENT_WORDS, -1); + + add_move_binding (binding_set, GDK_Home, 0, + GTK_MOVEMENT_DISPLAY_LINE_ENDS, -1); + + add_move_binding (binding_set, GDK_End, 0, + GTK_MOVEMENT_DISPLAY_LINE_ENDS, 1); + + add_move_binding (binding_set, GDK_KP_Home, 0, + GTK_MOVEMENT_DISPLAY_LINE_ENDS, -1); + + add_move_binding (binding_set, GDK_KP_End, 0, + GTK_MOVEMENT_DISPLAY_LINE_ENDS, 1); + + add_move_binding (binding_set, GDK_Home, GDK_CONTROL_MASK, + GTK_MOVEMENT_BUFFER_ENDS, -1); + + add_move_binding (binding_set, GDK_End, GDK_CONTROL_MASK, + GTK_MOVEMENT_BUFFER_ENDS, 1); + + add_move_binding (binding_set, GDK_KP_Home, GDK_CONTROL_MASK, + GTK_MOVEMENT_BUFFER_ENDS, -1); + + add_move_binding (binding_set, GDK_KP_End, GDK_CONTROL_MASK, + GTK_MOVEMENT_BUFFER_ENDS, 1); + + /* copy */ + gtk_binding_entry_add_signal (binding_set, GDK_c, GDK_CONTROL_MASK, + "copy_clipboard", 0); } static void @@ -278,10 +467,8 @@ gtk_label_set_property (GObject *object, GParamSpec *pspec) { GtkLabel *label; - guint last_keyval; label = GTK_LABEL (object); - last_keyval = label->mnemonic_keyval; switch (prop_id) { @@ -357,6 +544,26 @@ gtk_label_get_property (GObject *object, case PROP_MNEMONIC_WIDGET: g_value_set_object (value, (GObject*) label->mnemonic_widget); break; + case PROP_CURSOR_POSITION: + if (label->select_info) + { + gint offset = g_utf8_pointer_to_offset (label->text, + label->text + label->select_info->selection_end); + g_value_set_int (value, offset); + } + else + g_value_set_int (value, 0); + break; + case PROP_SELECTION_BOUND: + if (label->select_info) + { + gint offset = g_utf8_pointer_to_offset (label->text, + label->text + label->select_info->selection_anchor); + g_value_set_int (value, offset); + } + else + g_value_set_int (value, 0); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -371,7 +578,7 @@ gtk_label_init (GtkLabel *label) label->label = NULL; - label->jtype = GTK_JUSTIFY_CENTER; + label->jtype = GTK_JUSTIFY_LEFT; label->wrap = FALSE; label->use_underline = FALSE; @@ -392,7 +599,8 @@ gtk_label_init (GtkLabel *label) * gtk_label_new: * @str: The text of the label * - * Creates a new #GtkLabel, containing the text in @str. + * Creates a new label with the given text inside it. You can + * pass %NULL to get an empty label widget. * * Return value: the new #GtkLabel **/ @@ -401,7 +609,7 @@ gtk_label_new (const gchar *str) { GtkLabel *label; - label = gtk_type_new (GTK_TYPE_LABEL); + label = g_object_new (GTK_TYPE_LABEL, NULL); if (str && *str) gtk_label_set_text (label, str); @@ -417,9 +625,10 @@ gtk_label_new (const gchar *str) * Creates a new #GtkLabel, containing the text in @str. * * If characters in @str are preceded by an underscore, they are - * underlined indicating that they represent a keyboard accelerator - * called a mnemonic. The mnemonic key can be used to activate - * another widget, chosen automatically, or explicitly using + * 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 + * to activate another widget, chosen automatically, or explicitly using * gtk_label_set_mnemonic_widget(). * * If gtk_label_set_mnemonic_widget() @@ -436,7 +645,7 @@ gtk_label_new_with_mnemonic (const gchar *str) { GtkLabel *label; - label = gtk_type_new (GTK_TYPE_LABEL); + label = g_object_new (GTK_TYPE_LABEL, NULL); if (str && *str) gtk_label_set_text_with_mnemonic (label, str); @@ -457,6 +666,10 @@ gtk_label_mnemonic_activate (GtkWidget *widget, * widget's ancestry. */ parent = widget->parent; + + if (parent && GTK_IS_NOTEBOOK (parent)) + return FALSE; + while (parent) { if (GTK_WIDGET_CAN_FOCUS (parent) || @@ -469,7 +682,7 @@ gtk_label_mnemonic_activate (GtkWidget *widget, /* barf if there was nothing to activate */ g_warning ("Couldn't find a target for a mnemonic activation."); - gdk_beep (); + gdk_display_beep (gtk_widget_get_display (widget)); return FALSE; } @@ -492,8 +705,7 @@ gtk_label_setup_mnemonic (GtkLabel *label, return; toplevel = gtk_widget_get_toplevel (GTK_WIDGET (label)); - - if (GTK_IS_WINDOW (toplevel)) + if (GTK_WIDGET_TOPLEVEL (toplevel)) { gtk_window_add_mnemonic (GTK_WINDOW (toplevel), label->mnemonic_keyval, @@ -507,10 +719,26 @@ gtk_label_hierarchy_changed (GtkWidget *widget, GtkWidget *old_toplevel) { GtkLabel *label = GTK_LABEL (widget); - + gtk_label_setup_mnemonic (label, label->mnemonic_keyval); } +static void +gtk_label_screen_changed (GtkWidget *widget, + GdkScreen *old_screen) +{ + gtk_label_clear_layout (GTK_LABEL (widget)); +} + +static void +label_mnemonic_widget_weak_notify (gpointer data, + GObject *where_the_object_was) +{ + GtkLabel *label = data; + + label->mnemonic_widget = NULL; + g_object_notify (G_OBJECT (label), "mnemonic_widget"); +} /** * gtk_label_set_mnemonic_widget: @@ -540,10 +768,16 @@ gtk_label_set_mnemonic_widget (GtkLabel *label, g_return_if_fail (GTK_IS_WIDGET (widget)); if (label->mnemonic_widget) - gtk_widget_unref (label->mnemonic_widget); + g_object_weak_unref (G_OBJECT (label->mnemonic_widget), + label_mnemonic_widget_weak_notify, + label); label->mnemonic_widget = widget; if (label->mnemonic_widget) - gtk_widget_ref (label->mnemonic_widget); + g_object_weak_ref (G_OBJECT (label->mnemonic_widget), + label_mnemonic_widget_weak_notify, + label); + + g_object_notify (G_OBJECT (label), "mnemonic_widget"); } /** @@ -572,7 +806,7 @@ gtk_label_get_mnemonic_widget (GtkLabel *label) * returns the keyval used for the mnemonic accelerator. If there is no * mnemonic set up it returns #GDK_VoidSymbol. * - * Returns: GDK keyval usable for accelerators, or GDK_VoidSymbol + * Returns: GDK keyval usable for accelerators, or #GDK_VoidSymbol **/ guint gtk_label_get_mnemonic_keyval (GtkLabel *label) @@ -689,9 +923,10 @@ gtk_label_recalculate (GtkLabel *label) /** * gtk_label_set_text: * @label: a #GtkLabel - * @str: a string + * @str: The text you want to set. * - * Sets the text of the label to @str. + * Sets the text within the #GtkLabel widget. It overwrites any text that + * was there before. * * This will also clear any previously set mnemonic accelerators. **/ @@ -701,11 +936,15 @@ gtk_label_set_text (GtkLabel *label, { g_return_if_fail (GTK_IS_LABEL (label)); + g_object_freeze_notify (G_OBJECT (label)); + gtk_label_set_label_internal (label, g_strdup (str ? str : "")); gtk_label_set_use_markup_internal (label, FALSE); gtk_label_set_use_underline_internal (label, FALSE); gtk_label_recalculate (label); + + g_object_thaw_notify (G_OBJECT (label)); } /** @@ -715,7 +954,8 @@ gtk_label_set_text (GtkLabel *label, * * Sets a #PangoAttrList; the attributes in the list are applied to the * label text. The attributes set with this function will be ignored - * if label->use_underline or label->use_markup is %TRUE. + * if the "use_underline" property or the "use_markup" property + * is %TRUE. **/ void gtk_label_set_attributes (GtkLabel *label, @@ -840,7 +1080,7 @@ set_markup (GtkLabel *label, * @label: a #GtkLabel * @str: a markup string (see Pango markup format) * - * Parses @str which is marked up with the Pango text markup language, + * Parses @str which is marked up with the Pango text markup language, * setting the label's text and attribute list based on the parse results. **/ void @@ -861,7 +1101,7 @@ gtk_label_set_markup (GtkLabel *label, * @label: a #GtkLabel * @str: a markup string (see Pango markup format) * - * Parses @str which is marked up with the Pango text markup language, + * Parses @str which is marked up with the Pango text markup language, * setting the label's text and attribute list based on the parse results. * If characters in @str are preceded by an underscore, they are underlined * indicating that they represent a keyboard accelerator called a mnemonic. @@ -899,7 +1139,6 @@ gtk_label_set_markup_with_mnemonic (GtkLabel *label, G_CONST_RETURN gchar * gtk_label_get_text (GtkLabel *label) { - g_return_val_if_fail (label != NULL, NULL); g_return_val_if_fail (GTK_IS_LABEL (label), NULL); return label->text; @@ -953,7 +1192,10 @@ gtk_label_set_pattern_internal (GtkLabel *label, g_return_if_fail (GTK_IS_LABEL (label)); attrs = gtk_label_pattern_to_attrs (label, pattern); - gtk_label_set_attributes_internal (label, attrs); + + if (label->effective_attrs) + pango_attr_list_unref (label->effective_attrs); + label->effective_attrs = attrs; } void @@ -975,7 +1217,11 @@ gtk_label_set_pattern (GtkLabel *label, * @jtype: a #GtkJustification * * Sets the alignment of the lines in the text of the label relative to - * each other. + * each other. %GTK_JUSTIFY_LEFT is the default value when the + * widget is first created with gtk_label_new(). If you instead want + * to set the alignment of the label as a whole, use + * gtk_misc_set_alignment() instead. gtk_label_set_justify() has no + * effect on labels containing only a single line. **/ void gtk_label_set_justify (GtkLabel *label, @@ -1000,9 +1246,9 @@ gtk_label_set_justify (GtkLabel *label, * gtk_label_get_justify: * @label: a #GtkLabel * - * Returns the justification of the label. See gtk_label_set_justification (). + * Returns the justification of the label. See gtk_label_set_justify (). * - * Return value: GtkJustification + * Return value: #GtkJustification **/ GtkJustification gtk_label_get_justify (GtkLabel *label) @@ -1017,8 +1263,10 @@ gtk_label_get_justify (GtkLabel *label) * @label: a #GtkLabel * @wrap: the setting * - * If true, the lines will be wrapped if the text becomes too wide. - */ + * Toggles line wrapping within the #GtkLabel widget. %TRUE makes it break + * lines if text exceeds the widget's size. %FALSE lets the text get cut off + * by the edge of the widget if it exceeds the widget size. + **/ void gtk_label_set_line_wrap (GtkLabel *label, gboolean wrap) @@ -1056,7 +1304,6 @@ void gtk_label_get (GtkLabel *label, gchar **str) { - g_return_if_fail (label != NULL); g_return_if_fail (GTK_IS_LABEL (label)); g_return_if_fail (str != NULL); @@ -1086,13 +1333,13 @@ gtk_label_finalize (GObject *object) g_free (label->text); if (label->layout) - g_object_unref (G_OBJECT (label->layout)); + g_object_unref (label->layout); if (label->attrs) pango_attr_list_unref (label->attrs); if (label->effective_attrs) - pango_attr_list_unref (label->attrs); + pango_attr_list_unref (label->effective_attrs); g_free (label->select_info); @@ -1104,15 +1351,58 @@ gtk_label_clear_layout (GtkLabel *label) { if (label->layout) { - g_object_unref (G_OBJECT (label->layout)); + g_object_unref (label->layout); label->layout = NULL; } } +typedef struct _LabelWrapWidth LabelWrapWidth; +struct _LabelWrapWidth +{ + gint width; + PangoFontDescription *font_desc; +}; + static void -gtk_label_ensure_layout (GtkLabel *label, - gint *widthp, - gint *heightp) +label_wrap_width_free (gpointer data) +{ + LabelWrapWidth *wrap_width = data; + pango_font_description_free (wrap_width->font_desc); + g_free (wrap_width); +} + +static gint +get_label_wrap_width (GtkLabel *label) +{ + PangoLayout *layout; + GtkStyle *style = GTK_WIDGET (label)->style; + + LabelWrapWidth *wrap_width = g_object_get_data (G_OBJECT (style), "gtk-label-wrap-width"); + if (!wrap_width) + { + wrap_width = g_new0 (LabelWrapWidth, 1); + g_object_set_data_full (G_OBJECT (style), "gtk-label-wrap-width", + wrap_width, label_wrap_width_free); + } + + if (wrap_width->font_desc && pango_font_description_equal (wrap_width->font_desc, style->font_desc)) + return wrap_width->width; + + if (wrap_width->font_desc) + pango_font_description_free (wrap_width->font_desc); + + wrap_width->font_desc = pango_font_description_copy (style->font_desc); + + layout = gtk_widget_create_pango_layout (GTK_WIDGET (label), + "This long string gives a good enough length for any line to have."); + pango_layout_get_size (layout, &wrap_width->width, NULL); + g_object_unref (layout); + + return wrap_width->width; +} + +static void +gtk_label_ensure_layout (GtkLabel *label) { GtkWidget *widget; PangoRectangle logical_rect; @@ -1120,27 +1410,6 @@ gtk_label_ensure_layout (GtkLabel *label, widget = GTK_WIDGET (label); - /* - * There are a number of conditions which will necessitate re-filling - * our text: - * - * 1. text changed. - * 2. justification changed either from to to GTK_JUSTIFY_FILL. - * 3. font changed. - * - * These have been detected elsewhere, and label->words will be zero, - * if one of the above has occured. - * - * Additionally, though, if GTK_JUSTIFY_FILL, we need to re-fill if: - * - * 4. gtk_widget_set_usize has changed the requested width. - * 5. gtk_misc_set_padding has changed xpad. - * 6. maybe others?... - * - * Too much of a pain to detect all these case, so always re-fill. I - * don't think it's really that slow. - */ - rwidth = label->misc.xpad * 2; rheight = label->misc.ypad * 2; @@ -1162,7 +1431,7 @@ gtk_label_ensure_layout (GtkLabel *label, align = PANGO_ALIGN_RIGHT; break; case GTK_JUSTIFY_CENTER: - align = PANGO_ALIGN_LEFT; + align = PANGO_ALIGN_CENTER; break; case GTK_JUSTIFY_FILL: /* FIXME: This just doesn't work to do this */ @@ -1174,107 +1443,77 @@ gtk_label_ensure_layout (GtkLabel *label, } pango_layout_set_alignment (label->layout, align); - } - - if (label->wrap) - { - GtkWidgetAuxInfo *aux_info; - gint longest_paragraph; - gint width, height; - gint real_width; - aux_info = _gtk_widget_get_aux_info (widget, FALSE); - if (aux_info && aux_info->width > 0) + if (label->wrap) { - pango_layout_set_width (label->layout, aux_info->width * PANGO_SCALE); - pango_layout_get_extents (label->layout, NULL, &logical_rect); - - rwidth += aux_info->width; - rheight += PANGO_PIXELS (logical_rect.height); - } - else - { - pango_layout_set_width (label->layout, -1); - pango_layout_get_extents (label->layout, NULL, &logical_rect); - - width = logical_rect.width; - height = logical_rect.height; + GtkWidgetAuxInfo *aux_info; + gint longest_paragraph; + gint width, height; - /* Try to guess a reasonable maximum width - */ - longest_paragraph = width; - - width = MIN (width, - PANGO_SCALE * gdk_string_width (GTK_WIDGET (label)->style->font, - "This long string gives a good enough length for any line to have.")); - width = MIN (width, - PANGO_SCALE * (gdk_screen_width () + 1) / 2); - - pango_layout_set_width (label->layout, width); - pango_layout_get_extents (label->layout, NULL, &logical_rect); - real_width = logical_rect.width; - height = logical_rect.height; - - /* Unfortunately, the above may leave us with a very unbalanced looking paragraph, - * so we try short search for a narrower width that leaves us with the same height - */ - if (longest_paragraph > 0) + aux_info = _gtk_widget_get_aux_info (widget, FALSE); + if (aux_info && aux_info->width > 0) + pango_layout_set_width (label->layout, aux_info->width * PANGO_SCALE); + else { - gint nlines, perfect_width; + GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (label)); + gint wrap_width; + + pango_layout_set_width (label->layout, -1); + pango_layout_get_extents (label->layout, NULL, &logical_rect); + + width = logical_rect.width; + + /* Try to guess a reasonable maximum width */ + longest_paragraph = width; - nlines = pango_layout_get_line_count (label->layout); - perfect_width = (longest_paragraph + nlines - 1) / nlines; + wrap_width = get_label_wrap_width (label); + width = MIN (width, wrap_width); + width = MIN (width, + PANGO_SCALE * (gdk_screen_get_width (screen) + 1) / 2); + + pango_layout_set_width (label->layout, width); + pango_layout_get_extents (label->layout, NULL, &logical_rect); + width = logical_rect.width; + height = logical_rect.height; - if (perfect_width < width) + /* Unfortunately, the above may leave us with a very unbalanced looking paragraph, + * so we try short search for a narrower width that leaves us with the same height + */ + if (longest_paragraph > 0) { - pango_layout_set_width (label->layout, perfect_width); - pango_layout_get_extents (label->layout, NULL, &logical_rect); + gint nlines, perfect_width; - if (logical_rect.height <= height) - { - width = perfect_width; - real_width = logical_rect.width; - height = logical_rect.height; - } - else + nlines = pango_layout_get_line_count (label->layout); + perfect_width = (longest_paragraph + nlines - 1) / nlines; + + if (perfect_width < width) { - gint mid_width = (perfect_width + width) / 2; - - if (mid_width > perfect_width) + pango_layout_set_width (label->layout, perfect_width); + pango_layout_get_extents (label->layout, NULL, &logical_rect); + + if (logical_rect.height <= height) + width = logical_rect.width; + else { - pango_layout_set_width (label->layout, mid_width); - pango_layout_get_extents (label->layout, NULL, &logical_rect); - - if (logical_rect.height <= height) + gint mid_width = (perfect_width + width) / 2; + + if (mid_width > perfect_width) { - width = mid_width; - real_width = logical_rect.width; - height = logical_rect.height; + pango_layout_set_width (label->layout, mid_width); + pango_layout_get_extents (label->layout, NULL, &logical_rect); + + if (logical_rect.height <= height) + width = logical_rect.width; } } } } + pango_layout_set_width (label->layout, width); } - pango_layout_set_width (label->layout, width); - - rwidth += PANGO_PIXELS (real_width); - rheight += PANGO_PIXELS (height); } + else /* !label->wrap */ + pango_layout_set_width (label->layout, -1); } - else /* !label->wrap */ - { - pango_layout_set_width (label->layout, -1); - pango_layout_get_extents (label->layout, NULL, &logical_rect); - - rwidth += PANGO_PIXELS (logical_rect.width); - rheight += PANGO_PIXELS (logical_rect.height); - } - - if (widthp) - *widthp = rwidth; - - if (heightp) - *heightp = rheight; } static void @@ -1283,13 +1522,44 @@ gtk_label_size_request (GtkWidget *widget, { GtkLabel *label; gint width, height; + PangoRectangle logical_rect; + GtkWidgetAuxInfo *aux_info; 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); + /* + * If word wrapping is on, then the height requisition can depend + * on: + * + * - Any width set on the widget via gtk_widget_set_usize(). + * - The padding of the widget (xpad, set by gtk_misc_set_padding) + * + * Instead of trying to detect changes to these quantities, if we + * are wrapping, we just rewrap for each size request. Since + * size requisitions are cached by the GTK+ core, this is not + * expensive. + */ + + if (label->wrap) + gtk_label_clear_layout (label); + + gtk_label_ensure_layout (label); + + width = label->misc.xpad * 2; + height = label->misc.ypad * 2; + + pango_layout_get_extents (label->layout, NULL, &logical_rect); + + aux_info = _gtk_widget_get_aux_info (widget, FALSE); + if (label->wrap && aux_info && aux_info->width > 0) + width += aux_info->width; + else + width += PANGO_PIXELS (logical_rect.width); + + height += PANGO_PIXELS (logical_rect.height); requisition->width = width; requisition->height = height; @@ -1302,7 +1572,7 @@ gtk_label_size_allocate (GtkWidget *widget, GtkLabel *label; label = GTK_LABEL (widget); - + (* GTK_WIDGET_CLASS (parent_class)->size_allocate) (widget, allocation); if (label->select_info && label->select_info->window) @@ -1404,15 +1674,21 @@ get_layout_location (GtkLabel *label, 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); - + + x = floor (widget->allocation.x + (gint)misc->xpad + + xalign * (widget->allocation.width - widget->requisition.width) + + 0.5); + + if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR) + x = MAX (x, widget->allocation.x + misc->xpad); + else + x = MIN (x, + widget->allocation.x + widget->allocation.width - + widget->requisition.width - misc->xpad); + y = floor (widget->allocation.y + (gint)misc->ypad - + ((widget->allocation.height - widget->requisition.height) * misc->yalign) - + 0.5); - + + MAX (((widget->allocation.height - widget->requisition.height) * misc->yalign) + + 0.5, 0)); if (xp) *xp = x; @@ -1421,6 +1697,90 @@ get_layout_location (GtkLabel *label, *yp = y; } +static void +gtk_label_draw_cursor (GtkLabel *label, gint xoffset, gint yoffset) +{ + if (label->select_info == NULL) + return; + + if (GTK_WIDGET_DRAWABLE (label)) + { + GtkWidget *widget = GTK_WIDGET (label); + + GtkTextDirection keymap_direction; + GtkTextDirection widget_direction; + PangoRectangle strong_pos, weak_pos; + gboolean split_cursor; + PangoRectangle *cursor1 = NULL; + PangoRectangle *cursor2 = NULL; + GdkRectangle cursor_location; + GtkTextDirection dir1 = GTK_TEXT_DIR_NONE; + GtkTextDirection dir2 = GTK_TEXT_DIR_NONE; + GdkGC *gc; + + keymap_direction = + (gdk_keymap_get_direction (gdk_keymap_get_for_display (gtk_widget_get_display (widget))) == PANGO_DIRECTION_LTR) ? + GTK_TEXT_DIR_LTR : GTK_TEXT_DIR_RTL; + + widget_direction = gtk_widget_get_direction (widget); + + gtk_label_ensure_layout (label); + + pango_layout_get_cursor_pos (label->layout, label->select_info->selection_end, + &strong_pos, &weak_pos); + + g_object_get (gtk_widget_get_settings (widget), + "gtk-split-cursor", &split_cursor, + NULL); + + dir1 = widget_direction; + + if (split_cursor) + { + cursor1 = &strong_pos; + + if (strong_pos.x != weak_pos.x || + strong_pos.y != weak_pos.y) + { + dir2 = (widget_direction == GTK_TEXT_DIR_LTR) ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR; + cursor2 = &weak_pos; + } + } + else + { + if (keymap_direction == widget_direction) + cursor1 = &strong_pos; + else + cursor1 = &weak_pos; + } + + cursor_location.x = xoffset + PANGO_PIXELS (cursor1->x); + cursor_location.y = yoffset + PANGO_PIXELS (cursor1->y); + cursor_location.width = 0; + cursor_location.height = PANGO_PIXELS (cursor1->height); + + gc = _gtk_get_insertion_cursor_gc (widget, TRUE); + _gtk_draw_insertion_cursor (widget, widget->window, gc, + &cursor_location, dir1, + dir2 != GTK_TEXT_DIR_NONE); + g_object_unref (gc); + + if (dir2 != GTK_TEXT_DIR_NONE) + { + cursor_location.x = xoffset + PANGO_PIXELS (cursor2->x); + cursor_location.y = yoffset + PANGO_PIXELS (cursor2->y); + cursor_location.width = 0; + cursor_location.height = PANGO_PIXELS (cursor2->height); + + gc = _gtk_get_insertion_cursor_gc (widget, FALSE); + _gtk_draw_insertion_cursor (widget, widget->window, gc, + &cursor_location, dir2, TRUE); + g_object_unref (gc); + } + } +} + + static gint gtk_label_expose (GtkWidget *widget, GdkEventExpose *event) @@ -1433,7 +1793,7 @@ gtk_label_expose (GtkWidget *widget, label = GTK_LABEL (widget); - gtk_label_ensure_layout (label, NULL, NULL); + gtk_label_ensure_layout (label); if (GTK_WIDGET_VISIBLE (widget) && GTK_WIDGET_MAPPED (widget) && label->text && (*label->text != '\0')) @@ -1456,7 +1816,8 @@ gtk_label_expose (GtkWidget *widget, { gint range[2]; GdkRegion *clip; - + GtkStateType state; + range[0] = label->select_info->selection_anchor; range[1] = label->select_info->selection_end; @@ -1476,24 +1837,31 @@ gtk_label_expose (GtkWidget *widget, * region */ - gdk_gc_set_clip_region (widget->style->white_gc, clip); - + gdk_gc_set_clip_region (widget->style->black_gc, clip); + + + state = GTK_STATE_SELECTED; + if (!GTK_WIDGET_HAS_FOCUS (widget)) + state = GTK_STATE_ACTIVE; + gdk_draw_layout_with_colors (widget->window, - widget->style->white_gc, + widget->style->black_gc, x, y, label->layout, - &widget->style->fg[GTK_STATE_SELECTED], - &widget->style->bg[GTK_STATE_SELECTED]); + &widget->style->text[state], + &widget->style->base[state]); - gdk_gc_set_clip_region (widget->style->white_gc, NULL); + gdk_gc_set_clip_region (widget->style->black_gc, NULL); gdk_region_destroy (clip); } + else if (label->select_info && GTK_WIDGET_HAS_FOCUS (widget)) + gtk_label_draw_cursor (label, x, y); } - return TRUE; + return FALSE; } -void +static void gtk_label_set_uline_text_internal (GtkLabel *label, const gchar *str) { @@ -1508,8 +1876,10 @@ gtk_label_set_uline_text_internal (GtkLabel *label, g_return_if_fail (GTK_IS_LABEL (label)); g_return_if_fail (str != NULL); - /* Convert text to wide characters */ - + /* Split text into the base text and a separate pattern + * of underscores. + */ + new_str = g_new (gchar, strlen (str) + 1); pattern = g_new (gchar, g_utf8_strlen (str, -1) + 1); @@ -1545,7 +1915,7 @@ gtk_label_set_uline_text_internal (GtkLabel *label, { *pattern_dest++ = '_'; if (accel_key == GDK_VoidSymbol) - accel_key = gdk_keyval_to_lower (c); + accel_key = gdk_keyval_to_lower (gdk_unicode_to_keyval (c)); } while (src < next_src) @@ -1591,6 +1961,8 @@ gtk_label_parse_uline (GtkLabel *label, g_return_val_if_fail (str != NULL, GDK_VoidSymbol); orig_keyval = label->mnemonic_keyval; + + g_object_freeze_notify (G_OBJECT (label)); gtk_label_set_label_internal (label, g_strdup (str ? str : "")); gtk_label_set_use_markup_internal (label, FALSE); @@ -1603,6 +1975,8 @@ gtk_label_parse_uline (GtkLabel *label, gtk_label_setup_mnemonic (label, orig_keyval); + g_object_thaw_notify (G_OBJECT (label)); + return keyval; } @@ -1627,7 +2001,9 @@ gtk_label_set_text_with_mnemonic (GtkLabel *label, g_return_if_fail (str != NULL); last_keyval = label->mnemonic_keyval; - + + g_object_freeze_notify (G_OBJECT (label)); + gtk_label_set_label_internal (label, g_strdup (str ? str : "")); gtk_label_set_use_markup_internal (label, FALSE); gtk_label_set_use_underline_internal (label, TRUE); @@ -1635,8 +2011,9 @@ gtk_label_set_text_with_mnemonic (GtkLabel *label, gtk_label_recalculate (label); gtk_label_setup_mnemonic (label, last_keyval); -} + g_object_thaw_notify (G_OBJECT (label)); +} static void gtk_label_realize (GtkWidget *widget) @@ -1756,7 +2133,7 @@ get_layout_index (GtkLabel *label, *index = 0; - gtk_label_ensure_layout (label, NULL, NULL); + gtk_label_ensure_layout (label); window_to_layout_coords (label, &x, &y); @@ -1779,6 +2156,25 @@ get_layout_index (GtkLabel *label, *index += (cluster_end - cluster); } +static void +gtk_label_select_word (GtkLabel *label) +{ + gint min, max; + + gint start_index = gtk_label_move_backward_word (label, label->select_info->selection_end); + gint end_index = gtk_label_move_forward_word (label, label->select_info->selection_end); + + min = MIN (label->select_info->selection_anchor, + label->select_info->selection_end); + max = MAX (label->select_info->selection_anchor, + label->select_info->selection_end); + + min = MIN (min, start_index); + max = MAX (max, end_index); + + gtk_label_select_region_index (label, min, max); +} + static gint gtk_label_button_press (GtkWidget *widget, GdkEventButton *event) @@ -1791,40 +2187,71 @@ gtk_label_button_press (GtkWidget *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)) + if (event->button == 1) { - /* 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); + if (!GTK_WIDGET_HAS_FOCUS (widget)) + gtk_widget_grab_focus (widget); - /* 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; - } + if (event->type == GDK_3BUTTON_PRESS) + { + gtk_label_select_region_index (label, 0, strlen (label->text)); + return TRUE; + } + + if (event->type == GDK_2BUTTON_PRESS) + { + gtk_label_select_word (label); + return TRUE; + } + + 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)) + { + gint min, max; + + /* extend (same as motion) */ + min = MIN (label->select_info->selection_anchor, + label->select_info->selection_end); + max = MAX (label->select_info->selection_anchor, + label->select_info->selection_end); + + min = MIN (min, index); + max = MAX (max, index); + + /* ensure the anchor is opposite index */ + if (index == min) + { + gint tmp = min; + min = max; + max = tmp; + } + + gtk_label_select_region_index (label, min, max); + } + else + { + if (event->type == GDK_3BUTTON_PRESS) + gtk_label_select_region_index (label, 0, strlen (label->text)); + else if (event->type == GDK_2BUTTON_PRESS) + gtk_label_select_word (label); + else + /* start a replacement */ + gtk_label_select_region_index (label, index, index); + } + + return TRUE; } - else + else if (event->button == 3 && event->type == GDK_BUTTON_PRESS) { - /* start a replacement */ - gtk_label_select_region_index (label, index, index); + gtk_label_do_popup (label, event); + + return TRUE; + } - - return TRUE; + return FALSE; } static gint @@ -1899,16 +2326,20 @@ gtk_label_create_window (GtkLabel *label) attributes.window_type = GDK_WINDOW_TEMP; attributes.wclass = GDK_INPUT_ONLY; attributes.override_redirect = TRUE; + attributes.cursor = gdk_cursor_new_for_display (gtk_widget_get_display (widget), + GDK_XTERM); 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; + attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_NOREDIR | GDK_WA_CURSOR; label->select_info->window = gdk_window_new (widget->window, &attributes, attributes_mask); - gdk_window_set_user_data (label->select_info->window, widget); + gdk_window_set_user_data (label->select_info->window, widget); + + gdk_cursor_unref (attributes.cursor); } static void @@ -1948,14 +2379,12 @@ gtk_label_set_selectable (GtkLabel *label, { 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; + label->select_info = g_new0 (GtkLabelSelectionInfo, 1); + GTK_WIDGET_SET_FLAGS (label, GTK_CAN_FOCUS); + if (GTK_WIDGET_REALIZED (label)) - gtk_label_create_window (label); + gtk_label_create_window (label); if (GTK_WIDGET_MAPPED (label)) gdk_window_show (label->select_info->window); @@ -1969,17 +2398,25 @@ gtk_label_set_selectable (GtkLabel *label, gtk_label_select_region (label, 0, 0); if (label->select_info->window) - gtk_label_destroy_window (label); + { + gtk_label_destroy_window (label); + } g_free (label->select_info); label->select_info = NULL; + + GTK_WIDGET_UNSET_FLAGS (label, GTK_CAN_FOCUS); } } if (setting != old_setting) { - g_object_notify (G_OBJECT (label), "selectable"); - gtk_widget_queue_draw (GTK_WIDGET (label)); + g_object_freeze_notify (G_OBJECT (label)); + g_object_notify (G_OBJECT (label), "selectable"); + g_object_notify (G_OBJECT (label), "cursor_position"); + g_object_notify (G_OBJECT (label), "selection_bound"); + g_object_thaw_notify (G_OBJECT (label)); + gtk_widget_queue_draw (GTK_WIDGET (label)); } } @@ -2006,7 +2443,6 @@ get_text_callback (GtkClipboard *clipboard, gpointer user_data_or_owner) { GtkLabel *label; - gchar *str; label = GTK_LABEL (user_data_or_owner); @@ -2030,13 +2466,9 @@ get_text_callback (GtkClipboard *clipboard, if (start > len) start = len; - str = g_strndup (label->text + start, - end - start); - - gtk_selection_data_set_text (selection_data, - str); - - g_free (str); + gtk_selection_data_set_text (selection_data, + label->text + start, + end - start); } } @@ -2050,10 +2482,8 @@ clear_text_callback (GtkClipboard *clipboard, if (label->select_info) { - label->select_info->selection_anchor = 0; - label->select_info->selection_end = 0; + label->select_info->selection_anchor = label->select_info->selection_end; - gtk_label_clear_layout (label); gtk_widget_queue_draw (GTK_WIDGET (label)); } } @@ -2069,17 +2499,22 @@ gtk_label_select_region_index (GtkLabel *label, { "COMPOUND_TEXT", 0, 0 }, { "UTF8_STRING", 0, 0 } }; - + g_return_if_fail (GTK_IS_LABEL (label)); if (label->select_info) { GtkClipboard *clipboard; + if (label->select_info->selection_anchor == anchor_index && + label->select_info->selection_end == end_index) + return; + label->select_info->selection_anchor = anchor_index; label->select_info->selection_end = end_index; - clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY); + clipboard = gtk_widget_get_clipboard (GTK_WIDGET (label), + GDK_SELECTION_PRIMARY); if (anchor_index != end_index) { @@ -2096,8 +2531,12 @@ gtk_label_select_region_index (GtkLabel *label, gtk_clipboard_clear (clipboard); } - gtk_label_clear_layout (label); gtk_widget_queue_draw (GTK_WIDGET (label)); + + g_object_freeze_notify (G_OBJECT (label)); + g_object_notify (G_OBJECT (label), "cursor_position"); + g_object_notify (G_OBJECT (label), "selection_bound"); + g_object_thaw_notify (G_OBJECT (label)); } } @@ -2219,7 +2658,7 @@ gtk_label_get_layout (GtkLabel *label) { g_return_val_if_fail (GTK_IS_LABEL (label), NULL); - gtk_label_ensure_layout (label, NULL, NULL); + gtk_label_ensure_layout (label); return label->layout; } @@ -2255,8 +2694,9 @@ gtk_label_get_layout_offsets (GtkLabel *label, * @label: a #GtkLabel * @setting: %TRUE if the label's text should be parsed for markup. * - * Sets whether the text of the label contains markup in Pango's - * text markup lango. See gtk_label_set_markup(). + * Sets whether the text of the label contains markup in Pango's text markup + * language. See gtk_label_set_markup(). **/ void gtk_label_set_use_markup (GtkLabel *label, @@ -2272,8 +2712,9 @@ gtk_label_set_use_markup (GtkLabel *label, * gtk_label_get_use_markup: * @label: a #GtkLabel * - * Returns whether the label's text is interpreted as marked up with the - * Pango text markup language. See gtk_label_set_use_markup (). + * Returns whether the label's text is interpreted as marked up with + * the Pango text markup + * language. See gtk_label_set_use_markup (). * * Return value: %TRUE if the label's text will be parsed for markup. **/ @@ -2309,7 +2750,7 @@ gtk_label_set_use_underline (GtkLabel *label, * gtk_label_get_use_underline: * @label: a #GtkLabel * - * Returns whether an embedded underline in thef label indicates a + * Returns whether an embedded underline in the label indicates a * mnemonic. See gtk_label_set_use_underline (). * * Return value: %TRUE whether an embedded underline in the label indicates @@ -2322,3 +2763,489 @@ gtk_label_get_use_underline (GtkLabel *label) return label->use_underline; } + +static gboolean +gtk_label_focus (GtkWidget *widget, + GtkDirectionType direction) +{ + /* We never want to be in the tab chain */ + return FALSE; +} + +/* Compute the X position for an offset that corresponds to the "more important + * cursor position for that offset. We use this when trying to guess to which + * end of the selection we should go to when the user hits the left or + * right arrow key. + */ +static void +get_better_cursor (GtkLabel *label, + gint index, + gint *x, + gint *y) +{ + GdkKeymap *keymap = gdk_keymap_get_for_display (gtk_widget_get_display (GTK_WIDGET (label))); + GtkTextDirection keymap_direction = + (gdk_keymap_get_direction (keymap) == PANGO_DIRECTION_LTR) ? + GTK_TEXT_DIR_LTR : GTK_TEXT_DIR_RTL; + GtkTextDirection widget_direction = gtk_widget_get_direction (GTK_WIDGET (label)); + gboolean split_cursor; + PangoRectangle strong_pos, weak_pos; + + g_object_get (gtk_widget_get_settings (GTK_WIDGET (label)), + "gtk-split-cursor", &split_cursor, + NULL); + + gtk_label_ensure_layout (label); + + pango_layout_get_cursor_pos (label->layout, index, + &strong_pos, &weak_pos); + + if (split_cursor) + { + *x = strong_pos.x / PANGO_SCALE; + *y = strong_pos.y / PANGO_SCALE; + } + else + { + if (keymap_direction == widget_direction) + { + *x = strong_pos.x / PANGO_SCALE; + *y = strong_pos.y / PANGO_SCALE; + } + else + { + *x = weak_pos.x / PANGO_SCALE; + *y = weak_pos.y / PANGO_SCALE; + } + } +} + + +static gint +gtk_label_move_logically (GtkLabel *label, + gint start, + gint count) +{ + gint offset = g_utf8_pointer_to_offset (label->text, + label->text + start); + + if (label->text) + { + PangoLogAttr *log_attrs; + gint n_attrs; + gint length; + + gtk_label_ensure_layout (label); + + length = g_utf8_strlen (label->text, -1); + + pango_layout_get_log_attrs (label->layout, &log_attrs, &n_attrs); + + while (count > 0 && offset < length) + { + 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++; + } + + g_free (log_attrs); + } + + return g_utf8_offset_to_pointer (label->text, offset) - label->text; +} + +static gint +gtk_label_move_visually (GtkLabel *label, + gint start, + gint count) +{ + gint index; + + index = start; + + while (count != 0) + { + int new_index, new_trailing; + gboolean split_cursor; + gboolean strong; + + gtk_label_ensure_layout (label); + + g_object_get (gtk_widget_get_settings (GTK_WIDGET (label)), + "gtk-split-cursor", &split_cursor, + NULL); + + if (split_cursor) + strong = TRUE; + else + { + GdkKeymap *keymap = gdk_keymap_get_for_display (gtk_widget_get_display (GTK_WIDGET (label))); + GtkTextDirection keymap_direction = + (gdk_keymap_get_direction (keymap) == PANGO_DIRECTION_LTR) ? + GTK_TEXT_DIR_LTR : GTK_TEXT_DIR_RTL; + + strong = keymap_direction == gtk_widget_get_direction (GTK_WIDGET (label)); + } + + if (count > 0) + { + pango_layout_move_cursor_visually (label->layout, strong, index, 0, 1, &new_index, &new_trailing); + count--; + } + else + { + pango_layout_move_cursor_visually (label->layout, strong, index, 0, -1, &new_index, &new_trailing); + count++; + } + + if (new_index < 0 || new_index == G_MAXINT) + break; + + index = new_index; + + while (new_trailing--) + index = g_utf8_next_char (label->text + new_index) - label->text; + } + + return index; +} + +static gint +gtk_label_move_forward_word (GtkLabel *label, + gint start) +{ + gint new_pos = g_utf8_pointer_to_offset (label->text, + label->text + start); + gint length; + + length = g_utf8_strlen (label->text, -1); + if (new_pos < length) + { + PangoLogAttr *log_attrs; + gint n_attrs; + + gtk_label_ensure_layout (label); + + pango_layout_get_log_attrs (label->layout, &log_attrs, &n_attrs); + + /* Find the next word end */ + new_pos++; + while (new_pos < n_attrs && !log_attrs[new_pos].is_word_end) + new_pos++; + + g_free (log_attrs); + } + + return g_utf8_offset_to_pointer (label->text, new_pos) - label->text; +} + + +static gint +gtk_label_move_backward_word (GtkLabel *label, + gint start) +{ + gint new_pos = g_utf8_pointer_to_offset (label->text, + label->text + start); + gint length; + + length = g_utf8_strlen (label->text, -1); + + if (new_pos > 0) + { + PangoLogAttr *log_attrs; + gint n_attrs; + + gtk_label_ensure_layout (label); + + pango_layout_get_log_attrs (label->layout, &log_attrs, &n_attrs); + + new_pos -= 1; + + /* Find the previous word beginning */ + while (new_pos > 0 && !log_attrs[new_pos].is_word_start) + new_pos--; + + g_free (log_attrs); + } + + return g_utf8_offset_to_pointer (label->text, new_pos) - label->text; +} + +static void +gtk_label_move_cursor (GtkLabel *label, + GtkMovementStep step, + gint count, + gboolean extend_selection) +{ + gint new_pos; + + if (label->select_info == NULL) + return; + + new_pos = label->select_info->selection_end; + + if (label->select_info->selection_end != label->select_info->selection_anchor && + !extend_selection) + { + /* If we have a current selection and aren't extending it, move to the + * start/or end of the selection as appropriate + */ + switch (step) + { + case GTK_MOVEMENT_VISUAL_POSITIONS: + { + gint end_x, end_y; + gint anchor_x, anchor_y; + gboolean end_is_left; + + get_better_cursor (label, label->select_info->selection_end, &end_x, &end_y); + get_better_cursor (label, label->select_info->selection_anchor, &anchor_x, &anchor_y); + + end_is_left = (end_y < anchor_y) || (end_y == anchor_y && end_x < anchor_x); + + if (count < 0) + new_pos = end_is_left ? label->select_info->selection_end : label->select_info->selection_anchor; + else + new_pos = !end_is_left ? label->select_info->selection_end : label->select_info->selection_anchor; + + break; + } + case GTK_MOVEMENT_LOGICAL_POSITIONS: + case GTK_MOVEMENT_WORDS: + if (count < 0) + new_pos = MIN (label->select_info->selection_end, label->select_info->selection_anchor); + else + new_pos = MAX (label->select_info->selection_end, label->select_info->selection_anchor); + break; + case GTK_MOVEMENT_DISPLAY_LINE_ENDS: + case GTK_MOVEMENT_PARAGRAPH_ENDS: + case GTK_MOVEMENT_BUFFER_ENDS: + /* FIXME: Can do better here */ + new_pos = count < 0 ? 0 : strlen (label->text); + break; + case GTK_MOVEMENT_DISPLAY_LINES: + case GTK_MOVEMENT_PARAGRAPHS: + case GTK_MOVEMENT_PAGES: + break; + } + } + else + { + switch (step) + { + case GTK_MOVEMENT_LOGICAL_POSITIONS: + new_pos = gtk_label_move_logically (label, new_pos, count); + break; + case GTK_MOVEMENT_VISUAL_POSITIONS: + new_pos = gtk_label_move_visually (label, new_pos, count); + break; + case GTK_MOVEMENT_WORDS: + while (count > 0) + { + new_pos = gtk_label_move_forward_word (label, new_pos); + count--; + } + while (count < 0) + { + new_pos = gtk_label_move_backward_word (label, new_pos); + count++; + } + break; + case GTK_MOVEMENT_DISPLAY_LINE_ENDS: + case GTK_MOVEMENT_PARAGRAPH_ENDS: + case GTK_MOVEMENT_BUFFER_ENDS: + /* FIXME: Can do better here */ + new_pos = count < 0 ? 0 : strlen (label->text); + break; + case GTK_MOVEMENT_DISPLAY_LINES: + case GTK_MOVEMENT_PARAGRAPHS: + case GTK_MOVEMENT_PAGES: + break; + } + } + + if (extend_selection) + gtk_label_select_region_index (label, + label->select_info->selection_anchor, + new_pos); + else + gtk_label_select_region_index (label, new_pos, new_pos); +} + +static void +gtk_label_copy_clipboard (GtkLabel *label) +{ + if (label->text && label->select_info) + { + gint start, end; + gint len; + + start = MIN (label->select_info->selection_anchor, + label->select_info->selection_end); + end = MAX (label->select_info->selection_anchor, + label->select_info->selection_end); + + len = strlen (label->text); + + if (end > len) + end = len; + + if (start > len) + start = len; + + if (start != end) + gtk_clipboard_set_text (gtk_widget_get_clipboard (GTK_WIDGET (label), + GDK_SELECTION_CLIPBOARD), + label->text + start, end - start); + } +} + +static void +gtk_label_select_all (GtkLabel *label) +{ + gtk_label_select_region_index (label, 0, strlen (label->text)); +} + +/* Quick hack of a popup menu + */ +static void +activate_cb (GtkWidget *menuitem, + GtkLabel *label) +{ + const gchar *signal = g_object_get_data (G_OBJECT (menuitem), "gtk-signal"); + g_signal_emit_by_name (label, signal); +} + +static void +append_action_signal (GtkLabel *label, + GtkWidget *menu, + const gchar *stock_id, + const gchar *signal, + gboolean sensitive) +{ + GtkWidget *menuitem = gtk_image_menu_item_new_from_stock (stock_id, NULL); + + g_object_set_data (G_OBJECT (menuitem), "gtk-signal", (char *)signal); + g_signal_connect (menuitem, "activate", + G_CALLBACK (activate_cb), label); + + gtk_widget_set_sensitive (menuitem, sensitive); + + gtk_widget_show (menuitem); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); +} + +static void +popup_menu_detach (GtkWidget *attach_widget, + GtkMenu *menu) +{ + GtkLabel *label; + label = GTK_LABEL (attach_widget); + + if (label->select_info) + label->select_info->popup_menu = NULL; +} + +static void +popup_position_func (GtkMenu *menu, + gint *x, + gint *y, + gboolean *push_in, + gpointer user_data) +{ + GtkLabel *label; + GtkWidget *widget; + GtkRequisition req; + GdkScreen *screen; + + label = GTK_LABEL (user_data); + widget = GTK_WIDGET (label); + + if (label->select_info == NULL) + return; + + g_return_if_fail (GTK_WIDGET_REALIZED (label)); + + screen = gtk_widget_get_screen (widget); + gdk_window_get_origin (widget->window, x, y); + + gtk_widget_size_request (label->select_info->popup_menu, &req); + + *x += widget->allocation.width / 2; + *y += widget->allocation.height; + + *x = CLAMP (*x, 0, MAX (0, gdk_screen_get_width (screen) - req.width)); + *y = CLAMP (*y, 0, MAX (0, gdk_screen_get_height (screen) - req.height)); +} + + +static void +gtk_label_do_popup (GtkLabel *label, + GdkEventButton *event) +{ + GtkWidget *menuitem; + gboolean have_selection; + + if (label->select_info == NULL) + return; + + if (label->select_info->popup_menu) + gtk_widget_destroy (label->select_info->popup_menu); + + label->select_info->popup_menu = gtk_menu_new (); + + gtk_menu_attach_to_widget (GTK_MENU (label->select_info->popup_menu), + GTK_WIDGET (label), + popup_menu_detach); + + have_selection = + label->select_info->selection_anchor != label->select_info->selection_end; + + + append_action_signal (label, label->select_info->popup_menu, GTK_STOCK_CUT, "cut_clipboard", + FALSE); + append_action_signal (label, label->select_info->popup_menu, GTK_STOCK_COPY, "copy_clipboard", + have_selection); + append_action_signal (label, label->select_info->popup_menu, GTK_STOCK_PASTE, "paste_clipboard", + FALSE); + + menuitem = gtk_menu_item_new_with_label (_("Select All")); + g_signal_connect_swapped (menuitem, "activate", + G_CALLBACK (gtk_label_select_all), label); + gtk_widget_show (menuitem); + gtk_menu_shell_append (GTK_MENU_SHELL (label->select_info->popup_menu), menuitem); + + menuitem = gtk_separator_menu_item_new (); + gtk_widget_show (menuitem); + gtk_menu_shell_append (GTK_MENU_SHELL (label->select_info->popup_menu), menuitem); + + menuitem = gtk_menu_item_new_with_label (_("Input Methods")); + gtk_widget_show (menuitem); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (menuitem), gtk_menu_new ()); + gtk_widget_set_sensitive (menuitem, FALSE); + gtk_menu_shell_append (GTK_MENU_SHELL (label->select_info->popup_menu), menuitem); + + g_signal_emit (label, + signals[POPULATE_POPUP], + 0, + label->select_info->popup_menu); + + if (event) + gtk_menu_popup (GTK_MENU (label->select_info->popup_menu), NULL, NULL, + NULL, NULL, + event->button, event->time); + else + gtk_menu_popup (GTK_MENU (label->select_info->popup_menu), NULL, NULL, + popup_position_func, label, + 0, gtk_get_current_event_time ()); +}