diff options
author | Christian Persch <chpe@cvs.gnome.org> | 2004-08-08 23:33:26 +0800 |
---|---|---|
committer | Christian Persch <chpe@src.gnome.org> | 2004-08-08 23:33:26 +0800 |
commit | 10256b2ff6a49d5283ca281ea0068e1acbe7686e (patch) | |
tree | d6738bead4c91e36a16a2a0e325749d216711cfe /lib/widgets/ephy-label.c | |
parent | d368e97a37dbd9ed2163f0a446fa3723ed57c732 (diff) | |
download | gsoc2013-epiphany-10256b2ff6a49d5283ca281ea0068e1acbe7686e.tar gsoc2013-epiphany-10256b2ff6a49d5283ca281ea0068e1acbe7686e.tar.gz gsoc2013-epiphany-10256b2ff6a49d5283ca281ea0068e1acbe7686e.tar.bz2 gsoc2013-epiphany-10256b2ff6a49d5283ca281ea0068e1acbe7686e.tar.lz gsoc2013-epiphany-10256b2ff6a49d5283ca281ea0068e1acbe7686e.tar.xz gsoc2013-epiphany-10256b2ff6a49d5283ca281ea0068e1acbe7686e.tar.zst gsoc2013-epiphany-10256b2ff6a49d5283ca281ea0068e1acbe7686e.zip |
R lib/widgets/ephy-ellipsizing-label.c: R
2004-08-08 Christian Persch <chpe@cvs.gnome.org>
* lib/ephy-marshal.list:
* lib/widgets/Makefile.am:
R lib/widgets/ephy-ellipsizing-label.c:
R lib/widgets/ephy-ellipsizing-label.h:
A lib/widgets/ephy-label.c:
A lib/widgets/ephy-label.h:
* src/ephy-notebook.c: (sync_label), (build_tab_label):
* src/pdm-dialog.c: (show_cookies_properties):
* src/prefs-dialog.c: (create_download_path_label),
(download_path_response_cb):
Kill our ellipsizing label implementation, and import GtkLabel
into our prefix.
* configure.in:
Depend on pango 1.5.1 for ellipsisation support.
Diffstat (limited to 'lib/widgets/ephy-label.c')
-rw-r--r-- | lib/widgets/ephy-label.c | 3448 |
1 files changed, 3448 insertions, 0 deletions
diff --git a/lib/widgets/ephy-label.c b/lib/widgets/ephy-label.c new file mode 100644 index 000000000..97c3e1878 --- /dev/null +++ b/lib/widgets/ephy-label.c @@ -0,0 +1,3448 @@ +/* GTK - The GIMP Toolkit + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS + * file for a list of people on the GTK+ Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GTK+ at ftp://ftp.gtk.org/pub/gtk/. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <math.h> +#include <string.h> +#include "ephy-label.h" +#include <gtk/gtkmain.h> +#include <gtk/gtkwindow.h> +#include <gdk/gdkkeysyms.h> +#include <gtk/gtkclipboard.h> +#include <pango/pango.h> +#include <gtk/gtkimagemenuitem.h> +#include <gtk/gtkseparatormenuitem.h> +#include <gtk/gtkmenuitem.h> +#include <gtk/gtknotebook.h> +#include <gtk/gtkstock.h> +#include <gtk/gtkbindings.h> +#include <glib/gi18n.h> +#include <glib-object.h> + +#include "ephy-marshal.h" + +/* we don't need to translate the property blurbs */ +#undef P_ +#define P_(string) (string) + +struct _EphyLabelSelectionInfo +{ + GdkWindow *window; + gint selection_anchor; + gint selection_end; + GtkWidget *popup_menu; +}; + +enum { + MOVE_CURSOR, + COPY_CLIPBOARD, + POPULATE_POPUP, + LAST_SIGNAL +}; + +enum { + PROP_0, + PROP_LABEL, + PROP_ATTRIBUTES, + PROP_USE_MARKUP, + PROP_USE_UNDERLINE, + PROP_JUSTIFY, + PROP_PATTERN, + PROP_WRAP, + PROP_SELECTABLE, + PROP_MNEMONIC_KEYVAL, + PROP_MNEMONIC_WIDGET, + PROP_CURSOR_POSITION, + PROP_SELECTION_BOUND, + PROP_ELLIPSIZE +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +static void ephy_label_class_init (EphyLabelClass *klass); +static void ephy_label_init (EphyLabel *label); +static void ephy_label_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void ephy_label_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static void ephy_label_destroy (GtkObject *object); +static void ephy_label_finalize (GObject *object); +static void ephy_label_size_request (GtkWidget *widget, + GtkRequisition *requisition); +static void ephy_label_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); +static void ephy_label_state_changed (GtkWidget *widget, + GtkStateType state); +static void ephy_label_style_set (GtkWidget *widget, + GtkStyle *previous_style); +static void ephy_label_direction_changed (GtkWidget *widget, + GtkTextDirection previous_dir); +static gint ephy_label_expose (GtkWidget *widget, + GdkEventExpose *event); + +static void ephy_label_realize (GtkWidget *widget); +static void ephy_label_unrealize (GtkWidget *widget); +static void ephy_label_map (GtkWidget *widget); +static void ephy_label_unmap (GtkWidget *widget); + +static gboolean ephy_label_button_press (GtkWidget *widget, + GdkEventButton *event); +static gboolean ephy_label_button_release (GtkWidget *widget, + GdkEventButton *event); +static gboolean ephy_label_motion (GtkWidget *widget, + GdkEventMotion *event); + + +static void ephy_label_set_text_internal (EphyLabel *label, + gchar *str); +static void ephy_label_set_label_internal (EphyLabel *label, + gchar *str); +static void ephy_label_set_use_markup_internal (EphyLabel *label, + gboolean val); +static void ephy_label_set_use_underline_internal (EphyLabel *label, + gboolean val); +static void ephy_label_set_attributes_internal (EphyLabel *label, + PangoAttrList *attrs); +static void ephy_label_set_uline_text_internal (EphyLabel *label, + const gchar *str); +static void ephy_label_set_pattern_internal (EphyLabel *label, + const gchar *pattern); +static void set_markup (EphyLabel *label, + const gchar *str, + gboolean with_uline); +static void ephy_label_recalculate (EphyLabel *label); +static void ephy_label_hierarchy_changed (GtkWidget *widget, + GtkWidget *old_toplevel); +static void ephy_label_screen_changed (GtkWidget *widget, + GdkScreen *old_screen); + +static void ephy_label_create_window (EphyLabel *label); +static void ephy_label_destroy_window (EphyLabel *label); +static void ephy_label_clear_layout (EphyLabel *label); +static void ephy_label_ensure_layout (EphyLabel *label); +static void ephy_label_select_region_index (EphyLabel *label, + gint anchor_index, + gint end_index); + +static gboolean ephy_label_mnemonic_activate (GtkWidget *widget, + gboolean group_cycling); +static void ephy_label_setup_mnemonic (EphyLabel *label, + guint last_key); +static gboolean ephy_label_focus (GtkWidget *widget, + GtkDirectionType direction); + +/* For selectable lables: */ +static void ephy_label_move_cursor (EphyLabel *label, + GtkMovementStep step, + gint count, + gboolean extend_selection); +static void ephy_label_copy_clipboard (EphyLabel *label); +static void ephy_label_select_all (EphyLabel *label); +static void ephy_label_do_popup (EphyLabel *label, + GdkEventButton *event); + +static gint ephy_label_move_forward_word (EphyLabel *label, + gint start); +static gint ephy_label_move_backward_word (EphyLabel *label, + gint start); + +static GtkMiscClass *parent_class = NULL; + + +GType +ephy_label_get_type (void) +{ + static GType label_type = 0; + + if (!label_type) + { + static const GTypeInfo label_info = + { + sizeof (EphyLabelClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) ephy_label_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (EphyLabel), + 32, /* n_preallocs */ + (GInstanceInitFunc) ephy_label_init, + }; + + label_type = g_type_register_static (GTK_TYPE_MISC, "EphyLabel", + &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 +ephy_label_class_init (EphyLabelClass *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 = g_type_class_peek_parent (class); + + gobject_class->set_property = ephy_label_set_property; + gobject_class->get_property = ephy_label_get_property; + gobject_class->finalize = ephy_label_finalize; + + object_class->destroy = ephy_label_destroy; + + widget_class->size_request = ephy_label_size_request; + widget_class->size_allocate = ephy_label_size_allocate; + widget_class->state_changed = ephy_label_state_changed; + widget_class->style_set = ephy_label_style_set; + widget_class->direction_changed = ephy_label_direction_changed; + widget_class->expose_event = ephy_label_expose; + widget_class->realize = ephy_label_realize; + widget_class->unrealize = ephy_label_unrealize; + widget_class->map = ephy_label_map; + widget_class->unmap = ephy_label_unmap; + widget_class->button_press_event = ephy_label_button_press; + widget_class->button_release_event = ephy_label_button_release; + widget_class->motion_notify_event = ephy_label_motion; + widget_class->hierarchy_changed = ephy_label_hierarchy_changed; + widget_class->screen_changed = ephy_label_screen_changed; + widget_class->mnemonic_activate = ephy_label_mnemonic_activate; + widget_class->focus = ephy_label_focus; + + class->move_cursor = ephy_label_move_cursor; + class->copy_clipboard = ephy_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 (EphyLabelClass, move_cursor), + NULL, NULL, + ephy_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 (EphyLabelClass, copy_clipboard), + NULL, NULL, + g_cclosure_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 (EphyLabelClass, populate_popup), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + GTK_TYPE_MENU); + + g_object_class_install_property (gobject_class, + PROP_LABEL, + g_param_spec_string ("label", + P_("Label"), + P_("The text of the label"), + NULL, + G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, + PROP_ATTRIBUTES, + g_param_spec_boxed ("attributes", + P_("Attributes"), + P_("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", + P_("Use markup"), + P_("The text of the label includes XML markup. See pango_parse_markup()"), + FALSE, + G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, + PROP_USE_UNDERLINE, + g_param_spec_boolean ("use_underline", + P_("Use underline"), + P_("If set, an underline in the text indicates the next character should be used for the mnemonic accelerator key"), + FALSE, + G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, + PROP_JUSTIFY, + g_param_spec_enum ("justify", + P_("Justification"), + P_("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)); + + g_object_class_install_property (gobject_class, + PROP_PATTERN, + g_param_spec_string ("pattern", + P_("Pattern"), + P_("A string with _ characters in positions correspond to characters in the text to underline"), + NULL, + G_PARAM_WRITABLE)); + + g_object_class_install_property (gobject_class, + PROP_WRAP, + g_param_spec_boolean ("wrap", + P_("Line wrap"), + P_("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", + P_("Selectable"), + P_("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", + P_("Mnemonic key"), + P_("The mnemonic accelerator key for this label"), + 0, + G_MAXUINT, + GDK_VoidSymbol, + G_PARAM_READABLE)); + g_object_class_install_property (gobject_class, + PROP_MNEMONIC_WIDGET, + g_param_spec_object ("mnemonic_widget", + P_("Mnemonic widget"), + P_("The widget to be activated when the label's mnemonic " + "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", + P_("Cursor Position"), + P_("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", + P_("Selection Bound"), + P_("The position of the opposite end of the selection from the cursor in chars"), + 0, + G_MAXINT, + 0, + G_PARAM_READABLE)); + + /** + * EphyLabel:ellipsize: + * + * The preferred place to ellipsize the string, if the label does not have + * enough room to display the entire string, specified as a #PangoEllisizeMode. + * + * Note that setting this property to a value other than %PANGO_ELLIPSIZE_NONE + * has the side-effect that the label requests only enough space to display the + * ellipsis "...". Ellipsizing labels must be packed in a container which + * ensures that the label gets a reasonable size allocated. In particular, + * this means that ellipsizing labels don't work well in notebook tabs, unless + * the tab's ::tab-expand property is set to %TRUE. + * + * Since: 2.6 + */ + g_object_class_install_property (gobject_class, + PROP_ELLIPSIZE, + g_param_spec_enum ("ellipsize", + P_("Ellipsize"), + P_("The preferred place to ellipsize the string, if the label does not have enough room to display the entire string, if at all"), + PANGO_TYPE_ELLIPSIZE_MODE, + PANGO_ELLIPSIZE_NONE, + G_PARAM_READWRITE)); + + /* + * 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 +ephy_label_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + EphyLabel *label; + + label = EPHY_LABEL (object); + + switch (prop_id) + { + case PROP_LABEL: + ephy_label_set_label (label, g_value_get_string (value)); + break; + case PROP_ATTRIBUTES: + ephy_label_set_attributes (label, g_value_get_boxed (value)); + break; + case PROP_USE_MARKUP: + ephy_label_set_use_markup (label, g_value_get_boolean (value)); + break; + case PROP_USE_UNDERLINE: + ephy_label_set_use_underline (label, g_value_get_boolean (value)); + break; + case PROP_JUSTIFY: + ephy_label_set_justify (label, g_value_get_enum (value)); + break; + case PROP_PATTERN: + ephy_label_set_pattern (label, g_value_get_string (value)); + break; + case PROP_WRAP: + ephy_label_set_line_wrap (label, g_value_get_boolean (value)); + break; + case PROP_SELECTABLE: + ephy_label_set_selectable (label, g_value_get_boolean (value)); + break; + case PROP_MNEMONIC_WIDGET: + ephy_label_set_mnemonic_widget (label, (GtkWidget*) g_value_get_object (value)); + break; + case PROP_ELLIPSIZE: + ephy_label_set_ellipsize (label, g_value_get_enum (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +ephy_label_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + EphyLabel *label; + + label = EPHY_LABEL (object); + + switch (prop_id) + { + case PROP_LABEL: + g_value_set_string (value, label->label); + break; + case PROP_ATTRIBUTES: + g_value_set_boxed (value, label->attrs); + break; + case PROP_USE_MARKUP: + g_value_set_boolean (value, label->use_markup); + break; + case PROP_USE_UNDERLINE: + g_value_set_boolean (value, label->use_underline); + break; + case PROP_JUSTIFY: + g_value_set_enum (value, label->jtype); + break; + case PROP_WRAP: + g_value_set_boolean (value, label->wrap); + break; + case PROP_SELECTABLE: + g_value_set_boolean (value, ephy_label_get_selectable (label)); + break; + case PROP_MNEMONIC_KEYVAL: + g_value_set_uint (value, label->mnemonic_keyval); + break; + 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; + case PROP_ELLIPSIZE: + g_value_set_enum (value, label->ellipsize); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +ephy_label_init (EphyLabel *label) +{ + GTK_WIDGET_SET_FLAGS (label, GTK_NO_WINDOW); + + label->label = NULL; + + label->jtype = GTK_JUSTIFY_LEFT; + label->wrap = FALSE; + label->ellipsize = PANGO_ELLIPSIZE_NONE; + + label->use_underline = FALSE; + label->use_markup = FALSE; + + label->mnemonic_keyval = GDK_VoidSymbol; + label->layout = NULL; + label->text = NULL; + label->attrs = NULL; + + label->mnemonic_widget = NULL; + label->mnemonic_window = NULL; + + ephy_label_set_text (label, ""); +} + +/** + * ephy_label_new: + * @str: The text of the label + * + * Creates a new label with the given text inside it. You can + * pass %NULL to get an empty label widget. + * + * Return value: the new #EphyLabel + **/ +GtkWidget* +ephy_label_new (const gchar *str) +{ + EphyLabel *label; + + label = g_object_new (EPHY_TYPE_LABEL, NULL); + + if (str && *str) + ephy_label_set_text (label, str); + + return GTK_WIDGET (label); +} + +/** + * ephy_label_new_with_mnemonic: + * @str: The text of the label, with an underscore in front of the + * mnemonic character + * + * Creates a new #EphyLabel, containing the text in @str. + * + * If characters in @str are preceded by an underscore, they are + * underlined. If you need a literal underscore character in a label, use + * '__' (two underscores). The first underlined character represents a + * keyboard accelerator called a mnemonic. The mnemonic key can be used + * to activate another widget, chosen automatically, or explicitly using + * ephy_label_set_mnemonic_widget(). + * + * If ephy_label_set_mnemonic_widget() + * is not called, then the first activatable ancestor of the #EphyLabel + * will be chosen as the mnemonic widget. For instance, if the + * label is inside a button or menu item, the button or menu item will + * automatically become the mnemonic widget and be activated by + * the mnemonic. + * + * Return value: the new #EphyLabel + **/ +GtkWidget* +ephy_label_new_with_mnemonic (const gchar *str) +{ + EphyLabel *label; + + label = g_object_new (EPHY_TYPE_LABEL, NULL); + + if (str && *str) + ephy_label_set_text_with_mnemonic (label, str); + + return GTK_WIDGET (label); +} + +static gboolean +ephy_label_mnemonic_activate (GtkWidget *widget, + gboolean group_cycling) +{ + GtkWidget *parent; + + if (EPHY_LABEL (widget)->mnemonic_widget) + return gtk_widget_mnemonic_activate (EPHY_LABEL (widget)->mnemonic_widget, group_cycling); + + /* Try to find the widget to activate by traversing the + * widget's ancestry. + */ + parent = widget->parent; + + if (parent && GTK_IS_NOTEBOOK (parent)) + return FALSE; + + while (parent) + { + if (GTK_WIDGET_CAN_FOCUS (parent) || + (!group_cycling && GTK_WIDGET_GET_CLASS (parent)->activate_signal) || + (parent->parent && GTK_IS_NOTEBOOK (parent->parent)) || + (GTK_IS_MENU_ITEM (parent))) + return gtk_widget_mnemonic_activate (parent, group_cycling); + parent = parent->parent; + } + + /* barf if there was nothing to activate */ + g_warning ("Couldn't find a target for a mnemonic activation."); + gdk_display_beep (gtk_widget_get_display (widget)); + + return FALSE; +} + +static void +ephy_label_setup_mnemonic (EphyLabel *label, + guint last_key) +{ + GtkWidget *toplevel; + + if (last_key != GDK_VoidSymbol && label->mnemonic_window) + { + gtk_window_remove_mnemonic (label->mnemonic_window, + last_key, + GTK_WIDGET (label)); + label->mnemonic_window = NULL; + } + + if (label->mnemonic_keyval == GDK_VoidSymbol) + return; + + toplevel = gtk_widget_get_toplevel (GTK_WIDGET (label)); + if (GTK_WIDGET_TOPLEVEL (toplevel)) + { + gtk_window_add_mnemonic (GTK_WINDOW (toplevel), + label->mnemonic_keyval, + GTK_WIDGET (label)); + label->mnemonic_window = GTK_WINDOW (toplevel); + } +} + +static void +ephy_label_hierarchy_changed (GtkWidget *widget, + GtkWidget *old_toplevel) +{ + EphyLabel *label = EPHY_LABEL (widget); + + ephy_label_setup_mnemonic (label, label->mnemonic_keyval); +} + +static void +ephy_label_screen_changed (GtkWidget *widget, + GdkScreen *old_screen) +{ + ephy_label_clear_layout (EPHY_LABEL (widget)); +} + +static void +label_mnemonic_widget_weak_notify (gpointer data, + GObject *where_the_object_was) +{ + EphyLabel *label = data; + + label->mnemonic_widget = NULL; + g_object_notify (G_OBJECT (label), "mnemonic_widget"); +} + +/** + * ephy_label_set_mnemonic_widget: + * @label: a #EphyLabel + * @widget: the target #GtkWidget + * + * If the label has been set so that it has an mnemonic key (using + * i.e. ephy_label_set_markup_with_mnemonic(), + * ephy_label_set_text_with_mnemonic(), ephy_label_new_with_mnemonic() + * or the "use_underline" property) the label can be associated with a + * widget that is the target of the mnemonic. When the label is inside + * a widget (like a #GtkButton or a #GtkNotebook tab) it is + * automatically associated with the correct widget, but sometimes + * (i.e. when the target is a #GtkEntry next to the label) you need to + * set it explicitly using this function. + * + * The target widget will be accelerated by emitting "mnemonic_activate" on it. + * The default handler for this signal will activate the widget if there are no + * mnemonic collisions and toggle focus between the colliding widgets otherwise. + **/ +void +ephy_label_set_mnemonic_widget (EphyLabel *label, + GtkWidget *widget) +{ + g_return_if_fail (EPHY_IS_LABEL (label)); + if (widget) + g_return_if_fail (GTK_IS_WIDGET (widget)); + + if (label->mnemonic_widget) + { + gtk_widget_remove_mnemonic_label (label->mnemonic_widget, GTK_WIDGET (label)); + g_object_weak_unref (G_OBJECT (label->mnemonic_widget), + label_mnemonic_widget_weak_notify, + label); + } + label->mnemonic_widget = widget; + if (label->mnemonic_widget) + { + g_object_weak_ref (G_OBJECT (label->mnemonic_widget), + label_mnemonic_widget_weak_notify, + label); + gtk_widget_add_mnemonic_label (label->mnemonic_widget, GTK_WIDGET (label)); + } + + g_object_notify (G_OBJECT (label), "mnemonic_widget"); +} + +/** + * ephy_label_get_mnemonic_widget: + * @label: a #EphyLabel + * + * Retrieves the target of the mnemonic (keyboard shortcut) of this + * label. See ephy_label_set_mnemonic_widget (). + * + * Return value: the target of the label's mnemonic, or %NULL if none + * has been set and the default algorithm will be used. + **/ +GtkWidget * +ephy_label_get_mnemonic_widget (EphyLabel *label) +{ + g_return_val_if_fail (EPHY_IS_LABEL (label), NULL); + + return label->mnemonic_widget; +} + +/** + * ephy_label_get_mnemonic_keyval: + * @label: a #EphyLabel + * + * If the label has been set so that it has an mnemonic key this function + * 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 + **/ +guint +ephy_label_get_mnemonic_keyval (EphyLabel *label) +{ + g_return_val_if_fail (EPHY_IS_LABEL (label), GDK_VoidSymbol); + + return label->mnemonic_keyval; +} + +static void +ephy_label_set_text_internal (EphyLabel *label, + gchar *str) +{ + g_free (label->text); + + label->text = str; + + ephy_label_select_region_index (label, 0, 0); +} + +static void +ephy_label_set_label_internal (EphyLabel *label, + gchar *str) +{ + g_free (label->label); + + label->label = str; + + g_object_notify (G_OBJECT (label), "label"); +} + +static void +ephy_label_set_use_markup_internal (EphyLabel *label, + gboolean val) +{ + val = val != FALSE; + if (label->use_markup != val) + { + g_object_notify (G_OBJECT (label), "use_markup"); + label->use_markup = val; + } +} + +static void +ephy_label_set_use_underline_internal (EphyLabel *label, + gboolean val) +{ + val = val != FALSE; + if (label->use_underline != val) + { + g_object_notify (G_OBJECT (label), "use_underline"); + label->use_underline = val; + } +} + +static void +ephy_label_set_attributes_internal (EphyLabel *label, + PangoAttrList *attrs) +{ + if (attrs) + pango_attr_list_ref (attrs); + + if (label->attrs) + pango_attr_list_unref (label->attrs); + + if (!label->use_markup && !label->use_underline) + { + if (attrs) + pango_attr_list_ref (attrs); + if (label->effective_attrs) + pango_attr_list_unref (label->effective_attrs); + label->effective_attrs = attrs; + } + + label->attrs = attrs; + g_object_notify (G_OBJECT (label), "attributes"); +} + + +/* Calculates text, attrs and mnemonic_keyval from + * label, use_underline and use_markup + */ +static void +ephy_label_recalculate (EphyLabel *label) +{ + if (label->use_markup) + set_markup (label, label->label, label->use_underline); + else + { + if (label->use_underline) + ephy_label_set_uline_text_internal (label, label->label); + else + { + ephy_label_set_text_internal (label, g_strdup (label->label)); + if (label->attrs) + pango_attr_list_ref (label->attrs); + if (label->effective_attrs) + pango_attr_list_unref (label->effective_attrs); + label->effective_attrs = label->attrs; + } + } + + if (!label->use_underline) + { + guint keyval = label->mnemonic_keyval; + + label->mnemonic_keyval = GDK_VoidSymbol; + ephy_label_setup_mnemonic (label, keyval); + } + + ephy_label_clear_layout (label); + gtk_widget_queue_resize (GTK_WIDGET (label)); +} + +/** + * ephy_label_set_text: + * @label: a #EphyLabel + * @str: The text you want to set. + * + * Sets the text within the #EphyLabel widget. It overwrites any text that + * was there before. + * + * This will also clear any previously set mnemonic accelerators. + **/ +void +ephy_label_set_text (EphyLabel *label, + const gchar *str) +{ + g_return_if_fail (EPHY_IS_LABEL (label)); + + g_object_freeze_notify (G_OBJECT (label)); + + ephy_label_set_label_internal (label, g_strdup (str ? str : "")); + ephy_label_set_use_markup_internal (label, FALSE); + ephy_label_set_use_underline_internal (label, FALSE); + + ephy_label_recalculate (label); + + g_object_thaw_notify (G_OBJECT (label)); +} + +/** + * ephy_label_set_attributes: + * @label: a #EphyLabel + * @attrs: a #PangoAttrList + * + * Sets a #PangoAttrList; the attributes in the list are applied to the + * label text. The attributes set with this function will be ignored + * if the "use_underline" property or the "use_markup" property + * is %TRUE. + **/ +void +ephy_label_set_attributes (EphyLabel *label, + PangoAttrList *attrs) +{ + g_return_if_fail (EPHY_IS_LABEL (label)); + + ephy_label_set_attributes_internal (label, attrs); + + ephy_label_clear_layout (label); + gtk_widget_queue_resize (GTK_WIDGET (label)); +} + +/** + * ephy_label_get_attributes: + * @label: a #EphyLabel + * + * Gets the attribute list that was set on the label using + * ephy_label_set_attributes(), if any. This function does + * not reflect attributes that come from the labels markup + * (see ephy_label_set_markup()). If you want to get the + * effective attributes for the label, use + * pango_layout_get_attribute (ephy_label_get_layout (label)). + * + * Return value: the attribute list, or %NULL if none was set. + **/ +PangoAttrList * +ephy_label_get_attributes (EphyLabel *label) +{ + g_return_val_if_fail (EPHY_IS_LABEL (label), NULL); + + return label->attrs; +} + +/** + * ephy_label_set_label: + * @label: a #EphyLabel + * @str: the new text to set for the label + * + * Sets the text of the label. The label is interpreted as + * including embedded underlines and/or Pango markup depending + * on the values of label->use_underline and label->use_markup. + **/ +void +ephy_label_set_label (EphyLabel *label, + const gchar *str) +{ + guint last_keyval; + + g_return_if_fail (EPHY_IS_LABEL (label)); + g_return_if_fail (str != NULL); + + last_keyval = label->mnemonic_keyval; + + ephy_label_set_label_internal (label, g_strdup (str)); + ephy_label_recalculate (label); + if (last_keyval != label->mnemonic_keyval) + ephy_label_setup_mnemonic (label, last_keyval); +} + +/** + * ephy_label_get_label: + * @label: a #EphyLabel + * + * Fetches the text from a label widget including any embedded + * underlines indicating mnemonics and Pango markup. (See + * ephy_label_get_text ()). + * + * Return value: the text of the label widget. This string is + * owned by the widget and must not be modified or freed. + **/ +G_CONST_RETURN gchar * +ephy_label_get_label (EphyLabel *label) +{ + g_return_val_if_fail (EPHY_IS_LABEL (label), NULL); + + return label->label; +} + +static void +set_markup (EphyLabel *label, + const gchar *str, + gboolean with_uline) +{ + gchar *text = NULL; + GError *error = NULL; + PangoAttrList *attrs = NULL; + gunichar accel_char = 0; + + if (!pango_parse_markup (str, + -1, + with_uline ? '_' : 0, + &attrs, + &text, + with_uline ? &accel_char : NULL, + &error)) + { + g_warning ("Failed to set label from markup due to error parsing markup: %s", + error->message); + g_error_free (error); + return; + } + + if (text) + ephy_label_set_text_internal (label, text); + + if (attrs) + { + if (label->effective_attrs) + pango_attr_list_unref (label->effective_attrs); + label->effective_attrs = attrs; + } + + if (accel_char != 0) + label->mnemonic_keyval = gdk_keyval_to_lower (gdk_unicode_to_keyval (accel_char)); + else + label->mnemonic_keyval = GDK_VoidSymbol; +} + +/** + * ephy_label_set_markup: + * @label: a #EphyLabel + * @str: a markup string (see <link linkend="PangoMarkupFormat">Pango markup format</link>) + * + * 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 +ephy_label_set_markup (EphyLabel *label, + const gchar *str) +{ + g_return_if_fail (EPHY_IS_LABEL (label)); + + ephy_label_set_label_internal (label, g_strdup (str ? str : "")); + ephy_label_set_use_markup_internal (label, TRUE); + ephy_label_set_use_underline_internal (label, FALSE); + + ephy_label_recalculate (label); +} + +/** + * ephy_label_set_markup_with_mnemonic: + * @label: a #EphyLabel + * @str: a markup string (see <link linkend="PangoMarkupFormat">Pango markup format</link>) + * + * 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. + * + * The mnemonic key can be used to activate another widget, chosen automatically, + * or explicitly using ephy_label_set_mnemonic_widget(). + **/ +void +ephy_label_set_markup_with_mnemonic (EphyLabel *label, + const gchar *str) +{ + guint last_keyval; + g_return_if_fail (EPHY_IS_LABEL (label)); + + last_keyval = label->mnemonic_keyval; + ephy_label_set_label_internal (label, g_strdup (str ? str : "")); + ephy_label_set_use_markup_internal (label, TRUE); + ephy_label_set_use_underline_internal (label, TRUE); + + ephy_label_recalculate (label); + ephy_label_setup_mnemonic (label, last_keyval); +} + +/** + * ephy_label_get_text: + * @label: a #EphyLabel + * + * Fetches the text from a label widget, as displayed on the + * screen. This does not include any embedded underlines + * indicating mnemonics or Pango markup. (See ephy_label_get_label()) + * + * Return value: the text in the label widget. This is the internal + * string used by the label, and must not be modified. + **/ +G_CONST_RETURN gchar * +ephy_label_get_text (EphyLabel *label) +{ + g_return_val_if_fail (EPHY_IS_LABEL (label), NULL); + + return label->text; +} + +static PangoAttrList * +ephy_label_pattern_to_attrs (EphyLabel *label, + const gchar *pattern) +{ + const char *start; + const char *p = label->text; + const char *q = pattern; + PangoAttrList *attrs; + + attrs = pango_attr_list_new (); + + while (1) + { + while (*p && *q && *q != '_') + { + p = g_utf8_next_char (p); + q++; + } + start = p; + while (*p && *q && *q == '_') + { + p = g_utf8_next_char (p); + q++; + } + + if (p > start) + { + PangoAttribute *attr = pango_attr_underline_new (PANGO_UNDERLINE_LOW); + attr->start_index = start - label->text; + attr->end_index = p - label->text; + + pango_attr_list_insert (attrs, attr); + } + else + break; + } + + return attrs; +} + +static void +ephy_label_set_pattern_internal (EphyLabel *label, + const gchar *pattern) +{ + PangoAttrList *attrs; + g_return_if_fail (EPHY_IS_LABEL (label)); + + attrs = ephy_label_pattern_to_attrs (label, pattern); + + if (label->effective_attrs) + pango_attr_list_unref (label->effective_attrs); + label->effective_attrs = attrs; +} + +void +ephy_label_set_pattern (EphyLabel *label, + const gchar *pattern) +{ + g_return_if_fail (EPHY_IS_LABEL (label)); + + ephy_label_set_pattern_internal (label, pattern); + + ephy_label_clear_layout (label); + gtk_widget_queue_resize (GTK_WIDGET (label)); +} + + +/** + * ephy_label_set_justify: + * @label: a #EphyLabel + * @jtype: a #GtkJustification + * + * Sets the alignment of the lines in the text of the label relative to + * each other. %GTK_JUSTIFY_LEFT is the default value when the + * widget is first created with ephy_label_new(). If you instead want + * to set the alignment of the label as a whole, use + * gtk_misc_set_alignment() instead. ephy_label_set_justify() has no + * effect on labels containing only a single line. + **/ +void +ephy_label_set_justify (EphyLabel *label, + GtkJustification jtype) +{ + g_return_if_fail (EPHY_IS_LABEL (label)); + g_return_if_fail (jtype >= GTK_JUSTIFY_LEFT && jtype <= GTK_JUSTIFY_FILL); + + if ((GtkJustification) label->jtype != jtype) + { + label->jtype = jtype; + + /* No real need to be this drastic, but easier than duplicating the code */ + ephy_label_clear_layout (label); + + g_object_notify (G_OBJECT (label), "justify"); + gtk_widget_queue_resize (GTK_WIDGET (label)); + } +} + +/** + * ephy_label_get_justify: + * @label: a #EphyLabel + * + * Returns the justification of the label. See ephy_label_set_justify (). + * + * Return value: #GtkJustification + **/ +GtkJustification +ephy_label_get_justify (EphyLabel *label) +{ + g_return_val_if_fail (EPHY_IS_LABEL (label), 0); + + return label->jtype; +} + + +/** + * ephy_label_set_ellipsize: + * @label: a #EphyLabel + * @mode: a #PangoEllipsizeMode + * + * Sets the mode used to ellipsize (add an ellipsis: "...") to the text if there + * is not enough space to render the entire string. + * + * Since: 2.6 + **/ +void +ephy_label_set_ellipsize (EphyLabel *label, + PangoEllipsizeMode mode) +{ + g_return_if_fail (EPHY_IS_LABEL (label)); + g_return_if_fail (mode >= PANGO_ELLIPSIZE_NONE && mode <= PANGO_ELLIPSIZE_END); + + if ((PangoEllipsizeMode) label->ellipsize != mode) + { + label->ellipsize = mode; + + /* No real need to be this drastic, but easier than duplicating the code */ + ephy_label_clear_layout (label); + + g_object_notify (G_OBJECT (label), "ellipsize"); + gtk_widget_queue_resize (GTK_WIDGET (label)); + } +} + +/** + * ephy_label_get_ellipsize: + * @label: a #EphyLabel + * + * Returns the ellipsizing position of the label. See ephy_label_set_ellipsize(). + * + * Return value: #PangoEllipsizeMode + * + * Since: 2.6 + **/ +PangoEllipsizeMode +ephy_label_get_ellipsize (EphyLabel *label) +{ + g_return_val_if_fail (EPHY_IS_LABEL (label), PANGO_ELLIPSIZE_NONE); + + return label->ellipsize; +} + +/** + * ephy_label_set_line_wrap: + * @label: a #EphyLabel + * @wrap: the setting + * + * Toggles line wrapping within the #EphyLabel 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 +ephy_label_set_line_wrap (EphyLabel *label, + gboolean wrap) +{ + g_return_if_fail (EPHY_IS_LABEL (label)); + + wrap = wrap != FALSE; + + if (label->wrap != wrap) + { + label->wrap = wrap; + g_object_notify (G_OBJECT (label), "wrap"); + + gtk_widget_queue_resize (GTK_WIDGET (label)); + } +} + +/** + * ephy_label_get_line_wrap: + * @label: a #EphyLabel + * + * Returns whether lines in the label are automatically wrapped. See ephy_label_set_line_wrap (). + * + * Return value: %TRUE if the lines of the label are automatically wrapped. + */ +gboolean +ephy_label_get_line_wrap (EphyLabel *label) +{ + g_return_val_if_fail (EPHY_IS_LABEL (label), FALSE); + + return label->wrap; +} + +void +ephy_label_get (EphyLabel *label, + gchar **str) +{ + g_return_if_fail (EPHY_IS_LABEL (label)); + g_return_if_fail (str != NULL); + + *str = label->text; +} + +static void +ephy_label_destroy (GtkObject *object) +{ + EphyLabel *label = EPHY_LABEL (object); + + ephy_label_set_mnemonic_widget (label, NULL); + + GTK_OBJECT_CLASS (parent_class)->destroy (object); +} + +static void +ephy_label_finalize (GObject *object) +{ + EphyLabel *label; + + g_return_if_fail (EPHY_IS_LABEL (object)); + + label = EPHY_LABEL (object); + + g_free (label->label); + g_free (label->text); + + if (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->effective_attrs); + + g_free (label->select_info); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +ephy_label_clear_layout (EphyLabel *label) +{ + if (label->layout) + { + g_object_unref (label->layout); + label->layout = NULL; + } +} + +typedef struct _LabelWrapWidth LabelWrapWidth; +struct _LabelWrapWidth +{ + gint width; + PangoFontDescription *font_desc; +}; + +static void +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 (EphyLabel *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 +ephy_label_ensure_layout (EphyLabel *label) +{ + GtkWidget *widget; + PangoRectangle logical_rect; + gint rwidth, rheight; + gboolean rtl; + + widget = GTK_WIDGET (label); + + rtl = gtk_widget_get_direction(widget) == GTK_TEXT_DIR_RTL; + rwidth = label->misc.xpad * 2; + rheight = label->misc.ypad * 2; + + if (!label->layout) + { + PangoAlignment align = PANGO_ALIGN_LEFT; /* Quiet gcc */ + + label->layout = gtk_widget_create_pango_layout (widget, label->text); + + if (label->effective_attrs) + pango_layout_set_attributes (label->layout, label->effective_attrs); + + switch (label->jtype) + { + case GTK_JUSTIFY_LEFT: + align = rtl ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_LEFT; + break; + case GTK_JUSTIFY_RIGHT: + align = rtl ? PANGO_ALIGN_LEFT : PANGO_ALIGN_RIGHT; + break; + case GTK_JUSTIFY_CENTER: + align = PANGO_ALIGN_CENTER; + break; + case GTK_JUSTIFY_FILL: + /* FIXME: This just doesn't work to do this */ + align = rtl ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_LEFT; + pango_layout_set_justify (label->layout, TRUE); + break; + default: + g_assert_not_reached(); + } + + pango_layout_set_alignment (label->layout, align); + pango_layout_set_ellipsize (label->layout, label->ellipsize); + + if (label->wrap) + { + GtkWidgetAuxInfo *aux_info; + gint longest_paragraph; + gint width, height; + + aux_info = NULL; /* EPHY HACK */ + if (aux_info && aux_info->width > 0) + pango_layout_set_width (label->layout, aux_info->width * PANGO_SCALE); + else + { + 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; + + 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; + + /* 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) + { + gint nlines, perfect_width; + + nlines = pango_layout_get_line_count (label->layout); + perfect_width = (longest_paragraph + nlines - 1) / nlines; + + if (perfect_width < 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 + { + gint mid_width = (perfect_width + width) / 2; + + if (mid_width > perfect_width) + { + 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); + } + } + else /* !label->wrap */ + pango_layout_set_width (label->layout, -1); + } +} + +static void +ephy_label_size_request (GtkWidget *widget, + GtkRequisition *requisition) +{ + EphyLabel *label; + gint width, height; + PangoRectangle logical_rect; + GtkWidgetAuxInfo *aux_info; + + g_return_if_fail (EPHY_IS_LABEL (widget)); + g_return_if_fail (requisition != NULL); + + label = EPHY_LABEL (widget); + + /* + * 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) + ephy_label_clear_layout (label); + + ephy_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 = NULL; /* EPHY HACK */ + + if (label->ellipsize) + { + PangoContext *context; + PangoFontMetrics *metrics; + gint char_width; + + /* The minimum size for ellipsized labels is ~ 3 chars */ + context = pango_layout_get_context (label->layout); + metrics = pango_context_get_metrics (context, widget->style->font_desc, NULL); + + char_width = pango_font_metrics_get_approximate_char_width (metrics); + pango_font_metrics_unref (metrics); + + width += (PANGO_PIXELS (char_width) * 3); + } + else + { + 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; +} + +static void +ephy_label_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + EphyLabel *label; + + label = EPHY_LABEL (widget); + + (* GTK_WIDGET_CLASS (parent_class)->size_allocate) (widget, allocation); + + if (label->ellipsize) + pango_layout_set_width (label->layout, allocation->width * PANGO_SCALE); + + if (label->select_info && label->select_info->window) + { + gdk_window_move_resize (label->select_info->window, + allocation->x, + allocation->y, + allocation->width, + allocation->height); + } +} + +static void +ephy_label_state_changed (GtkWidget *widget, + GtkStateType prev_state) +{ + EphyLabel *label; + + label = EPHY_LABEL (widget); + + if (label->select_info) + ephy_label_select_region (label, 0, 0); + + if (GTK_WIDGET_CLASS (parent_class)->state_changed) + GTK_WIDGET_CLASS (parent_class)->state_changed (widget, prev_state); +} + +static void +ephy_label_style_set (GtkWidget *widget, + GtkStyle *previous_style) +{ + EphyLabel *label; + + g_return_if_fail (EPHY_IS_LABEL (widget)); + + label = EPHY_LABEL (widget); + + /* We have to clear the layout, fonts etc. may have changed */ + ephy_label_clear_layout (label); +} + +static void +ephy_label_direction_changed (GtkWidget *widget, + GtkTextDirection previous_dir) +{ + EphyLabel *label = EPHY_LABEL (widget); + + if (label->layout) + pango_layout_context_changed (label->layout); + + GTK_WIDGET_CLASS (parent_class)->direction_changed (widget, previous_dir); +} + +#if 0 +static void +ephy_label_paint_word (EphyLabel *label, + gint x, + gint y, + EphyLabelWord *word, + GdkRectangle *area) +{ + GtkWidget *widget = GTK_WIDGET (label); + EphyLabelULine *uline; + gchar *tmp_str; + + tmp_str = gdk_wcstombs (word->beginning); + if (tmp_str) + { + gtk_paint_string (widget->style, widget->window, widget->state, + area, widget, "label", + x + word->x, + y + word->y, + tmp_str); + g_free (tmp_str); + } + + for (uline = word->uline; uline; uline = uline->next) + gtk_paint_hline (widget->style, widget->window, + widget->state, area, + widget, "label", + x + uline->x1, x + uline->x2, y + uline->y); +} +#endif + +static void +get_layout_location (EphyLabel *label, + gint *xp, + gint *yp) +{ + GtkMisc *misc; + GtkWidget *widget; + gfloat xalign; + gint req_width, x, y; + + misc = GTK_MISC (label); + widget = GTK_WIDGET (label); + + if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR) + xalign = misc->xalign; + else + xalign = 1.0 - misc->xalign; + + if (label->ellipsize) + { + PangoRectangle ink_rect; + + pango_layout_get_extents (label->layout, &ink_rect, NULL); + + req_width = PANGO_PIXELS (ink_rect.width); + } + else + req_width = widget->requisition.width; + + x = floor (widget->allocation.x + (gint)misc->xpad + + xalign * (widget->allocation.width - req_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 - + req_width - misc->xpad); + + y = floor (widget->allocation.y + (gint)misc->ypad + + MAX (((widget->allocation.height - widget->requisition.height) * misc->yalign) + + 0.5, 0)); + + if (xp) + *xp = x; + + if (yp) + *yp = y; +} + +static void +draw_insertion_cursor (EphyLabel *label, + GdkRectangle *cursor_location, + gboolean is_primary, + PangoDirection direction, + gboolean draw_arrow) +{ + GtkWidget *widget = GTK_WIDGET (label); + GtkTextDirection text_dir; + + if (direction == PANGO_DIRECTION_LTR) + text_dir = GTK_TEXT_DIR_LTR; + else + text_dir = GTK_TEXT_DIR_RTL; + + gtk_draw_insertion_cursor (widget, widget->window, NULL, + cursor_location, + is_primary, text_dir, draw_arrow); +} + +static PangoDirection +get_cursor_direction (EphyLabel *label) +{ + GSList *l; + + g_assert (label->select_info); + + ephy_label_ensure_layout (label); + + for (l = pango_layout_get_lines (label->layout); l; l = l->next) + { + PangoLayoutLine *line = l->data; + + /* If label->select_info->selection_end is at the very end of + * the line, we don't know if the cursor is on this line or + * the next without looking ahead at the next line. (End + * of paragraph is different from line break.) But it's + * definitely in this paragraph, which is good enough + * to figure out the resolved direction. + */ + if (line->start_index + line->length >= label->select_info->selection_end) + return line->resolved_dir; + } + + return PANGO_DIRECTION_LTR; +} + +static void +ephy_label_draw_cursor (EphyLabel *label, gint xoffset, gint yoffset) +{ + if (label->select_info == NULL) + return; + + if (GTK_WIDGET_DRAWABLE (label)) + { + GtkWidget *widget = GTK_WIDGET (label); + + PangoDirection keymap_direction; + PangoDirection cursor_direction; + PangoRectangle strong_pos, weak_pos; + gboolean split_cursor; + PangoRectangle *cursor1 = NULL; + PangoRectangle *cursor2 = NULL; + GdkRectangle cursor_location; + PangoDirection dir1 = PANGO_DIRECTION_NEUTRAL; + PangoDirection dir2 = PANGO_DIRECTION_NEUTRAL; + + keymap_direction = gdk_keymap_get_direction (gdk_keymap_get_for_display (gtk_widget_get_display (widget))); + cursor_direction = get_cursor_direction (label); + + ephy_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 = cursor_direction; + + if (split_cursor) + { + cursor1 = &strong_pos; + + if (strong_pos.x != weak_pos.x || + strong_pos.y != weak_pos.y) + { + dir2 = (cursor_direction == PANGO_DIRECTION_LTR) ? PANGO_DIRECTION_RTL : PANGO_DIRECTION_LTR; + cursor2 = &weak_pos; + } + } + else + { + if (keymap_direction == cursor_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); + + draw_insertion_cursor (label, + &cursor_location, TRUE, dir1, + dir2 != PANGO_DIRECTION_NEUTRAL); + + if (dir2 != PANGO_DIRECTION_NEUTRAL) + { + 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); + + draw_insertion_cursor (label, + &cursor_location, FALSE, dir2, + TRUE); + } + } +} + + +static gint +ephy_label_expose (GtkWidget *widget, + GdkEventExpose *event) +{ + EphyLabel *label; + gint x, y; + + g_return_val_if_fail (EPHY_IS_LABEL (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + label = EPHY_LABEL (widget); + + ephy_label_ensure_layout (label); + + if (GTK_WIDGET_VISIBLE (widget) && GTK_WIDGET_MAPPED (widget) && + label->text && (*label->text != '\0')) + { + get_layout_location (label, &x, &y); + + gtk_paint_layout (widget->style, + widget->window, + GTK_WIDGET_STATE (widget), + FALSE, + &event->area, + widget, + "label", + x, y, + label->layout); + + if (label->select_info && + (label->select_info->selection_anchor != + label->select_info->selection_end)) + { + gint range[2]; + GdkRegion *clip; + GtkStateType state; + + range[0] = label->select_info->selection_anchor; + range[1] = label->select_info->selection_end; + + if (range[0] > range[1]) + { + gint tmp = range[0]; + range[0] = range[1]; + range[1] = tmp; + } + + clip = gdk_pango_layout_get_clip_region (label->layout, + x, y, + range, + 1); + + /* FIXME should use gtk_paint, but it can't use a clip + * region + */ + + gdk_gc_set_clip_region (widget->style->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->black_gc, + x, y, + label->layout, + &widget->style->text[state], + &widget->style->base[state]); + + gdk_gc_set_clip_region (widget->style->black_gc, NULL); + gdk_region_destroy (clip); + } + else if (label->select_info && GTK_WIDGET_HAS_FOCUS (widget)) + ephy_label_draw_cursor (label, x, y); + } + + return FALSE; +} + +static void +ephy_label_set_uline_text_internal (EphyLabel *label, + const gchar *str) +{ + guint accel_key = GDK_VoidSymbol; + + gchar *new_str; + gchar *pattern; + const gchar *src; + gchar *dest, *pattern_dest; + gboolean underscore; + + g_return_if_fail (EPHY_IS_LABEL (label)); + g_return_if_fail (str != NULL); + + /* 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); + + underscore = FALSE; + + if (str == NULL) + str = ""; + + src = str; + dest = new_str; + pattern_dest = pattern; + + while (*src) + { + gunichar c; + gchar *next_src; + + c = g_utf8_get_char (src); + if (c == (gunichar)-1) + { + g_warning ("Invalid input string"); + g_free (new_str); + g_free (pattern); + return; + } + next_src = g_utf8_next_char (src); + + if (underscore) + { + if (c == '_') + *pattern_dest++ = ' '; + else + { + *pattern_dest++ = '_'; + if (accel_key == GDK_VoidSymbol) + accel_key = gdk_keyval_to_lower (gdk_unicode_to_keyval (c)); + } + + while (src < next_src) + *dest++ = *src++; + + underscore = FALSE; + } + else + { + if (c == '_') + { + underscore = TRUE; + src = next_src; + } + else + { + while (src < next_src) + *dest++ = *src++; + + *pattern_dest++ = ' '; + } + } + } + *dest = 0; + *pattern_dest = 0; + + ephy_label_set_text_internal (label, new_str); + ephy_label_set_pattern_internal (label, pattern); + + g_free (pattern); + + label->mnemonic_keyval = accel_key; +} + +guint +ephy_label_parse_uline (EphyLabel *label, + const gchar *str) +{ + guint keyval; + guint orig_keyval; + + g_return_val_if_fail (EPHY_IS_LABEL (label), GDK_VoidSymbol); + g_return_val_if_fail (str != NULL, GDK_VoidSymbol); + + orig_keyval = label->mnemonic_keyval; + + g_object_freeze_notify (G_OBJECT (label)); + + ephy_label_set_label_internal (label, g_strdup (str ? str : "")); + ephy_label_set_use_markup_internal (label, FALSE); + ephy_label_set_use_underline_internal (label, TRUE); + + ephy_label_recalculate (label); + + keyval = label->mnemonic_keyval; + label->mnemonic_keyval = GDK_VoidSymbol; + + ephy_label_setup_mnemonic (label, orig_keyval); + + g_object_thaw_notify (G_OBJECT (label)); + + return keyval; +} + +/** + * ephy_label_set_text_with_mnemonic: + * @label: a #EphyLabel + * @str: a string + * + * Sets the label's text from the string @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 ephy_label_set_mnemonic_widget(). + **/ +void +ephy_label_set_text_with_mnemonic (EphyLabel *label, + const gchar *str) +{ + guint last_keyval; + + g_return_if_fail (EPHY_IS_LABEL (label)); + g_return_if_fail (str != NULL); + + last_keyval = label->mnemonic_keyval; + + g_object_freeze_notify (G_OBJECT (label)); + + ephy_label_set_label_internal (label, g_strdup (str ? str : "")); + ephy_label_set_use_markup_internal (label, FALSE); + ephy_label_set_use_underline_internal (label, TRUE); + + ephy_label_recalculate (label); + + ephy_label_setup_mnemonic (label, last_keyval); + + g_object_thaw_notify (G_OBJECT (label)); +} + +static void +ephy_label_realize (GtkWidget *widget) +{ + EphyLabel *label; + + label = EPHY_LABEL (widget); + + (* GTK_WIDGET_CLASS (parent_class)->realize) (widget); + + if (label->select_info) + ephy_label_create_window (label); +} + +static void +ephy_label_unrealize (GtkWidget *widget) +{ + EphyLabel *label; + + label = EPHY_LABEL (widget); + + if (label->select_info) + ephy_label_destroy_window (label); + + (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget); +} + +static void +ephy_label_map (GtkWidget *widget) +{ + EphyLabel *label; + + label = EPHY_LABEL (widget); + + (* GTK_WIDGET_CLASS (parent_class)->map) (widget); + + if (label->select_info) + gdk_window_show (label->select_info->window); +} + +static void +ephy_label_unmap (GtkWidget *widget) +{ + EphyLabel *label; + + label = EPHY_LABEL (widget); + + if (label->select_info) + gdk_window_hide (label->select_info->window); + + (* GTK_WIDGET_CLASS (parent_class)->unmap) (widget); +} + +static void +window_to_layout_coords (EphyLabel *label, + gint *x, + gint *y) +{ + gint lx, ly; + GtkWidget *widget; + + widget = GTK_WIDGET (label); + + /* get layout location in widget->window coords */ + get_layout_location (label, &lx, &ly); + + if (x) + { + *x += widget->allocation.x; /* go to widget->window */ + *x -= lx; /* go to layout */ + } + + if (y) + { + *y += widget->allocation.y; /* go to widget->window */ + *y -= ly; /* go to layout */ + } +} + +#if 0 +static void +layout_to_window_coords (EphyLabel *label, + gint *x, + gint *y) +{ + gint lx, ly; + GtkWidget *widget; + + widget = GTK_WIDGET (label); + + /* get layout location in widget->window coords */ + get_layout_location (label, &lx, &ly); + + if (x) + { + *x += lx; /* go to widget->window */ + *x -= widget->allocation.x; /* go to selection window */ + } + + if (y) + { + *y += ly; /* go to widget->window */ + *y -= widget->allocation.y; /* go to selection window */ + } +} +#endif + +static void +get_layout_index (EphyLabel *label, + gint x, + gint y, + gint *index) +{ + gint trailing = 0; + const gchar *cluster; + const gchar *cluster_end; + + *index = 0; + + ephy_label_ensure_layout (label); + + window_to_layout_coords (label, &x, &y); + + x *= PANGO_SCALE; + y *= PANGO_SCALE; + + pango_layout_xy_to_index (label->layout, + x, y, + index, &trailing); + + + cluster = label->text + *index; + cluster_end = cluster; + while (trailing) + { + cluster_end = g_utf8_next_char (cluster_end); + --trailing; + } + + *index += (cluster_end - cluster); +} + +static void +ephy_label_select_word (EphyLabel *label) +{ + gint min, max; + + gint start_index = ephy_label_move_backward_word (label, label->select_info->selection_end); + gint end_index = ephy_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); + + ephy_label_select_region_index (label, min, max); +} + +static gboolean +ephy_label_button_press (GtkWidget *widget, + GdkEventButton *event) +{ + EphyLabel *label; + gint index = 0; + + label = EPHY_LABEL (widget); + + if (label->select_info == NULL) + return FALSE; + + if (event->button == 1) + { + if (!GTK_WIDGET_HAS_FOCUS (widget)) + gtk_widget_grab_focus (widget); + + if (event->type == GDK_3BUTTON_PRESS) + { + ephy_label_select_region_index (label, 0, strlen (label->text)); + return TRUE; + } + + if (event->type == GDK_2BUTTON_PRESS) + { + ephy_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; + } + + ephy_label_select_region_index (label, min, max); + } + else + { + if (event->type == GDK_3BUTTON_PRESS) + ephy_label_select_region_index (label, 0, strlen (label->text)); + else if (event->type == GDK_2BUTTON_PRESS) + ephy_label_select_word (label); + else + /* start a replacement */ + ephy_label_select_region_index (label, index, index); + } + + return TRUE; + } + else if (event->button == 3 && event->type == GDK_BUTTON_PRESS) + { + ephy_label_do_popup (label, event); + + return TRUE; + + } + return FALSE; +} + +static gboolean +ephy_label_button_release (GtkWidget *widget, + GdkEventButton *event) + +{ + EphyLabel *label; + + label = EPHY_LABEL (widget); + + if (label->select_info == NULL) + return FALSE; + + if (event->button != 1) + return FALSE; + + /* The goal here is to return TRUE iff we ate the + * button press to start selecting. + */ + + return TRUE; +} + +static gboolean +ephy_label_motion (GtkWidget *widget, + GdkEventMotion *event) +{ + EphyLabel *label; + gint index; + gint x, y; + + label = EPHY_LABEL (widget); + + if (label->select_info == NULL) + return FALSE; + + if ((event->state & GDK_BUTTON1_MASK) == 0) + return FALSE; + + gdk_window_get_pointer (label->select_info->window, + &x, &y, NULL); + + get_layout_index (label, x, y, &index); + + ephy_label_select_region_index (label, + label->select_info->selection_anchor, + index); + + return TRUE; +} + +static void +ephy_label_create_window (EphyLabel *label) +{ + GtkWidget *widget; + GdkWindowAttr attributes; + gint attributes_mask; + + g_assert (label->select_info); + g_assert (GTK_WIDGET_REALIZED (label)); + + if (label->select_info->window) + return; + + widget = GTK_WIDGET (label); + + attributes.x = widget->allocation.x; + attributes.y = widget->allocation.y; + attributes.width = widget->allocation.width; + attributes.height = widget->allocation.height; + attributes.window_type = GDK_WINDOW_TEMP; + attributes.wclass = GDK_INPUT_ONLY; + attributes.override_redirect = TRUE; + attributes.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 | 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_cursor_unref (attributes.cursor); +} + +static void +ephy_label_destroy_window (EphyLabel *label) +{ + g_assert (label->select_info); + + if (label->select_info->window == NULL) + return; + + gdk_window_set_user_data (label->select_info->window, NULL); + gdk_window_destroy (label->select_info->window); + label->select_info->window = NULL; +} + +/** + * ephy_label_set_selectable: + * @label: a #EphyLabel + * @setting: %TRUE to allow selecting text in the label + * + * Selectable labels allow the user to select text from the label, for + * copy-and-paste. + * + **/ +void +ephy_label_set_selectable (EphyLabel *label, + gboolean setting) +{ + gboolean old_setting; + + g_return_if_fail (EPHY_IS_LABEL (label)); + + setting = setting != FALSE; + old_setting = label->select_info != NULL; + + if (setting) + { + if (label->select_info == NULL) + { + label->select_info = g_new0 (EphyLabelSelectionInfo, 1); + + GTK_WIDGET_SET_FLAGS (label, GTK_CAN_FOCUS); + + if (GTK_WIDGET_REALIZED (label)) + ephy_label_create_window (label); + + if (GTK_WIDGET_MAPPED (label)) + gdk_window_show (label->select_info->window); + } + } + else + { + if (label->select_info) + { + /* unselect, to give up the selection */ + ephy_label_select_region (label, 0, 0); + + if (label->select_info->window) + { + ephy_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_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)); + } +} + +/** + * ephy_label_get_selectable: + * @label: a #EphyLabel + * + * Gets the value set by ephy_label_set_selectable(). + * + * Return value: %TRUE if the user can copy text from the label + **/ +gboolean +ephy_label_get_selectable (EphyLabel *label) +{ + g_return_val_if_fail (EPHY_IS_LABEL (label), FALSE); + + return label->select_info != NULL; +} + +static void +get_text_callback (GtkClipboard *clipboard, + GtkSelectionData *selection_data, + guint info, + gpointer user_data_or_owner) +{ + EphyLabel *label; + + label = EPHY_LABEL (user_data_or_owner); + + if ((label->select_info->selection_anchor != + label->select_info->selection_end) && + label->text) + { + 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; + + gtk_selection_data_set_text (selection_data, + label->text + start, + end - start); + } +} + +static void +clear_text_callback (GtkClipboard *clipboard, + gpointer user_data_or_owner) +{ + EphyLabel *label; + + label = EPHY_LABEL (user_data_or_owner); + + if (label->select_info) + { + label->select_info->selection_anchor = label->select_info->selection_end; + + gtk_widget_queue_draw (GTK_WIDGET (label)); + } +} + +static void +ephy_label_select_region_index (EphyLabel *label, + gint anchor_index, + gint end_index) +{ + static const GtkTargetEntry targets[] = { + { "STRING", 0, 0 }, + { "TEXT", 0, 0 }, + { "COMPOUND_TEXT", 0, 0 }, + { "UTF8_STRING", 0, 0 } + }; + + g_return_if_fail (EPHY_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_widget_get_clipboard (GTK_WIDGET (label), + GDK_SELECTION_PRIMARY); + + if (anchor_index != end_index) + { + gtk_clipboard_set_with_owner (clipboard, + targets, + G_N_ELEMENTS (targets), + get_text_callback, + clear_text_callback, + G_OBJECT (label)); + } + else + { + if (gtk_clipboard_get_owner (clipboard) == G_OBJECT (label)) + gtk_clipboard_clear (clipboard); + } + + 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)); + } +} + +/** + * ephy_label_select_region: + * @label: a #EphyLabel + * @start_offset: start offset (in characters not bytes) + * @end_offset: end offset (in characters not bytes) + * + * Selects a range of characters in the label, if the label is selectable. + * See ephy_label_set_selectable(). If the label is not selectable, + * this function has no effect. If @start_offset or + * @end_offset are -1, then the end of the label will be substituted. + * + **/ +void +ephy_label_select_region (EphyLabel *label, + gint start_offset, + gint end_offset) +{ + g_return_if_fail (EPHY_IS_LABEL (label)); + + if (label->text && label->select_info) + { + if (start_offset < 0) + start_offset = g_utf8_strlen (label->text, -1); + + if (end_offset < 0) + end_offset = g_utf8_strlen (label->text, -1); + + ephy_label_select_region_index (label, + g_utf8_offset_to_pointer (label->text, start_offset) - label->text, + g_utf8_offset_to_pointer (label->text, end_offset) - label->text); + } +} + +/** + * ephy_label_get_selection_bounds: + * @label: a #EphyLabel + * @start: return location for start of selection, as a character offset + * @end: return location for end of selection, as a character offset + * + * Gets the selected range of characters in the label, returning %TRUE + * if there's a selection. + * + * Return value: %TRUE if selection is non-empty + **/ +gboolean +ephy_label_get_selection_bounds (EphyLabel *label, + gint *start, + gint *end) +{ + g_return_val_if_fail (EPHY_IS_LABEL (label), FALSE); + + if (label->select_info == NULL) + { + /* not a selectable label */ + if (start) + *start = 0; + if (end) + *end = 0; + + return FALSE; + } + else + { + gint start_index, end_index; + gint start_offset, end_offset; + gint len; + + start_index = MIN (label->select_info->selection_anchor, + label->select_info->selection_end); + end_index = MAX (label->select_info->selection_anchor, + label->select_info->selection_end); + + len = strlen (label->text); + + if (end_index > len) + end_index = len; + + if (start_index > len) + start_index = len; + + start_offset = g_utf8_strlen (label->text, start_index); + end_offset = g_utf8_strlen (label->text, end_index); + + if (start_offset > end_offset) + { + gint tmp = start_offset; + start_offset = end_offset; + end_offset = tmp; + } + + if (start) + *start = start_offset; + + if (end) + *end = end_offset; + + return start_offset != end_offset; + } +} + + +/** + * ephy_label_get_layout: + * @label: a #EphyLabel + * + * Gets the #PangoLayout used to display the label. + * The layout is useful to e.g. convert text positions to + * pixel positions, in combination with ephy_label_get_layout_offsets(). + * The returned layout is owned by the label so need not be + * freed by the caller. + * + * Return value: the #PangoLayout for this label + **/ +PangoLayout* +ephy_label_get_layout (EphyLabel *label) +{ + g_return_val_if_fail (EPHY_IS_LABEL (label), NULL); + + ephy_label_ensure_layout (label); + + return label->layout; +} + +/** + * ephy_label_get_layout_offsets: + * @label: a #EphyLabel + * @x: location to store X offset of layout, or %NULL + * @y: location to store Y offset of layout, or %NULL + * + * Obtains the coordinates where the label will draw the #PangoLayout + * representing the text in the label; useful to convert mouse events + * into coordinates inside the #PangoLayout, e.g. to take some action + * if some part of the label is clicked. Of course you will need to + * create a #GtkEventBox to receive the events, and pack the label + * inside it, since labels are a #GTK_NO_WINDOW widget. Remember + * when using the #PangoLayout functions you need to convert to + * and from pixels using PANGO_PIXELS() or #PANGO_SCALE. + * + **/ +void +ephy_label_get_layout_offsets (EphyLabel *label, + gint *x, + gint *y) +{ + g_return_if_fail (EPHY_IS_LABEL (label)); + + get_layout_location (label, x, y); +} + +/** + * ephy_label_set_use_markup: + * @label: a #EphyLabel + * @setting: %TRUE if the label's text should be parsed for markup. + * + * Sets whether the text of the label contains markup in <link + * linkend="PangoMarkupFormat">Pango's text markup + * language</link>. See ephy_label_set_markup(). + **/ +void +ephy_label_set_use_markup (EphyLabel *label, + gboolean setting) +{ + g_return_if_fail (EPHY_IS_LABEL (label)); + + ephy_label_set_use_markup_internal (label, setting); + ephy_label_recalculate (label); +} + +/** + * ephy_label_get_use_markup: + * @label: a #EphyLabel + * + * Returns whether the label's text is interpreted as marked up with + * the <link linkend="PangoMarkupFormat">Pango text markup + * language</link>. See ephy_label_set_use_markup (). + * + * Return value: %TRUE if the label's text will be parsed for markup. + **/ +gboolean +ephy_label_get_use_markup (EphyLabel *label) +{ + g_return_val_if_fail (EPHY_IS_LABEL (label), FALSE); + + return label->use_markup; +} + +/** + * ephy_label_set_use_underline: + * @label: a #EphyLabel + * @setting: %TRUE if underlines in the text indicate mnemonics + * + * If true, an underline in the text indicates the next character should be + * used for the mnemonic accelerator key. + */ +void +ephy_label_set_use_underline (EphyLabel *label, + gboolean setting) +{ + g_return_if_fail (EPHY_IS_LABEL (label)); + + ephy_label_set_use_underline_internal (label, setting); + ephy_label_recalculate (label); + if (label->use_underline) + ephy_label_setup_mnemonic (label, label->mnemonic_keyval); +} + +/** + * ephy_label_get_use_underline: + * @label: a #EphyLabel + * + * Returns whether an embedded underline in the label indicates a + * mnemonic. See ephy_label_set_use_underline (). + * + * Return value: %TRUE whether an embedded underline in the label indicates + * the mnemonic accelerator keys. + **/ +gboolean +ephy_label_get_use_underline (EphyLabel *label) +{ + g_return_val_if_fail (EPHY_IS_LABEL (label), FALSE); + + return label->use_underline; +} + +static gboolean +ephy_label_focus (GtkWidget *widget, + GtkDirectionType direction) +{ + EphyLabel *label = EPHY_LABEL (widget); + GdkEvent *current_event; + gboolean is_control_tab = FALSE; + + /* We want to be in the tab chain only if we are selectable + * and Control-[Shift]Tab is pressed + */ + if (label->select_info == NULL) + return FALSE; + + current_event = gtk_get_current_event (); + + if (current_event) + { + if (current_event->type == GDK_KEY_PRESS && + (current_event->key.keyval == GDK_Tab || + current_event->key.keyval == GDK_KP_Tab || + current_event->key.keyval == GDK_ISO_Left_Tab) && + (current_event->key.state & GDK_CONTROL_MASK) != 0) + is_control_tab = TRUE; + + gdk_event_free (current_event); + } + + if (is_control_tab) + return GTK_WIDGET_CLASS (parent_class)->focus (widget, direction); + else + 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 (EphyLabel *label, + gint index, + gint *x, + gint *y) +{ + GdkKeymap *keymap = gdk_keymap_get_for_display (gtk_widget_get_display (GTK_WIDGET (label))); + PangoDirection keymap_direction = gdk_keymap_get_direction (keymap); + PangoDirection cursor_direction = get_cursor_direction (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); + + ephy_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 == cursor_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 +ephy_label_move_logically (EphyLabel *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; + + ephy_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 +ephy_label_move_visually (EphyLabel *label, + gint start, + gint count) +{ + gint index; + + index = start; + + while (count != 0) + { + int new_index, new_trailing; + gboolean split_cursor; + gboolean strong; + + ephy_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))); + PangoDirection keymap_direction = gdk_keymap_get_direction (keymap); + + strong = keymap_direction == get_cursor_direction (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 +ephy_label_move_forward_word (EphyLabel *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; + + ephy_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 +ephy_label_move_backward_word (EphyLabel *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; + + ephy_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 +ephy_label_move_cursor (EphyLabel *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: + case GTK_MOVEMENT_HORIZONTAL_PAGES: + break; + } + } + else + { + switch (step) + { + case GTK_MOVEMENT_LOGICAL_POSITIONS: + new_pos = ephy_label_move_logically (label, new_pos, count); + break; + case GTK_MOVEMENT_VISUAL_POSITIONS: + new_pos = ephy_label_move_visually (label, new_pos, count); + break; + case GTK_MOVEMENT_WORDS: + while (count > 0) + { + new_pos = ephy_label_move_forward_word (label, new_pos); + count--; + } + while (count < 0) + { + new_pos = ephy_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: + case GTK_MOVEMENT_HORIZONTAL_PAGES: + break; + } + } + + if (extend_selection) + ephy_label_select_region_index (label, + label->select_info->selection_anchor, + new_pos); + else + ephy_label_select_region_index (label, new_pos, new_pos); +} + +static void +ephy_label_copy_clipboard (EphyLabel *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 +ephy_label_select_all (EphyLabel *label) +{ + ephy_label_select_region_index (label, 0, strlen (label->text)); +} + +/* Quick hack of a popup menu + */ +static void +activate_cb (GtkWidget *menuitem, + EphyLabel *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 (EphyLabel *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) +{ + EphyLabel *label; + label = EPHY_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) +{ + EphyLabel *label; + GtkWidget *widget; + GtkRequisition req; + GdkScreen *screen; + + label = EPHY_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 +ephy_label_do_popup (EphyLabel *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 (ephy_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 ()); +} |