#include <math.h>
#include <string.h>
#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 <pango/pango.h>
+#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,
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,
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)
{
(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;
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,
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));
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));
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,
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
GParamSpec *pspec)
{
GtkLabel *label;
- guint last_keyval;
label = GTK_LABEL (object);
- last_keyval = label->mnemonic_keyval;
switch (prop_id)
{
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);
label->label = NULL;
- label->jtype = GTK_JUSTIFY_CENTER;
+ label->jtype = GTK_JUSTIFY_LEFT;
label->wrap = FALSE;
label->use_underline = FALSE;
* 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
**/
{
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);
* 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()
{
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);
* widget's ancestry.
*/
parent = widget->parent;
+
+ if (parent && GTK_IS_NOTEBOOK (parent))
+ return FALSE;
+
while (parent)
{
if (GTK_WIDGET_CAN_FOCUS (parent) ||
/* 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;
}
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,
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:
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");
}
/**
* 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)
/**
* 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.
**/
{
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));
}
/**
*
* 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,
* @label: a #GtkLabel
* @str: a markup string (see <link linkend="PangoMarkupFormat">Pango markup format</link>)
*
- * Parses @str which is marked up with the Pango text markup language,
+ * Parses @str which is marked up with the <link linkend="PangoMarkupFormat">Pango text markup language</link>,
* setting the label's text and attribute list based on the parse results.
**/
void
* @label: a #GtkLabel
* @str: a markup string (see <link linkend="PangoMarkupFormat">Pango markup format</link>)
*
- * Parses @str which is marked up with the Pango text markup language,
+ * Parses @str which is marked up with the <link linkend="PangoMarkupFormat">Pango text markup language</link>,
* 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.
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;
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
* @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,
* 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)
* @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)
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);
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);
{
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;
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;
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 */
}
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
{
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;
GtkLabel *label;
label = GTK_LABEL (widget);
-
+
(* GTK_WIDGET_CLASS (parent_class)->size_allocate) (widget, allocation);
if (label->select_info && label->select_info->window)
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;
*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)
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'))
{
gint range[2];
GdkRegion *clip;
-
+ GtkStateType state;
+
range[0] = label->select_info->selection_anchor;
range[1] = label->select_info->selection_end;
* 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)
{
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);
{
*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)
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);
gtk_label_setup_mnemonic (label, orig_keyval);
+ g_object_thaw_notify (G_OBJECT (label));
+
return keyval;
}
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);
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)
*index = 0;
- gtk_label_ensure_layout (label, NULL, NULL);
+ gtk_label_ensure_layout (label);
window_to_layout_coords (label, &x, &y);
*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)
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
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
{
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);
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));
}
}
gpointer user_data_or_owner)
{
GtkLabel *label;
- gchar *str;
label = GTK_LABEL (user_data_or_owner);
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);
}
}
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));
}
}
{ "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)
{
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));
}
}
{
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;
}
* @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 <link
+ * linkend="PangoMarkupFormat">Pango's text markup
+ * language</link>. See gtk_label_set_markup().
**/
void
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 <link linkend="PangoMarkupFormat">Pango text markup
+ * language</link>. See gtk_label_set_use_markup ().
*
* Return value: %TRUE if the label's text will be parsed for markup.
**/
* 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
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 ());
+}