/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Author : * Damon Chaplin * * Copyright 1999, Helix Code, Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA */ /* * Based on gnome-icon-text-item: an editable text block with word wrapping * for the GNOME canvas. * * Copyright (C) 1998, 1999 The Free Software Foundation * * Authors: Miguel de Icaza * Federico Mena */ /* * EIconBarTextItem - An editable canvas text item for the EIconBar. */ #include #include #include #include #include #include #include #include "e-icon-bar-text-item.h" /* Margins used to display the information */ #define MARGIN_X 2 #define MARGIN_Y 2 /* Default fontset to be used if the user specified fontset is not found */ #define DEFAULT_FONT_NAME "-adobe-helvetica-medium-r-normal--*-100-*-*-*-*-*-*," \ "-*-*-medium-r-normal--10-*-*-*-*-*-*-*,*" /* Separators for text layout */ #define DEFAULT_SEPARATORS " \t-.[]#" /* This is the string to draw when the text is clipped, e.g. '...'. */ static gchar *e_icon_bar_text_item_ellipsis; /* Aliases to minimize screen use in my laptop */ #define ITI(x) E_ICON_BAR_TEXT_ITEM (x) #define ITI_CLASS(x) E_ICON_BAR_TEXT_ITEM_CLASS (x) #define IS_ITI(x) E_IS_ICON_BAR_TEXT_ITEM (x) typedef EIconBarTextItem Iti; /* Private part of the EIconBarTextItem structure */ typedef struct { /* Font */ GdkFont *font; /* Hack: create an offscreen window and place an entry inside it */ GtkEntry *entry; GtkWidget *entry_top; /* Whether the user pressed the mouse while the item was unselected */ guint unselected_click : 1; /* Whether we need to update the position */ guint need_pos_update : 1; /* Whether we need to update the font */ guint need_font_update : 1; /* Whether we need to update the text */ guint need_text_update : 1; /* Whether we need to update because the editing/selected state changed */ guint need_state_update : 1; } ItiPrivate; typedef struct _EIconBarTextItemInfoRow EIconBarTextItemInfoRow; struct _EIconBarTextItemInfoRow { gchar *text; gint width; GdkWChar *text_wc; /* text in wide characters */ gint text_length; /* number of characters */ }; struct _EIconBarTextItemInfo { GList *rows; GdkFont *font; gint width; gint height; gint baseline_skip; }; static GnomeCanvasItemClass *parent_class; enum { ARG_0, ARG_XALIGN, ARG_JUSTIFY, ARG_MAX_LINES, ARG_SHOW_ELLIPSIS }; enum { TEXT_CHANGED, HEIGHT_CHANGED, WIDTH_CHANGED, EDITING_STARTED, EDITING_STOPPED, SELECTION_STARTED, SELECTION_STOPPED, LAST_SIGNAL }; static guint iti_signals [LAST_SIGNAL] = { 0 }; static GdkFont *default_font; static void e_icon_bar_text_item_free_info (EIconBarTextItemInfo *ti); static EIconBarTextItemInfo *e_icon_bar_text_item_layout_text (EIconBarTextItem *iti, GdkFont *font, const gchar *text, const gchar *separators, gint max_width, gboolean confine); static void e_icon_bar_text_item_paint_text (EIconBarTextItem *iti, EIconBarTextItemInfo *ti, GdkDrawable *drawable, GdkGC *gc, gint x, gint y, GtkJustification just); /* Stops the editing state of an icon text item */ static void iti_stop_editing (Iti *iti) { ItiPrivate *priv; priv = iti->priv; iti->editing = FALSE; gtk_widget_destroy (priv->entry_top); priv->entry = NULL; priv->entry_top = NULL; priv->need_state_update = TRUE; gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (iti)); gtk_signal_emit (GTK_OBJECT (iti), iti_signals[EDITING_STOPPED]); } /* Lays out the text in an icon item */ static void layout_text (Iti *iti) { ItiPrivate *priv; char *text; int old_width, old_height; int width, height; priv = iti->priv; /* Save old size */ if (iti->ti) { old_width = iti->ti->width + 2 * MARGIN_X; old_height = iti->ti->height + 2 * MARGIN_Y; e_icon_bar_text_item_free_info (iti->ti); } else { old_width = 2 * MARGIN_X; old_height = 2 * MARGIN_Y; } /* Change the text layout */ if (iti->editing) text = gtk_entry_get_text (priv->entry); else text = iti->text; iti->ti = e_icon_bar_text_item_layout_text (iti, priv->font, text, DEFAULT_SEPARATORS, iti->width - 2 * MARGIN_X, TRUE); /* Check the sizes and see if we need to emit any signals */ width = iti->ti->width + 2 * MARGIN_X; height = iti->ti->height + 2 * MARGIN_Y; if (width != old_width) gtk_signal_emit (GTK_OBJECT (iti), iti_signals[WIDTH_CHANGED]); if (height != old_height) gtk_signal_emit (GTK_OBJECT (iti), iti_signals[HEIGHT_CHANGED]); } /* Accepts the text in the off-screen entry of an icon text item */ static void iti_edition_accept (Iti *iti) { ItiPrivate *priv; gboolean accept; priv = iti->priv; accept = TRUE; gtk_signal_emit (GTK_OBJECT (iti), iti_signals [TEXT_CHANGED], &accept); if (iti->editing){ if (accept) { if (iti->is_text_allocated) g_free (iti->text); iti->text = g_strdup (gtk_entry_get_text (priv->entry)); iti->is_text_allocated = 1; } iti_stop_editing (iti); } priv->need_text_update = TRUE; gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (iti)); } /* Callback used when the off-screen entry of an icon text item is activated. * When this happens, we have to accept edition. */ static void iti_entry_activate (GtkWidget *entry, Iti *iti) { iti_edition_accept (iti); } /* Starts the editing state of an icon text item */ static void iti_start_editing (Iti *iti) { ItiPrivate *priv; priv = iti->priv; if (iti->editing) return; /* Trick: The actual edition of the entry takes place in a GtkEntry * which is placed offscreen. That way we get all of the advantages * from GtkEntry without duplicating code. Yes, this is a hack. */ priv->entry = (GtkEntry *) gtk_entry_new (); gtk_entry_set_text (priv->entry, iti->text); gtk_signal_connect (GTK_OBJECT (priv->entry), "activate", GTK_SIGNAL_FUNC (iti_entry_activate), iti); priv->entry_top = gtk_window_new (GTK_WINDOW_POPUP); gtk_container_add (GTK_CONTAINER (priv->entry_top), GTK_WIDGET (priv->entry)); gtk_widget_set_uposition (priv->entry_top, 20000, 20000); gtk_widget_show_all (priv->entry_top); gtk_editable_select_region (GTK_EDITABLE (priv->entry), 0, -1); iti->editing = TRUE; priv->need_text_update = TRUE; priv->need_state_update = TRUE; gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (iti)); gtk_signal_emit (GTK_OBJECT (iti), iti_signals[EDITING_STARTED]); } /* Destroy method handler for the icon text item */ static void iti_destroy (GtkObject *object) { Iti *iti; ItiPrivate *priv; GnomeCanvasItem *item; g_return_if_fail (object != NULL); g_return_if_fail (IS_ITI (object)); iti = ITI (object); priv = iti->priv; item = GNOME_CANVAS_ITEM (object); /* FIXME: stop selection and editing */ /* Queue redraw of bounding box */ gnome_canvas_request_redraw (item->canvas, item->x1, item->y1, item->x2, item->y2); /* Free everything */ if (iti->fontname) g_free (iti->fontname); if (iti->text && iti->is_text_allocated) g_free (iti->text); if (iti->ti) e_icon_bar_text_item_free_info (iti->ti); if (priv->font) gdk_font_unref (priv->font); if (priv->entry_top) gtk_widget_destroy (priv->entry_top); g_free (priv); if (GTK_OBJECT_CLASS (parent_class)->destroy) (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); } /* set_arg handler for the icon text item */ static void iti_set_arg (GtkObject *object, GtkArg *arg, guint arg_id) { Iti *iti; GnomeCanvasItem *item; ItiPrivate *priv; gfloat xalign; gint max_lines; gboolean show_ellipsis; GtkJustification justification; iti = ITI (object); item = GNOME_CANVAS_ITEM (object); priv = iti->priv; switch (arg_id) { case ARG_XALIGN: xalign = GTK_VALUE_FLOAT (*arg); if (iti->xalign != xalign) { iti->xalign = xalign; priv->need_pos_update = TRUE; gnome_canvas_item_request_update (item); } break; case ARG_JUSTIFY: justification = GTK_VALUE_ENUM (*arg); if (iti->justification != justification) { iti->justification = justification; priv->need_text_update = TRUE; gnome_canvas_item_request_update (item); } break; case ARG_MAX_LINES: max_lines = GTK_VALUE_INT (*arg); if (iti->max_lines != max_lines) { iti->max_lines = max_lines; priv->need_text_update = TRUE; gnome_canvas_item_request_update (item); } break; case ARG_SHOW_ELLIPSIS: show_ellipsis = GTK_VALUE_BOOL (*arg); if (iti->show_ellipsis != show_ellipsis) { iti->show_ellipsis = show_ellipsis; priv->need_text_update = TRUE; gnome_canvas_item_request_update (item); } break; default: break; } } static void iti_get_arg (GtkObject *object, GtkArg *arg, guint arg_id) { Iti *iti; ItiPrivate *priv; iti = ITI (object); priv = iti->priv; switch (arg_id) { case ARG_XALIGN: GTK_VALUE_FLOAT (*arg) = iti->xalign; break; case ARG_JUSTIFY: GTK_VALUE_ENUM (*arg) = iti->justification; break; case ARG_MAX_LINES: GTK_VALUE_INT (*arg) = iti->max_lines; break; case ARG_SHOW_ELLIPSIS: GTK_VALUE_BOOL (*arg) = iti->show_ellipsis; break; default: arg->type = GTK_TYPE_INVALID; break; } } /* Loads the default font for icon text items if necessary */ static GdkFont * get_default_font (void) { if (!default_font) { /* FIXME: this is never unref-ed */ default_font = gdk_fontset_load (DEFAULT_FONT_NAME); g_assert (default_font != NULL); } return gdk_font_ref (default_font); } /* Recomputes the bounding box of an icon text item */ static void recompute_bounding_box (Iti *iti) { GnomeCanvasItem *item; double affine[6]; ArtPoint p, q; int x1, y1, x2, y2; int width, height; item = GNOME_CANVAS_ITEM (iti); /* Compute width, height, position */ width = iti->ti->width + 2 * MARGIN_X; height = iti->ti->height + 2 * MARGIN_Y; x1 = iti->x + (iti->width - width) * iti->xalign; y1 = iti->y; x2 = x1 + width; y2 = y1 + height; /* Translate to world coordinates */ gnome_canvas_item_i2w_affine (item, affine); p.x = x1; p.y = y1; art_affine_point (&q, &p, affine); item->x1 = q.x; item->y1 = q.y; p.x = x2; p.y = y2; art_affine_point (&q, &p, affine); item->x2 = q.x; item->y2 = q.y; } /* Update method for the icon text item */ static void iti_update (GnomeCanvasItem *item, double *affine, ArtSVP *clip_path, int flags) { Iti *iti; ItiPrivate *priv; iti = ITI (item); priv = iti->priv; if (parent_class->update) (* parent_class->update) (item, affine, clip_path, flags); /* If necessary, queue a redraw of the old bounding box */ if ((flags & GNOME_CANVAS_UPDATE_VISIBILITY) || (flags & GNOME_CANVAS_UPDATE_AFFINE) || priv->need_pos_update || priv->need_font_update || priv->need_text_update) gnome_canvas_request_redraw (item->canvas, item->x1, item->y1, item->x2, item->y2); if (priv->need_text_update) layout_text (iti); /* Compute new bounds */ if (priv->need_pos_update || priv->need_font_update || priv->need_text_update) recompute_bounding_box (iti); /* Queue redraw */ gnome_canvas_request_redraw (item->canvas, item->x1, item->y1, item->x2, item->y2); priv->need_pos_update = FALSE; priv->need_font_update = FALSE; priv->need_text_update = FALSE; priv->need_state_update = FALSE; } /* Draw the icon text item's text when it is being edited */ static void iti_paint_text (Iti *iti, GdkDrawable *drawable, int x, int y) { ItiPrivate *priv; EIconBarTextItemInfoRow *row; EIconBarTextItemInfo *ti; GtkStyle *style; GdkGC *fg_gc, *bg_gc; GdkGC *gc, *bgc, *sgc, *bsgc; GList *item; int xpos, len; priv = iti->priv; style = GTK_WIDGET (GNOME_CANVAS_ITEM (iti)->canvas)->style; ti = iti->ti; len = 0; y += ti->font->ascent; /* * Pointers to all of the GCs we use */ gc = style->black_gc; bgc = style->white_gc; sgc = style->fg_gc [GTK_STATE_SELECTED]; bsgc = style->bg_gc [GTK_STATE_SELECTED]; for (item = ti->rows; item; item = item->next, len += (row ? row->text_length : 0)) { GdkWChar *text_wc; int text_length; int cursor, offset, i; int sel_start, sel_end; row = item->data; if (!row) { y += ti->baseline_skip; continue; } text_wc = row->text_wc; text_length = row->text_length; switch (iti->justification) { case GTK_JUSTIFY_LEFT: xpos = 0; break; case GTK_JUSTIFY_RIGHT: xpos = ti->width - row->width; break; case GTK_JUSTIFY_CENTER: xpos = (ti->width - row->width) / 2; break; default: /* Anyone care to implement GTK_JUSTIFY_FILL? */ g_warning ("Justification type %d not supported. Using left-justification.", (int) iti->justification); xpos = 0; } sel_start = GTK_EDITABLE (priv->entry)->selection_start_pos - len; sel_end = GTK_EDITABLE (priv->entry)->selection_end_pos - len; offset = 0; cursor = GTK_EDITABLE (priv->entry)->current_pos - len; for (i = 0; *text_wc; text_wc++, i++) { int size, px; size = gdk_text_width_wc (ti->font, text_wc, 1); if (i >= sel_start && i < sel_end) { fg_gc = sgc; bg_gc = bsgc; } else { fg_gc = gc; bg_gc = bgc; } px = x + xpos + offset; gdk_draw_rectangle (drawable, bg_gc, TRUE, px, y - ti->font->ascent, size, ti->baseline_skip); gdk_draw_text_wc (drawable, ti->font, fg_gc, px, y, text_wc, 1); if (cursor == i) gdk_draw_line (drawable, gc, px - 1, y - ti->font->ascent, px - 1, y + ti->font->descent - 1); offset += size; } if (cursor == i) { int px = x + xpos + offset; gdk_draw_line (drawable, gc, px - 1, y - ti->font->ascent, px - 1, y + ti->font->descent - 1); } y += ti->baseline_skip; } } /* Draw method handler for the icon text item */ static void iti_draw (GnomeCanvasItem *item, GdkDrawable *drawable, int x, int y, int width, int height) { Iti *iti; GtkStyle *style; int w, h; int xofs, yofs; iti = ITI (item); if (iti->ti) { w = iti->ti->width + 2 * MARGIN_X; h = iti->ti->height + 2 * MARGIN_Y; } else { w = 2 * MARGIN_X; h = 2 * MARGIN_Y; } xofs = item->x1 - x; yofs = item->y1 - y; style = GTK_WIDGET (item->canvas)->style; if (iti->selected && !iti->editing) gdk_draw_rectangle (drawable, style->bg_gc[GTK_STATE_SELECTED], TRUE, xofs, yofs, w, h); if (iti->editing) { gdk_draw_rectangle (drawable, style->white_gc, TRUE, xofs + 1, yofs + 1, w - 2, h - 2); gdk_draw_rectangle (drawable, style->black_gc, FALSE, xofs, yofs, w - 1, h - 1); iti_paint_text (iti, drawable, xofs + MARGIN_X, yofs + MARGIN_Y); } else e_icon_bar_text_item_paint_text (iti, iti->ti, drawable, style->fg_gc[(iti->selected ? GTK_STATE_SELECTED : GTK_STATE_NORMAL)], xofs + MARGIN_X, yofs + MARGIN_Y, iti->justification); } /* Point method handler for the icon text item */ static double iti_point (GnomeCanvasItem *item, double x, double y, int cx, int cy, GnomeCanvasItem **actual_item) { double dx, dy; *actual_item = item; if (cx < item->x1) dx = item->x1 - cx; else if (cx > item->x2) dx = cx - item->x2; else dx = 0.0; if (cy < item->y1) dy = item->y1 - cy; else if (cy > item->y2) dy = cy - item->y2; else dy = 0.0; return sqrt (dx * dx + dy * dy); } /* Given X, Y, a mouse position, return a valid index inside the edited text */ static int iti_idx_from_x_y (Iti *iti, int x, int y) { ItiPrivate *priv; EIconBarTextItemInfoRow *row; int lines; int line, col, i, idx; GList *l; priv = iti->priv; if (iti->ti->rows == NULL) return 0; lines = g_list_length (iti->ti->rows); line = y / iti->ti->baseline_skip; if (line < 0) line = 0; else if (lines < line + 1) line = lines - 1; /* Compute the base index for this line */ for (l = iti->ti->rows, idx = i = 0; i < line; l = l->next, i++) { row = l->data; idx += row->text_length; } row = g_list_nth (iti->ti->rows, line)->data; col = 0; if (row != NULL) { int first_char; int last_char; first_char = (iti->ti->width - row->width) / 2; last_char = first_char + row->width; if (x < first_char) { /* nothing */ } else if (x > last_char) { col = row->text_length; } else { GdkWChar *s = row->text_wc; int pos = first_char; while (pos < last_char) { pos += gdk_text_width_wc (iti->ti->font, s, 1); if (pos > x) break; col++; s++; } } } idx += col; g_assert (idx <= priv->entry->text_size); return idx; } /* Starts the selection state in the icon text item */ static void iti_start_selecting (Iti *iti, int idx, guint32 event_time) { ItiPrivate *priv; GtkEditable *e; GdkCursor *ibeam; priv = iti->priv; e = GTK_EDITABLE (priv->entry); gtk_editable_select_region (e, idx, idx); gtk_editable_set_position (e, idx); ibeam = gdk_cursor_new (GDK_XTERM); gnome_canvas_item_grab (GNOME_CANVAS_ITEM (iti), GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK, ibeam, event_time); gdk_cursor_destroy (ibeam); gtk_editable_select_region (e, idx, idx); e->current_pos = e->selection_start_pos; e->has_selection = TRUE; iti->selecting = TRUE; priv->need_state_update = TRUE; gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (iti)); gtk_signal_emit (GTK_OBJECT (iti), iti_signals[SELECTION_STARTED]); } /* Stops the selection state in the icon text item */ static void iti_stop_selecting (Iti *iti, guint32 event_time) { ItiPrivate *priv; GnomeCanvasItem *item; GtkEditable *e; priv = iti->priv; item = GNOME_CANVAS_ITEM (iti); e = GTK_EDITABLE (priv->entry); gnome_canvas_item_ungrab (item, event_time); e->has_selection = FALSE; iti->selecting = FALSE; priv->need_state_update = TRUE; gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (iti)); gtk_signal_emit (GTK_OBJECT (iti), iti_signals[SELECTION_STOPPED]); } /* Handles selection range changes on the icon text item */ static void iti_selection_motion (Iti *iti, int idx) { ItiPrivate *priv; GtkEditable *e; priv = iti->priv; e = GTK_EDITABLE (priv->entry); if (idx < e->current_pos) { e->selection_start_pos = idx; e->selection_end_pos = e->current_pos; } else { e->selection_start_pos = e->current_pos; e->selection_end_pos = idx; } priv->need_state_update = TRUE; gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (iti)); } /* Event handler for icon text items */ static gint iti_event (GnomeCanvasItem *item, GdkEvent *event) { Iti *iti; ItiPrivate *priv; int idx; double x, y; iti = ITI (item); priv = iti->priv; switch (event->type) { case GDK_KEY_PRESS: if (!iti->editing) break; if (event->key.keyval == GDK_Escape) iti_stop_editing (iti); else gtk_widget_event (GTK_WIDGET (priv->entry), event); priv->need_text_update = TRUE; gnome_canvas_item_request_update (item); return TRUE; case GDK_BUTTON_PRESS: if (!iti->editing) break; if (iti->editing && event->button.button == 1) { x = event->button.x - (item->x1 + MARGIN_X); y = event->button.y - (item->y1 + MARGIN_Y); idx = iti_idx_from_x_y (iti, x, y); iti_start_selecting (iti, idx, event->button.time); } return TRUE; case GDK_MOTION_NOTIFY: if (!iti->selecting) break; x = event->motion.x - (item->x1 + MARGIN_X); y = event->motion.y - (item->y1 + MARGIN_Y); idx = iti_idx_from_x_y (iti, x, y); iti_selection_motion (iti, idx); return TRUE; case GDK_BUTTON_RELEASE: if (iti->selecting && event->button.button == 1) iti_stop_selecting (iti, event->button.time); else break; return TRUE; default: break; } return FALSE; } /* Bounds method handler for the icon text item */ static void iti_bounds (GnomeCanvasItem *item, double *x1, double *y1, double *x2, double *y2) { Iti *iti; ItiPrivate *priv; int width, height; iti = ITI (item); priv = iti->priv; if (priv->need_text_update) { layout_text (iti); priv->need_text_update = FALSE; } if (iti->ti) { width = iti->ti->width + 2 * MARGIN_X; height = iti->ti->height + 2 * MARGIN_Y; } else { width = 2 * MARGIN_X; height = 2 * MARGIN_Y; } *x1 = iti->x + (iti->width - width) * iti->xalign; *y1 = iti->y; *x2 = *x1 + width; *y2 = *y1 + height; } /* Class initialization function for the icon text item */ static void iti_class_init (EIconBarTextItemClass *text_item_class) { GtkObjectClass *object_class; GnomeCanvasItemClass *item_class; object_class = (GtkObjectClass *) text_item_class; item_class = (GnomeCanvasItemClass *) text_item_class; parent_class = gtk_type_class (gnome_canvas_item_get_type ()); gtk_object_add_arg_type ("EIconBarTextItem::xalign", GTK_TYPE_FLOAT, GTK_ARG_READWRITE, ARG_XALIGN); gtk_object_add_arg_type ("EIconBarTextItem::justify", GTK_TYPE_JUSTIFICATION, GTK_ARG_READWRITE, ARG_JUSTIFY); gtk_object_add_arg_type ("EIconBarTextItem::max_lines", GTK_TYPE_INT, GTK_ARG_READWRITE, ARG_MAX_LINES); gtk_object_add_arg_type ("EIconBarTextItem::show_ellipsis", GTK_TYPE_BOOL, GTK_ARG_READWRITE, ARG_SHOW_ELLIPSIS); iti_signals [TEXT_CHANGED] = gtk_signal_new ( "text_changed", GTK_RUN_LAST, object_class->type, GTK_SIGNAL_OFFSET (EIconBarTextItemClass, text_changed), gtk_marshal_BOOL__NONE, GTK_TYPE_BOOL, 0); iti_signals [HEIGHT_CHANGED] = gtk_signal_new ( "height_changed", GTK_RUN_LAST, object_class->type, GTK_SIGNAL_OFFSET (EIconBarTextItemClass, height_changed), gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0); iti_signals [WIDTH_CHANGED] = gtk_signal_new ( "width_changed", GTK_RUN_LAST, object_class->type, GTK_SIGNAL_OFFSET (EIconBarTextItemClass, width_changed), gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0); iti_signals[EDITING_STARTED] = gtk_signal_new ( "editing_started", GTK_RUN_LAST, object_class->type, GTK_SIGNAL_OFFSET (EIconBarTextItemClass, editing_started), gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0); iti_signals[EDITING_STOPPED] = gtk_signal_new ( "editing_stopped", GTK_RUN_LAST, object_class->type, GTK_SIGNAL_OFFSET (EIconBarTextItemClass, editing_stopped), gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0); iti_signals[SELECTION_STARTED] = gtk_signal_new ( "selection_started", GTK_RUN_FIRST, object_class->type, GTK_SIGNAL_OFFSET (EIconBarTextItemClass, selection_started), gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0); iti_signals[SELECTION_STOPPED] = gtk_signal_new ( "selection_stopped", GTK_RUN_FIRST, object_class->type, GTK_SIGNAL_OFFSET (EIconBarTextItemClass, selection_stopped), gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0); gtk_object_class_add_signals (object_class, iti_signals, LAST_SIGNAL); object_class->destroy = iti_destroy; object_class->get_arg = iti_get_arg; object_class->set_arg = iti_set_arg; item_class->update = iti_update; item_class->draw = iti_draw; item_class->point = iti_point; item_class->bounds = iti_bounds; item_class->event = iti_event; e_icon_bar_text_item_ellipsis = _("..."); } /* Object initialization function for the icon text item */ static void iti_init (EIconBarTextItem *iti) { ItiPrivate *priv; priv = g_new0 (ItiPrivate, 1); iti->priv = priv; iti->xalign = 0.5; iti->justification = GTK_JUSTIFY_CENTER; iti->max_lines = -1; iti->show_ellipsis = TRUE; } /** * e_icon_bar_text_item_configure: * @iti: An #EIconBarTextItem. * @x: X position in which to place the item. * @y: Y position in which to place the item. * @width: Maximum width allowed for this item, to be used for word wrapping. * @fontname: Name of the fontset that should be used to display the text. * @text: Text that is going to be displayed. * @is_static: Whether @text points to a static string or not. * * This routine is used to configure an #EIconBarTextItem. * * @x and @y specify the coordinates where the item is placed in the canvas. * The @x coordinate should be the leftmost position that the item can * assume at any one time, that is, the left margin of the column in which the * icon is to be placed. The @y coordinate specifies the top of the item. * * @width is the maximum width allowed for this icon text item. The coordinates * define the upper-left corner of an item with maximum width; this may * actually be outside the bounding box of the item if the text is narrower * than the maximum width. * * If @is_static is true, it means that there is no need for the item to * allocate memory for the string (it is a guarantee that the text is allocated * by the caller and it will not be deallocated during the lifetime of this * item). This is an optimization to reduce memory usage for large icon sets. */ void e_icon_bar_text_item_configure (EIconBarTextItem *iti, int x, int y, int width, const char *fontname, const char *text, gboolean is_static) { ItiPrivate *priv; g_return_if_fail (iti != NULL); g_return_if_fail (IS_ITI (iti)); g_return_if_fail (width > 2 * MARGIN_X); g_return_if_fail (text != NULL); priv = iti->priv; iti->x = x; iti->y = y; iti->width = width; if (iti->text && iti->is_text_allocated) g_free (iti->text); iti->is_text_allocated = !is_static; /* This cast is to shut up the compiler */ if (is_static) iti->text = (char *) text; else iti->text = g_strdup (text); if (iti->fontname) g_free (iti->fontname); iti->fontname = g_strdup (fontname ? fontname : DEFAULT_FONT_NAME); if (priv->font) gdk_font_unref (priv->font); priv->font = NULL; if (fontname) priv->font = gdk_fontset_load (iti->fontname); if (!priv->font) priv->font = get_default_font (); /* Request update */ priv->need_pos_update = TRUE; priv->need_font_update = TRUE; priv->need_text_update = TRUE; gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (iti)); } /** * e_icon_bar_text_item_set_width: * @iti: An #EIconBarTextItem. * @width: Maximum width allowed for this item, to be used for word wrapping. * * This routine is used to set the maximum width of an #EIconBarTextItem. */ void e_icon_bar_text_item_set_width (EIconBarTextItem *iti, int width) { ItiPrivate *priv; g_return_if_fail (iti != NULL); g_return_if_fail (IS_ITI (iti)); g_return_if_fail (width > 2 * MARGIN_X); priv = iti->priv; if (iti->width == width) return; iti->width = width; /* Request update */ priv->need_text_update = TRUE; gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (iti)); } /** * e_icon_bar_text_item_setxy: * @iti: An #EIconBarTextItem. * @x: X position. * @y: Y position. * * Sets the coordinates at which the #EIconBarTextItem should be placed. * * See also: e_icon_bar_text_item_configure(). */ void e_icon_bar_text_item_setxy (EIconBarTextItem *iti, int x, int y) { ItiPrivate *priv; g_return_if_fail (iti != NULL); g_return_if_fail (IS_ITI (iti)); priv = iti->priv; iti->x = x; iti->y = y; priv->need_pos_update = TRUE; gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (iti)); } /** * e_icon_bar_text_item_select: * @iti: An #EIconBarTextItem. * @sel: Whether the item should be displayed as selected. * * This function is used to control whether an icon text item is displayed as * selected or not. Mouse events are ignored by the item when it is unselected; * when the user clicks on a selected icon text item, it will start the text * editing process. */ void e_icon_bar_text_item_select (EIconBarTextItem *iti, int sel) { ItiPrivate *priv; g_return_if_fail (iti != NULL); g_return_if_fail (IS_ITI (iti)); priv = iti->priv; if (!iti->selected == !sel) return; iti->selected = sel ? TRUE : FALSE; if (!iti->selected && iti->editing) iti_edition_accept (iti); priv->need_state_update = TRUE; gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (iti)); } /** * e_icon_bar_text_item_get_text: * @iti: An #EIconBarTextItem. * * Returns the current text. The client should not free this string, as it is * internal to the #EIconBarTextItem. */ char * e_icon_bar_text_item_get_text (EIconBarTextItem *iti) { ItiPrivate *priv; g_return_val_if_fail (iti != NULL, NULL); g_return_val_if_fail (IS_ITI (iti), NULL); priv = iti->priv; if (iti->editing) return gtk_entry_get_text (priv->entry); else return iti->text; } /** * e_icon_bar_text_item_set_text: * @iti: An #EIconBarTextItem. * @text: Text that is going to be displayed. * @is_static: Whether @text points to a static string or not. * * If @is_static is true, it means that there is no need for the item to * allocate memory for the string (it is a guarantee that the text is allocated * by the caller and it will not be deallocated during the lifetime of this * item). This is an optimization to reduce memory usage for large icon sets. */ void e_icon_bar_text_item_set_text (EIconBarTextItem *iti, const char *text, gboolean is_static) { ItiPrivate *priv; g_return_if_fail (iti != NULL); g_return_if_fail (IS_ITI (iti)); g_return_if_fail (text != NULL); priv = iti->priv; if (iti->text && iti->is_text_allocated) g_free (iti->text); iti->is_text_allocated = !is_static; /* This cast is to shut up the compiler */ if (is_static) iti->text = (char *) text; else iti->text = g_strdup (text); /* Request update */ priv->need_text_update = TRUE; gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (iti)); } /** * e_icon_bar_text_item_start_editing: * @iti: An #EIconBarTextItem. * * Starts the editing state of an #EIconBarTextItem. **/ void e_icon_bar_text_item_start_editing (EIconBarTextItem *iti) { g_return_if_fail (iti != NULL); g_return_if_fail (IS_ITI (iti)); if (iti->editing) return; iti->selected = TRUE; /* Ensure that we are selected */ gnome_canvas_item_grab_focus (GNOME_CANVAS_ITEM (iti)); iti_start_editing (iti); } /** * e_icon_bar_text_item_stop_editing: * @iti: An #EIconBarTextItem. * @accept: Whether to accept the current text or to discard it. * * Terminates the editing state of an icon text item. The @accept argument * controls whether the item's current text should be accepted or discarded. * If it is discarded, then the icon's original text will be restored. **/ void e_icon_bar_text_item_stop_editing (EIconBarTextItem *iti, gboolean accept) { g_return_if_fail (iti != NULL); g_return_if_fail (IS_ITI (iti)); if (!iti->editing) return; if (accept) iti_edition_accept (iti); else iti_stop_editing (iti); } /** * e_icon_bar_text_item_get_type: * * Registers the &EIconBarTextItem class if necessary, and returns the type ID * associated to it. * * Return value: the type ID of the #EIconBarTextItem class. **/ GtkType e_icon_bar_text_item_get_type (void) { static GtkType iti_type = 0; if (!iti_type) { static const GtkTypeInfo iti_info = { "EIconBarTextItem", sizeof (EIconBarTextItem), sizeof (EIconBarTextItemClass), (GtkClassInitFunc) iti_class_init, (GtkObjectInitFunc) iti_init, NULL, /* reserved_1 */ NULL, /* reserved_2 */ (GtkClassInitFunc) NULL }; iti_type = gtk_type_unique (gnome_canvas_item_get_type (), &iti_info); } return iti_type; } static void free_row (gpointer data, gpointer user_data) { EIconBarTextItemInfoRow *row; if (data) { row = data; g_free (row->text); g_free (row->text_wc); g_free (row); } } /* * e_icon_bar_text_item_free_info: * @ti: An icon text info structure. * * Frees a &EIconBarTextItemInfo structure. You should call this instead of * freeing the structure yourself. */ static void e_icon_bar_text_item_free_info (EIconBarTextItemInfo *ti) { g_list_foreach (ti->rows, free_row, NULL); g_list_free (ti->rows); g_free (ti); } /* * e_icon_bar_text_item_layout_text: * @font: Name of the font that will be used to render the text. * @text: Text to be formatted. * @separators: Separators used for word wrapping, can be NULL. * @max_width: Width in pixels to be used for word wrapping. * @confine: Whether it is mandatory to wrap at @max_width. * * Creates a new &EIconBarTextItemInfo structure by wrapping the specified * text. If non-NULL, the @separators argument defines a set of characters * to be used as word delimiters for performing word wrapping. If it is * NULL, then only spaces will be used as word delimiters. * * The @max_width argument is used to specify the width at which word * wrapping will be performed. If there is a very long word that does not * fit in a single line, the @confine argument can be used to specify * whether the word should be unconditionally split to fit or whether * the maximum width should be increased as necessary. * * Return value: A newly-created &EIconBarTextItemInfo structure. */ static EIconBarTextItemInfo * e_icon_bar_text_item_layout_text (EIconBarTextItem *iti, GdkFont *font, const gchar *text, const gchar *separators, gint max_width, gboolean confine) { EIconBarTextItemInfo *ti; EIconBarTextItemInfoRow *row; GdkWChar *row_end; GdkWChar *s, *word_start, *word_end, *old_word_end; GdkWChar *sub_text; int i, w_len, w; GdkWChar *text_wc, *text_iter, *separators_wc; int text_len_wc, separators_len_wc; gboolean restrict_lines; int lines; g_return_val_if_fail (font != NULL, NULL); g_return_val_if_fail (text != NULL, NULL); if (!separators) separators = " "; text_wc = g_new (GdkWChar, strlen (text) + 1); text_len_wc = gdk_mbstowcs (text_wc, text, strlen (text)); if (text_len_wc < 0) text_len_wc = 0; text_wc[text_len_wc] = 0; separators_wc = g_new (GdkWChar, strlen (separators) + 1); separators_len_wc = gdk_mbstowcs (separators_wc, separators, strlen (separators)); if (separators_len_wc < 0) separators_len_wc = 0; separators_wc[separators_len_wc] = 0; ti = g_new (EIconBarTextItemInfo, 1); ti->rows = NULL; ti->font = font; ti->width = 0; ti->height = 0; ti->baseline_skip = font->ascent + font->descent; word_end = NULL; if (!iti->editing && iti->max_lines != -1) restrict_lines = TRUE; else restrict_lines = FALSE; text_iter = text_wc; lines = 0; while (*text_iter) { /* If we are restricting the height, and this is the last line, and we are displaying the ellipsis, then subtract the width of the ellipsis from our max_width. */ if (restrict_lines && lines == iti->max_lines - 1 && iti->show_ellipsis) { max_width -= gdk_string_measure (font, e_icon_bar_text_item_ellipsis); } for (row_end = text_iter; *row_end != 0 && *row_end != '\n'; row_end++); /* Accumulate words from this row until they don't fit in the max_width */ s = text_iter; while (s < row_end) { word_start = s; old_word_end = word_end; for (word_end = word_start; *word_end; word_end++) { GdkWChar *p; for (p = separators_wc; *p; p++) { if (*word_end == *p) goto found; } } found: if (word_end < row_end) word_end++; if (gdk_text_width_wc (font, text_iter, word_end - text_iter) > max_width) { if (word_start == text_iter || (restrict_lines && lines == iti->max_lines - 1)) { if (confine) { /* We must force-split the word. Look for a proper * place to do it. */ w_len = word_end - text_iter; for (i = 1; i < w_len; i++) { w = gdk_text_width_wc (font, text_iter, i); if (w > max_width) { if (i == 1) /* Shit, not even a single character fits */ max_width = w; else break; } } /* Create sub-row with the chars that fit */ sub_text = g_new (GdkWChar, i); memcpy (sub_text, text_iter, (i - 1) * sizeof (GdkWChar)); sub_text[i - 1] = 0; row = g_new (EIconBarTextItemInfoRow, 1); row->text_wc = sub_text; row->text_length = i - 1; row->width = gdk_text_width_wc (font, sub_text, i - 1); row->text = gdk_wcstombs(sub_text); if (row->text == NULL) row->text = g_strdup(""); ti->rows = g_list_append (ti->rows, row); if (row->width > ti->width) ti->width = row->width; ti->height += ti->baseline_skip; /* Bump the text pointer */ text_iter += i - 1; s = text_iter; lines++; if (restrict_lines && lines >= iti->max_lines) break; continue; } else max_width = gdk_text_width_wc (font, word_start, word_end - word_start); continue; /* Retry split */ } else { word_end = old_word_end; /* Restore to region that does fit */ break; /* Stop the loop because we found something that doesn't fit */ } } s = word_end; } if (restrict_lines && lines >= iti->max_lines) break; /* Append row */ if (text_iter == row_end) { /* We are on a newline, so append an empty row */ ti->rows = g_list_append (ti->rows, NULL); ti->height += ti->baseline_skip; /* Next! */ text_iter = row_end + 1; lines++; if (restrict_lines && lines >= iti->max_lines) break; } else { /* Create subrow and append it to the list */ int sub_len; sub_len = word_end - text_iter; sub_text = g_new (GdkWChar, sub_len + 1); memcpy (sub_text, text_iter, sub_len * sizeof (GdkWChar)); sub_text[sub_len] = 0; row = g_new (EIconBarTextItemInfoRow, 1); row->text_wc = sub_text; row->text_length = sub_len; row->width = gdk_text_width_wc (font, sub_text, sub_len); row->text = gdk_wcstombs(sub_text); if (row->text == NULL) row->text = g_strdup(""); ti->rows = g_list_append (ti->rows, row); if (row->width > ti->width) ti->width = row->width; ti->height += ti->baseline_skip; /* Next! */ text_iter = word_end; lines++; if (restrict_lines && lines >= iti->max_lines) break; } } /* Check if we've had to clip the text. */ iti->is_clipped = *text_iter ? TRUE : FALSE; g_free (text_wc); g_free (separators_wc); return ti; } /* * e_icon_bar_text_item_paint_text: * @ti: An icon text info structure. * @drawable: Target drawable. * @gc: GC used to render the string. * @x: Left coordinate for text. * @y: Upper coordinate for text. * @just: Justification for text. * * Paints the formatted text in the icon text info structure onto a drawable. * This is just a sample implementation; applications can choose to use other * rendering functions. */ static void e_icon_bar_text_item_paint_text (EIconBarTextItem *iti, EIconBarTextItemInfo *ti, GdkDrawable *drawable, GdkGC *gc, gint x, gint y, GtkJustification just) { GList *item; EIconBarTextItemInfoRow *row; int xpos, line, width; gboolean show_ellipsis; g_return_if_fail (ti != NULL); g_return_if_fail (drawable != NULL); g_return_if_fail (gc != NULL); y += ti->font->ascent; for (item = ti->rows, line = 1; item; item = item->next, line++) { if (item->data) { row = item->data; width = row->width; } /* If this is the last line, and the text has been clipped, and show_ellipsis is TRUE, display '...' */ if (line == iti->max_lines && iti->is_clipped) { show_ellipsis = TRUE; width += gdk_string_measure (ti->font, e_icon_bar_text_item_ellipsis); } else { show_ellipsis = FALSE; } switch (just) { case GTK_JUSTIFY_LEFT: xpos = 0; break; case GTK_JUSTIFY_RIGHT: xpos = ti->width - width; break; case GTK_JUSTIFY_CENTER: xpos = (ti->width - width) / 2; break; default: /* Anyone care to implement GTK_JUSTIFY_FILL? */ g_warning ("Justification type %d not supported. Using left-justification.", (int) just); xpos = 0; } if (item->data) gdk_draw_text_wc (drawable, ti->font, gc, x + xpos, y, row->text_wc, row->text_length); if (show_ellipsis) gdk_draw_string (drawable, ti->font, gc, x + xpos + row->width, y, e_icon_bar_text_item_ellipsis); y += ti->baseline_skip; } }