1 /* GTK - The GIMP Toolkit
2 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the Free
16 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 * Modified by the GTK+ Team and others 1997-1999. See the AUTHORS
21 * file for a list of people on the GTK+ Team. See the ChangeLog
22 * files for a list of changes. These files are distributed with
23 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
29 #include "gdk/gdkkeysyms.h"
30 #include "gdk/gdki18n.h"
40 typedef struct _GtkLabelULine GtkLabelULine;
47 * We need (space,width) only before we've set (x,y), so to save
48 * memory, these pairs should really be wrapped in a union.
49 * I haven't yet figured out how to do this without making the code
69 static void gtk_label_class_init (GtkLabelClass *klass);
70 static void gtk_label_init (GtkLabel *label);
71 static void gtk_label_set_arg (GtkObject *object,
74 static void gtk_label_get_arg (GtkObject *object,
77 static void gtk_label_finalize (GObject *object);
78 static void gtk_label_size_request (GtkWidget *widget,
79 GtkRequisition *requisition);
80 static void gtk_label_style_set (GtkWidget *widget,
81 GtkStyle *previous_style);
82 static gint gtk_label_expose (GtkWidget *widget,
83 GdkEventExpose *event);
85 static GtkLabelWord* gtk_label_word_alloc (void);
86 static GtkLabelULine* gtk_label_uline_alloc (void);
87 static void gtk_label_free_words (GtkLabel *label);
88 static void gtk_label_free_ulines (GtkLabelWord *word);
89 static gint gtk_label_split_text (GtkLabel *label);
92 static GtkMiscClass *parent_class = NULL;
94 static GMemChunk *word_chunk = NULL;
95 static GMemChunk *uline_chunk = NULL;
98 gtk_label_get_type (void)
100 static GtkType label_type = 0;
104 static const GTypeInfo label_info =
106 sizeof (GtkLabelClass),
107 NULL, /* base_init */
108 NULL, /* base_finalize */
109 (GClassInitFunc) gtk_label_class_init,
110 NULL, /* class_finalize */
111 NULL, /* class_data */
113 32, /* n_preallocs */
114 (GInstanceInitFunc) gtk_label_init,
117 label_type = g_type_register_static (GTK_TYPE_MISC, "GtkLabel", &label_info);
124 gtk_label_class_init (GtkLabelClass *class)
126 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
127 GtkObjectClass *object_class;
128 GtkWidgetClass *widget_class;
130 object_class = (GtkObjectClass*) class;
131 widget_class = (GtkWidgetClass*) class;
133 parent_class = gtk_type_class (GTK_TYPE_MISC);
135 gtk_object_add_arg_type ("GtkLabel::label", GTK_TYPE_STRING, GTK_ARG_READWRITE, ARG_LABEL);
136 gtk_object_add_arg_type ("GtkLabel::pattern", GTK_TYPE_STRING, GTK_ARG_READWRITE, ARG_PATTERN);
137 gtk_object_add_arg_type ("GtkLabel::justify", GTK_TYPE_JUSTIFICATION, GTK_ARG_READWRITE, ARG_JUSTIFY);
138 gtk_object_add_arg_type ("GtkLabel::wrap", GTK_TYPE_BOOL, GTK_ARG_READWRITE, ARG_WRAP);
140 gobject_class->finalize = gtk_label_finalize;
142 object_class->set_arg = gtk_label_set_arg;
143 object_class->get_arg = gtk_label_get_arg;
145 widget_class->size_request = gtk_label_size_request;
146 widget_class->style_set = gtk_label_style_set;
147 widget_class->expose_event = gtk_label_expose;
151 gtk_label_set_arg (GtkObject *object,
157 label = GTK_LABEL (object);
162 gtk_label_set_text (label, GTK_VALUE_STRING (*arg));
165 gtk_label_set_pattern (label, GTK_VALUE_STRING (*arg));
168 gtk_label_set_justify (label, GTK_VALUE_ENUM (*arg));
171 gtk_label_set_line_wrap (label, GTK_VALUE_BOOL (*arg));
179 gtk_label_get_arg (GtkObject *object,
185 label = GTK_LABEL (object);
190 GTK_VALUE_STRING (*arg) = g_strdup (label->label);
193 GTK_VALUE_STRING (*arg) = g_strdup (label->pattern);
196 GTK_VALUE_ENUM (*arg) = label->jtype;
199 GTK_VALUE_BOOL (*arg) = label->wrap;
202 arg->type = GTK_TYPE_INVALID;
208 gtk_label_init (GtkLabel *label)
210 GTK_WIDGET_SET_FLAGS (label, GTK_NO_WINDOW);
213 label->label_wc = NULL;
214 label->pattern = NULL;
218 label->max_width = 0;
219 label->jtype = GTK_JUSTIFY_CENTER;
222 gtk_label_set_text (label, "");
226 gtk_label_new (const gchar *str)
230 label = gtk_type_new (GTK_TYPE_LABEL);
233 gtk_label_set_text (label, str);
235 return GTK_WIDGET (label);
239 gtk_label_set_text_internal (GtkLabel *label,
243 gtk_label_free_words (label);
245 g_free (label->label);
246 g_free (label->label_wc);
249 label->label_wc = str_wc;
251 gtk_widget_queue_resize (GTK_WIDGET (label));
255 gtk_label_set_text (GtkLabel *label,
262 g_return_if_fail (GTK_IS_LABEL (label));
266 if (!label->label || strcmp (label->label, str))
268 /* Convert text to wide characters */
270 str_wc = g_new (GdkWChar, len + 1);
271 wc_len = gdk_mbstowcs (str_wc, str, len + 1);
274 str_wc[wc_len] = '\0';
275 gtk_label_set_text_internal (label, g_strdup (str), str_wc);
283 gtk_label_set_pattern (GtkLabel *label,
284 const gchar *pattern)
286 g_return_if_fail (GTK_IS_LABEL (label));
288 gtk_label_free_words (label);
290 g_free (label->pattern);
291 label->pattern = g_strdup (pattern);
293 gtk_widget_queue_resize (GTK_WIDGET (label));
297 gtk_label_set_justify (GtkLabel *label,
298 GtkJustification jtype)
300 g_return_if_fail (GTK_IS_LABEL (label));
301 g_return_if_fail (jtype >= GTK_JUSTIFY_LEFT && jtype <= GTK_JUSTIFY_FILL);
303 if ((GtkJustification) label->jtype != jtype)
305 gtk_label_free_words (label);
307 label->jtype = jtype;
309 gtk_widget_queue_resize (GTK_WIDGET (label));
314 gtk_label_set_line_wrap (GtkLabel *label,
317 g_return_if_fail (GTK_IS_LABEL (label));
319 wrap = wrap != FALSE;
321 if (label->wrap != wrap)
323 gtk_label_free_words (label);
327 gtk_widget_queue_resize (GTK_WIDGET (label));
332 gtk_label_get (GtkLabel *label,
335 g_return_if_fail (label != NULL);
336 g_return_if_fail (GTK_IS_LABEL (label));
337 g_return_if_fail (str != NULL);
343 gtk_label_get_text (GtkLabel *label)
345 g_return_val_if_fail (label != NULL, NULL);
346 g_return_val_if_fail (GTK_IS_LABEL (label), NULL);
348 return g_strdup (label->label);
352 gtk_label_finalize (GObject *object)
356 g_return_if_fail (GTK_IS_LABEL (object));
358 label = GTK_LABEL (object);
360 g_free (label->label);
361 g_free (label->label_wc);
362 g_free (label->pattern);
364 gtk_label_free_words (label);
366 G_OBJECT_CLASS (parent_class)->finalize (object);
369 static GtkLabelULine*
370 gtk_label_uline_alloc (void)
372 GtkLabelULine *uline;
375 uline_chunk = g_mem_chunk_create (GtkLabelWord, 32, G_ALLOC_AND_FREE);
377 uline = g_chunk_new0 (GtkLabelULine, uline_chunk);
385 gtk_label_free_ulines (GtkLabelWord *word)
389 GtkLabelULine *uline = word->uline;
391 word->uline = uline->next;
392 g_chunk_free (uline, uline_chunk);
397 gtk_label_word_alloc (void)
402 word_chunk = g_mem_chunk_create (GtkLabelWord, 32, G_ALLOC_AND_FREE);
404 word = g_chunk_new0 (GtkLabelWord, word_chunk);
406 word->beginning = NULL;
414 gtk_label_free_words (GtkLabel *label)
418 GtkLabelWord *word = label->words;
420 label->words = word->next;
422 gtk_label_free_ulines (word);
424 g_chunk_free (word, word_chunk);
429 gtk_label_split_text (GtkLabel *label)
431 GtkLabelWord *word, **tailp;
432 gint space_width, line_width, max_line_width;
435 gtk_label_free_words (label);
436 if (label->label == NULL)
439 /* Split text at new-lines. */
440 space_width = gdk_string_width (GTK_WIDGET (label)->style->font, " ");
444 tailp = &label->words;
445 str = label->label_wc;
449 word = gtk_label_word_alloc ();
451 if (str == label->label_wc || str[-1] == '\n')
453 /* Paragraph break */
456 max_line_width = MAX (line_width, max_line_width);
459 else if (str[0] == ' ')
461 while (str[0] == ' ')
464 word->space += space_width;
469 /* Regular inter-word space */
470 word->space = space_width;
473 word->beginning = str;
477 while (*p && *p != '\n')
483 word->width = gdk_text_width_wc (GTK_WIDGET (label)->style->font, str, word->length);
489 line_width += word->space + word->width;
495 /* Add an empty word to represent an empty line
497 if (str == label->label_wc || str[-1] == '\n')
499 word = gtk_label_word_alloc ();
502 word->beginning = str;
510 return MAX (line_width, max_line_width);
513 /* this needs to handle white space better. */
515 gtk_label_split_text_wrapped (GtkLabel *label)
517 GtkLabelWord *word, **tailp;
518 gint space_width, line_width, max_line_width;
521 gtk_label_free_words (label);
522 if (label->label == NULL)
525 /* Split text at new-lines. (Or at spaces in the case of paragraphs). */
526 space_width = gdk_string_width (GTK_WIDGET (label)->style->font, " ");
530 tailp = &label->words;
531 str = label->label_wc;
534 word = gtk_label_word_alloc ();
536 if (str == label->label_wc || str[-1] == '\n')
538 /* Paragraph break */
541 max_line_width = MAX (line_width, max_line_width);
544 else if (str[0] == ' ')
548 while (str[0] == ' ')
554 if (label->jtype == GTK_JUSTIFY_FILL)
555 word->space = (space_width * 3 + 1) / 2;
557 word->space = space_width * nspaces;
561 /* Regular inter-word space */
562 word->space = space_width;
565 word->beginning = str;
568 while (*p && !gdk_iswspace (*p))
573 word->width = gdk_text_width_wc (GTK_WIDGET (label)->style->font, str, word->length);
579 line_width += word->space + word->width;
585 return MAX (line_width, max_line_width);
588 /* gtk_label_pick_width
590 * Split paragraphs, trying to make each line at least min_width,
591 * and trying even harder to make each line no longer than max_width.
593 * Returns the length of the longest resulting line.
595 * (The reason we go to all this effort to pick a paragraph width is to
596 * try to avoid the lame look of a short paragraph with a
600 gtk_label_pick_width (GtkLabel *label,
605 gint width, line_width;
607 g_return_val_if_fail (label->wrap, min_width);
611 for (word = label->words; word; word = word->next)
615 && (line_width >= min_width
616 || line_width + word->width + word->space > max_width)))
619 width = MAX (width, line_width);
622 line_width += word->space + word->width;
625 return MAX (width, line_width);
628 /* Here, we finalize the lines.
629 * This is only for non-wrap labels. Wrapped labels
630 * use gtk_label_finalize_wrap instead.
633 gtk_label_finalize_lines (GtkLabel *label,
634 GtkRequisition *requisition,
638 gint y, baseline_skip, y_max;
642 g_return_if_fail (!label->wrap);
643 ptrn = label->pattern;
646 baseline_skip = (GTK_WIDGET (label)->style->font->ascent +
647 GTK_WIDGET (label)->style->font->descent + 2);
649 for (line = label->words; line; line = line->next)
651 if (label->jtype == GTK_JUSTIFY_CENTER)
652 line->x = (max_line_width - line->width) / 2;
653 else if (label->jtype == GTK_JUSTIFY_RIGHT)
654 line->x = max_line_width - line->width;
658 line->y = y + GTK_WIDGET (label)->style->font->ascent + 1;
661 /* now we deal with the underline stuff; */
662 if (ptrn && ptrn[0] != '\0')
664 for (i = 0; i < line->length; i++)
668 else if (ptrn[i] == '_')
675 GtkLabelULine *uline;
677 for (j = i + 1; j < line->length; j++)
681 else if (ptrn[j] == ' ')
685 /* good. Now we have an underlined segment.
686 * let's measure it and record it.
688 offset = gdk_text_width_wc (GTK_WIDGET (label)->style->font,
691 gdk_text_extents_wc (GTK_WIDGET (label)->style->font,
694 &rbearing, &width, NULL,
696 y_max = MAX (descent + 2, y_max);
697 uline = gtk_label_uline_alloc ();
698 uline->x1 = offset + line->x + lbearing - 1;
699 uline->x2 = offset + line->x + rbearing;
700 uline->y = line->y + descent + 2;
701 uline->next = line->uline;
706 if (strlen (ptrn) > line->length)
707 /* the + 1 is for line breaks. */
708 ptrn += line->length + 1;
712 y += (baseline_skip + y_max);
715 label->max_width = max_line_width;
716 requisition->width = max_line_width + 2 * label->misc.xpad;
717 requisition->height = y + 2 * label->misc.ypad;
720 /* this finalizes word-wrapped words */
722 gtk_label_finalize_lines_wrap (GtkLabel *label,
723 GtkRequisition *requisition,
726 GtkLabelWord *word, *line, *next_line;
729 gint x, y, space, extra_width, add_space, baseline_skip;
731 g_return_if_fail (label->wrap);
733 ptrn = label->pattern;
735 baseline_skip = (GTK_WIDGET (label)->style->font->ascent +
736 GTK_WIDGET (label)->style->font->descent + 1);
738 for (line = label->words; line != 0; line = next_line)
741 extra_width = max_line_width - line->width;
743 for (next_line = line->next; next_line; next_line = next_line->next)
745 if (next_line->space == 0)
746 break; /* New paragraph */
747 if (next_line->space + next_line->width > extra_width)
749 extra_width -= next_line->space + next_line->width;
750 space += next_line->space;
754 line->y = y + GTK_WIDGET (label)->style->font->ascent + 1;
758 for (word = line->next; word != next_line; word = word->next)
760 if (next_line && next_line->space)
762 /* Not last line of paragraph --- fill line if needed */
763 if (label->jtype == GTK_JUSTIFY_FILL) {
764 add_space = (extra_width * word->space + space / 2) / space;
765 extra_width -= add_space;
766 space -= word->space;
770 word->x = x + word->space + add_space;
772 x = word->x + word->width;
775 y += (baseline_skip);
778 label->max_width = max_line_width;
779 widget = GTK_WIDGET (label);
780 requisition->width = max_line_width + 2 * label->misc.xpad;
781 requisition->height = y + 2 * label->misc.ypad + 1;
785 gtk_label_size_request (GtkWidget *widget,
786 GtkRequisition *requisition)
790 g_return_if_fail (GTK_IS_LABEL (widget));
791 g_return_if_fail (requisition != NULL);
793 label = GTK_LABEL (widget);
796 * There are a number of conditions which will necessitate re-filling
800 * 2. justification changed either from to to GTK_JUSTIFY_FILL.
803 * These have been detected elsewhere, and label->words will be zero,
804 * if one of the above has occured.
806 * Additionally, though, if GTK_JUSTIFY_FILL, we need to re-fill if:
808 * 4. gtk_widget_set_usize has changed the requested width.
809 * 5. gtk_misc_set_padding has changed xpad.
810 * 6. maybe others?...
812 * Too much of a pain to detect all these case, so always re-fill. I
813 * don't think it's really that slow.
818 GtkWidgetAuxInfo *aux_info;
819 gint longest_paragraph;
821 longest_paragraph = gtk_label_split_text_wrapped (label);
823 aux_info = gtk_object_get_data (GTK_OBJECT (widget), "gtk-aux-info");
824 if (aux_info && aux_info->width > 0)
826 label->max_width = MAX (aux_info->width - 2 * label->misc.xpad, 1);
827 gtk_label_split_text_wrapped (label);
831 label->max_width = gdk_string_width (GTK_WIDGET (label)->style->font,
832 "This is a good enough length for any line to have.");
833 label->max_width = MIN (label->max_width, (gdk_screen_width () + 1) / 2);
834 label->max_width = MIN (label->max_width, longest_paragraph);
835 if (longest_paragraph > 0)
837 gint nlines, perfect_width;
839 nlines = (longest_paragraph + label->max_width - 1) / label->max_width;
840 perfect_width = (longest_paragraph + nlines - 1) / nlines;
841 label->max_width = gtk_label_pick_width (label,
846 gtk_label_finalize_lines_wrap (label, requisition, label->max_width);
848 else if (!label->words)
850 label->max_width = gtk_label_split_text (label);
851 gtk_label_finalize_lines (label, requisition, label->max_width);
856 gtk_label_style_set (GtkWidget *widget,
857 GtkStyle *previous_style)
861 g_return_if_fail (GTK_IS_LABEL (widget));
863 label = GTK_LABEL (widget);
865 /* Clear the list of words so that they are recomputed on
868 if (previous_style && label->words)
869 gtk_label_free_words (label);
873 gtk_label_paint_word (GtkLabel *label,
879 GtkWidget *widget = GTK_WIDGET (label);
880 GtkLabelULine *uline;
883 tmp_str = gdk_wcstombs (word->beginning);
886 gtk_paint_string (widget->style, widget->window, widget->state,
887 area, widget, "label",
894 for (uline = word->uline; uline; uline = uline->next)
895 gtk_paint_hline (widget->style, widget->window,
898 x + uline->x1, x + uline->x2, y + uline->y);
902 gtk_label_expose (GtkWidget *widget,
903 GdkEventExpose *event)
910 g_return_val_if_fail (GTK_IS_LABEL (widget), FALSE);
911 g_return_val_if_fail (event != NULL, FALSE);
913 label = GTK_LABEL (widget);
915 if (GTK_WIDGET_VISIBLE (widget) && GTK_WIDGET_MAPPED (widget) &&
916 label->label && (*label->label != '\0'))
918 misc = GTK_MISC (widget);
923 gdk_gc_set_clip_rectangle (widget->style->white_gc, &event->area);
924 gdk_gc_set_clip_rectangle (widget->style->fg_gc[widget->state], &event->area);
926 x = floor (widget->allocation.x + (gint)misc->xpad
927 + (((gint)widget->allocation.width -
928 (gint)label->max_width - 2 * (gint)misc->xpad)
929 * misc->xalign) + 0.5);
931 y = floor (widget->allocation.y + (gint)misc->ypad
932 + (((gint)widget->allocation.height
933 - (gint)widget->requisition.height)
934 * misc->yalign) + 0.5);
936 for (word = label->words; word; word = word->next)
938 gchar save = word->beginning[word->length];
939 word->beginning[word->length] = '\0';
940 gtk_label_paint_word (label, x, y, word, &event->area);
941 word->beginning[word->length] = save;
944 gdk_gc_set_clip_mask (widget->style->white_gc, NULL);
945 gdk_gc_set_clip_mask (widget->style->fg_gc[widget->state], NULL);
952 gtk_label_parse_uline (GtkLabel *label,
955 guint accel_key = GDK_VoidSymbol;
956 GdkWChar *p, *q, *string_wc;
959 gint length, wc_length;
962 g_return_val_if_fail (GTK_IS_LABEL (label), GDK_VoidSymbol);
963 g_return_val_if_fail (string != NULL, GDK_VoidSymbol);
965 /* Convert text to wide characters */
966 length = strlen (string);
967 string_wc = g_new (GdkWChar, length + 1);
968 wc_length = gdk_mbstowcs (string_wc, string, length + 1);
972 return GDK_VoidSymbol;
975 string_wc[wc_length] = '\0';
977 pattern = g_new (gchar, length+1);
993 if (accel_key == GDK_VoidSymbol)
994 accel_key = gdk_keyval_to_lower (*p);
1015 gtk_label_set_text_internal (label, gdk_wcstombs (string_wc), string_wc);
1016 gtk_label_set_pattern (label, pattern);