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.
21 #include "gdk/gdkkeysyms.h"
22 #include "gdk/gdki18n.h"
31 typedef struct _GtkLabelULine GtkLabelULine;
38 * We need (space,width) only before we've set (x,y), so to save
39 * memory, these pairs should really be wrapped in a union.
40 * I haven't yet figured out how to do this without making the code
60 static void gtk_label_class_init (GtkLabelClass *klass);
61 static void gtk_label_init (GtkLabel *label);
62 static void gtk_label_set_arg (GtkObject *object,
65 static void gtk_label_get_arg (GtkObject *object,
68 static void gtk_label_finalize (GtkObject *object);
69 static void gtk_label_size_request (GtkWidget *widget,
70 GtkRequisition *requisition);
71 static gint gtk_label_expose (GtkWidget *widget,
72 GdkEventExpose *event);
74 static GtkLabelWord * gtk_label_word_alloc (void);
75 static GtkLabelULine * gtk_label_uline_alloc (void);
76 static void gtk_label_free_words (GtkLabel *label);
77 static void gtk_label_free_ulines (GtkLabelWord *word);
78 static gint gtk_label_split_text (GtkLabel * label);
79 static void gtk_label_finalize_lines (GtkLabel * label, gint line_width);
80 static void gtk_label_finalize_lines_wrap(GtkLabel * label, gint line_width);
83 static GtkMiscClass *parent_class = NULL;
85 static GMemChunk *word_chunk = 0;
86 static GtkLabelWord *free_words = 0;
87 static GMemChunk *uline_chunk = 0;
88 static GtkLabelULine *free_ulines = 0;
91 gtk_label_get_type (void)
93 static GtkType label_type = 0;
97 static const GtkTypeInfo label_info =
101 sizeof (GtkLabelClass),
102 (GtkClassInitFunc) gtk_label_class_init,
103 (GtkObjectInitFunc) gtk_label_init,
104 /* reserved_1 */ NULL,
105 /* reserved_2 */ NULL,
106 (GtkClassInitFunc) NULL,
109 label_type = gtk_type_unique (gtk_misc_get_type (), &label_info);
110 gtk_type_set_chunk_alloc (label_type, 32);
117 gtk_label_class_init (GtkLabelClass *class)
119 GtkObjectClass *object_class;
120 GtkWidgetClass *widget_class;
122 object_class = (GtkObjectClass*) class;
123 widget_class = (GtkWidgetClass*) class;
125 parent_class = gtk_type_class (gtk_misc_get_type ());
127 gtk_object_add_arg_type ("GtkLabel::label", GTK_TYPE_STRING, GTK_ARG_READWRITE, ARG_LABEL);
128 gtk_object_add_arg_type ("GtkLabel::pattern", GTK_TYPE_STRING, GTK_ARG_READWRITE, ARG_PATTERN);
129 gtk_object_add_arg_type ("GtkLabel::justify", GTK_TYPE_JUSTIFICATION, GTK_ARG_READWRITE, ARG_JUSTIFY);
131 object_class->set_arg = gtk_label_set_arg;
132 object_class->get_arg = gtk_label_get_arg;
133 object_class->finalize = gtk_label_finalize;
135 widget_class->size_request = gtk_label_size_request;
136 widget_class->expose_event = gtk_label_expose;
140 gtk_label_set_arg (GtkObject *object,
146 label = GTK_LABEL (object);
151 gtk_label_set_text (label, GTK_VALUE_STRING (*arg) ? GTK_VALUE_STRING (*arg) : "");
154 gtk_label_set_pattern (label, GTK_VALUE_STRING (*arg));
157 gtk_label_set_justify (label, GTK_VALUE_ENUM (*arg));
165 gtk_label_get_arg (GtkObject *object,
171 label = GTK_LABEL (object);
176 GTK_VALUE_STRING (*arg) = g_strdup (label->label);
179 GTK_VALUE_STRING (*arg) = g_strdup (label->pattern);
182 GTK_VALUE_ENUM (*arg) = label->jtype;
185 arg->type = GTK_TYPE_INVALID;
191 gtk_label_init (GtkLabel *label)
193 GTK_WIDGET_SET_FLAGS (label, GTK_NO_WINDOW);
196 label->label_wc = NULL;
197 label->pattern = NULL;
201 label->max_width = 0;
202 label->jtype = GTK_JUSTIFY_CENTER;
205 gtk_label_set_text (label, "");
209 gtk_label_new (const char *str)
213 g_return_val_if_fail (str != NULL, NULL);
215 label = gtk_type_new (gtk_label_get_type ());
217 gtk_label_set_text (label, str);
219 return GTK_WIDGET (label);
223 gtk_label_set_text_internal (GtkLabel *label,
228 g_free (label->label);
230 g_free (label->label_wc);
233 label->label_wc = str_wc;
235 gtk_label_free_words (label);
237 if (GTK_WIDGET_VISIBLE (label))
239 if (GTK_WIDGET_MAPPED (label))
240 gtk_widget_queue_clear (GTK_WIDGET (label));
242 gtk_widget_queue_resize (GTK_WIDGET (label));
247 gtk_label_set_text (GtkLabel *label,
254 g_return_if_fail (label != NULL);
255 g_return_if_fail (GTK_IS_LABEL (label));
256 g_return_if_fail (str != NULL);
258 if (!label->label || strcmp (label->label, str))
260 /* Convert text to wide characters */
262 str_wc = g_new (GdkWChar, len + 1);
263 wc_len = gdk_mbstowcs (str_wc, str, len + 1);
264 str_wc[wc_len] = '\0';
266 gtk_label_set_text_internal (label, g_strdup (str), str_wc);
271 gtk_label_set_pattern (GtkLabel *label,
272 const gchar *pattern)
274 g_return_if_fail (label != NULL);
275 g_return_if_fail (GTK_IS_LABEL (label));
278 g_free (label->pattern);
279 label->pattern = g_strdup (pattern);
281 if (GTK_WIDGET_VISIBLE (label))
283 if (GTK_WIDGET_MAPPED (label))
284 gtk_widget_queue_clear (GTK_WIDGET (label));
286 gtk_widget_queue_resize (GTK_WIDGET (label));
291 gtk_label_set_justify (GtkLabel *label, GtkJustification jtype)
293 g_return_if_fail (label != NULL);
294 g_return_if_fail (GTK_IS_LABEL (label));
296 if ((GtkJustification) label->jtype != jtype)
298 if ((label->jtype == GTK_JUSTIFY_FILL) ||
299 (jtype == GTK_JUSTIFY_FILL))
300 /* FIXME: think about this a little */
301 gtk_label_free_words (label);
303 label->jtype = jtype;
305 if (GTK_WIDGET_VISIBLE (label))
307 if (GTK_WIDGET_MAPPED (label))
308 gtk_widget_queue_clear (GTK_WIDGET (label));
310 gtk_widget_queue_resize (GTK_WIDGET (label));
316 gtk_label_set_line_wrap (GtkLabel *label, gboolean wrap)
318 g_return_if_fail (label != NULL);
319 g_return_if_fail (GTK_IS_LABEL (label));
321 if (label->wrap != wrap) {
322 if (GTK_WIDGET_VISIBLE (label))
324 if (GTK_WIDGET_MAPPED (label))
325 gtk_widget_queue_clear (GTK_WIDGET (label));
327 gtk_widget_queue_resize (GTK_WIDGET (label));
334 gtk_label_get (GtkLabel *label,
337 g_return_if_fail (label != NULL);
338 g_return_if_fail (GTK_IS_LABEL (label));
339 g_return_if_fail (str != NULL);
345 gtk_label_finalize (GtkObject *object)
349 g_return_if_fail (object != NULL);
350 g_return_if_fail (GTK_IS_LABEL (object));
352 label = GTK_LABEL (object);
354 g_free (label->label);
356 g_free (label->pattern);
357 gtk_label_free_words (label);
358 (* GTK_OBJECT_CLASS (parent_class)->finalize) (object);
362 gtk_label_word_alloc ()
368 word_chunk = g_mem_chunk_new ("GtkLabelWord chunk",
369 sizeof (GtkLabelWord),
370 32 * sizeof (GtkLabelWord),
377 free_words = word->next;
381 word = g_mem_chunk_alloc (word_chunk);
390 gtk_label_free_words (GtkLabel *label)
396 for (last = label->words; last->next != 0; last = last->next)
397 gtk_label_free_ulines (label->words);
398 last->next = free_words;
399 free_words = label->words;
403 static GtkLabelULine*
404 gtk_label_uline_alloc (void)
406 GtkLabelULine * uline;
410 uline_chunk = g_mem_chunk_new ("GtkLabelWord chunk",
411 sizeof (GtkLabelULine),
412 32 * sizeof (GtkLabelULine),
419 free_ulines = uline->next;
423 uline = g_mem_chunk_alloc (uline_chunk);
432 gtk_label_free_ulines (GtkLabelWord *word)
437 for (last = word->uline; last->next != 0; last = last->next)
439 last->next = free_ulines;
440 free_ulines = word->uline;
446 gtk_label_split_text (GtkLabel *label)
448 GtkLabelWord *word, **tailp;
449 gint space_width, line_width, max_line_width;
452 g_return_val_if_fail (GTK_WIDGET (label)->style->font != NULL, 0);
454 gtk_label_free_words (label);
455 if (label->label == NULL)
458 /* Split text at new-lines. */
459 space_width = gdk_string_width (GTK_WIDGET (label)->style->font, " ");
463 tailp = &label->words;
464 str = label->label_wc;
468 word = gtk_label_word_alloc ();
470 if (str == label->label_wc || str[-1] == '\n')
472 /* Paragraph break */
475 max_line_width = MAX (line_width, max_line_width);
478 else if (str[0] == ' ')
480 while (str[0] == ' ')
483 word->space += space_width;
488 /* Regular inter-word space */
489 word->space = space_width;
492 word->beginning = str;
496 while (*p && *p != '\n')
502 word->width = gdk_text_width_wc (GTK_WIDGET (label)->style->font, str, word->length);
508 line_width += word->space + word->width;
514 /* Add an empty word to represent an empty line
516 if ((str == label->label_wc) || (str[-1] == '\n'))
518 word = gtk_label_word_alloc ();
521 word->beginning = str;
529 return MAX (line_width, max_line_width);
533 gtk_label_split_text_wrapped (GtkLabel *label)
535 /* this needs to handle white space better. */
536 GtkLabelWord *word, **tailp;
537 gint space_width, line_width, max_line_width;
540 g_return_val_if_fail (GTK_WIDGET (label)->style->font != NULL, 0);
542 gtk_label_free_words (label);
543 if (label->label == NULL)
546 /* Split text at new-lines. (Or at spaces in the case of paragraphs). */
547 space_width = gdk_string_width (GTK_WIDGET (label)->style->font, " ");
551 tailp = &label->words;
552 str = label->label_wc;
555 word = gtk_label_word_alloc ();
557 if (str == label->label_wc || str[-1] == '\n')
559 /* Paragraph break */
562 max_line_width = MAX (line_width, max_line_width);
565 else if (str[0] == ' ')
569 while (str[0] == ' ')
575 if (label->jtype == GTK_JUSTIFY_FILL)
576 word->space = (space_width * 3 + 1) / 2;
578 word->space = space_width * nspaces;
582 /* Regular inter-word space */
583 word->space = space_width;
586 word->beginning = str;
589 while (*p && !gdk_iswspace (*p))
594 word->width = gdk_text_width_wc (GTK_WIDGET (label)->style->font, str, word->length);
600 line_width += word->space + word->width;
606 return MAX (line_width, max_line_width);
609 /* gtk_label_pick_width
611 * Split paragraphs, trying to make each line at least min_width,
612 * and trying even harder to make each line no longer than max_width.
614 * Returns the length of the longest resulting line.
616 * (The reason we go to all this effort to pick a paragraph width is to
617 * try to avoid the lame look of a short paragraph with a
621 gtk_label_pick_width (GtkLabel *label,
626 gint width, line_width;
628 g_return_val_if_fail (label->wrap, min_width);
632 for (word = label->words; word; word = word->next)
636 && (line_width >= min_width
637 || line_width + word->width + word->space > max_width)))
640 width = MAX (width, line_width);
643 line_width += word->space + word->width;
646 return MAX (width, line_width);
649 /* Here, we finalize the lines.
650 * This is only for non-wrap labels. Wrapped labels
651 * use gtk_label_finalize_wrap instead.
654 gtk_label_finalize_lines (GtkLabel *label,
658 gint y, baseline_skip, y_max;
662 g_return_if_fail (!label->wrap);
663 ptrn = label->pattern;
666 baseline_skip = GTK_WIDGET (label)->style->font->ascent + GTK_WIDGET (label)->style->font->descent + 2;
668 for (line = label->words; line; line = line->next)
670 if (label->jtype == GTK_JUSTIFY_CENTER)
671 line->x = (line_width - line->width) / 2;
672 else if (label->jtype == GTK_JUSTIFY_RIGHT)
673 line->x = line_width - line->width;
677 line->y = y + GTK_WIDGET (label)->style->font->ascent + 1;
680 /* now we deal with the underline stuff; */
681 if (ptrn && ptrn[0] != '\0')
683 for (i = 0; i < line->length; i++)
687 else if (ptrn[i] == '_')
694 GtkLabelULine *uline;
696 for (j = i + 1; j < line->length; j++)
700 else if (ptrn[j] == ' ')
704 /* good. Now we have an underlined segment.
705 * let's measure it and record it.
707 offset = gdk_text_width_wc (GTK_WIDGET (label)->style->font,
710 gdk_text_extents_wc (GTK_WIDGET (label)->style->font,
713 &rbearing, &width, NULL,
715 y_max = MAX (descent + 2, y_max);
716 uline = gtk_label_uline_alloc ();
717 uline->x1 = offset + line->x + lbearing - 1;
718 uline->x2 = offset + line->x + rbearing;
719 uline->y = line->y + descent + 2;
720 uline->next = line->uline;
725 if (strlen (ptrn) > line->length)
726 /* the + 1 is for line breaks. */
727 ptrn += line->length + 1;
731 y += (baseline_skip + y_max);
734 label->max_width = line_width;
735 GTK_WIDGET (label)->requisition.width = line_width + 2 * label->misc.xpad;
736 GTK_WIDGET (label)->requisition.height = y + 2 * label->misc.ypad;
739 /* this finalizes word-wrapped words */
741 gtk_label_finalize_lines_wrap (GtkLabel *label,
744 GtkLabelWord *word, *line, *next_line;
747 gint x, y, space, extra_width, add_space, baseline_skip;
749 g_return_if_fail (label->wrap);
751 ptrn = label->pattern;
753 baseline_skip = GTK_WIDGET (label)->style->font->ascent + GTK_WIDGET (label)->style->font->descent + 1;
755 for (line = label->words; line != 0; line = next_line)
758 extra_width = line_width - line->width;
760 for (next_line = line->next; next_line; next_line = next_line->next)
762 if (next_line->space == 0)
763 break; /* New paragraph */
764 if (next_line->space + next_line->width > extra_width)
766 extra_width -= next_line->space + next_line->width;
767 space += next_line->space;
771 line->y = y + GTK_WIDGET (label)->style->font->ascent + 1;
775 for (word = line->next; word != next_line; word = word->next)
777 if (next_line && next_line->space)
779 /* Not last line of paragraph --- fill line if needed */
780 if (label->jtype == GTK_JUSTIFY_FILL) {
781 add_space = (extra_width * word->space + space / 2) / space;
782 extra_width -= add_space;
783 space -= word->space;
787 word->x = x + word->space + add_space;
789 x = word->x + word->width;
792 y += (baseline_skip);
795 label->max_width = line_width;
796 widget = GTK_WIDGET (label);
797 widget->requisition.width = line_width + 2 * label->misc.xpad;
798 widget->requisition.height = y + 2 * label->misc.ypad + 1;
802 gtk_label_size_request (GtkWidget *widget,
803 GtkRequisition *requisition)
807 g_return_if_fail (widget != NULL);
808 g_return_if_fail (GTK_IS_LABEL (widget));
809 g_return_if_fail (requisition != NULL);
811 label = GTK_LABEL (widget);
814 * There are a number of conditions which will necessitate re-filling
818 * 2. justification changed either from to to GTK_JUSTIFY_FILL.
821 * These have been detected elsewhere, and label->words will be zero,
822 * if one of the above has occured.
824 * Additionally, though, if GTK_JUSTIFY_FILL, we need to re-fill if:
826 * 4. gtk_widget_set_usize has changed the requested width.
827 * 5. gtk_misc_set_padding has changed xpad.
828 * 6. maybe others?...
830 * Too much of a pain to detect all these case, so always re-fill. I
831 * don't think it's really that slow.
836 GtkWidgetAuxInfo *aux_info;
837 gint longest_paragraph;
839 longest_paragraph = gtk_label_split_text_wrapped (label);
841 aux_info = gtk_object_get_data (GTK_OBJECT (widget), "gtk-aux-info");
842 if (aux_info && aux_info->width > 0)
844 label->max_width = MAX(aux_info->width - 2 * label->misc.xpad, 1);
845 gtk_label_split_text_wrapped (label);
849 label->max_width = gdk_string_width (GTK_WIDGET (label)->style->font,
850 "This is a good enough length for any line to have.");
851 label->max_width = MIN (label->max_width, (gdk_screen_width () + 1) / 2);
852 label->max_width = MIN (label->max_width, longest_paragraph);
853 if (longest_paragraph > 0)
855 gint nlines, perfect_width;
857 nlines = (longest_paragraph + label->max_width - 1) / label->max_width;
858 perfect_width = (longest_paragraph + nlines - 1) / nlines;
859 label->max_width = gtk_label_pick_width (label,
864 gtk_label_finalize_lines_wrap (label, label->max_width);
866 else if (label->words == NULL)
868 label->max_width = gtk_label_split_text (label);
869 gtk_label_finalize_lines (label, label->max_width);
872 if (requisition != &widget->requisition)
873 *requisition = widget->requisition;
877 gtk_label_paint_word (GtkLabel *label,
883 GtkWidget *widget = GTK_WIDGET (label);
884 GtkLabelULine *uline;
887 tmp_str = gdk_wcstombs (word->beginning);
888 gtk_paint_string (widget->style, widget->window, widget->state,
889 area, widget, "label",
895 for (uline = word->uline; uline; uline = uline->next)
896 gtk_paint_hline (widget->style, widget->window,
899 x + uline->x1, x + uline->x2, y + uline->y);
905 gtk_label_expose (GtkWidget *widget,
906 GdkEventExpose *event)
913 g_return_val_if_fail (widget != NULL, FALSE);
914 g_return_val_if_fail (GTK_IS_LABEL (widget), FALSE);
915 g_return_val_if_fail (event != NULL, FALSE);
917 label = GTK_LABEL (widget);
919 if (GTK_WIDGET_VISIBLE (widget) && GTK_WIDGET_MAPPED (widget) &&
920 label->label && (*label->label != '\0'))
922 misc = GTK_MISC (widget);
927 gdk_gc_set_clip_rectangle (widget->style->white_gc, &event->area);
928 gdk_gc_set_clip_rectangle (widget->style->fg_gc[widget->state], &event->area);
930 x = floor (widget->allocation.x + (gint)misc->xpad
931 + (((gint)widget->allocation.width -
932 (gint)label->max_width - 2 * (gint)misc->xpad)
933 * misc->xalign) + 0.5);
935 y = floor (widget->allocation.y + (gint)misc->ypad
936 + (((gint)widget->allocation.height
937 - (gint)widget->requisition.height)
938 * misc->yalign) + 0.5);
940 for (word = label->words; word; word = word->next)
942 gchar save = word->beginning[word->length];
943 word->beginning[word->length] = '\0';
944 gtk_label_paint_word (label, x, y, word, &event->area);
945 word->beginning[word->length] = save;
948 gdk_gc_set_clip_mask (widget->style->white_gc, NULL);
949 gdk_gc_set_clip_mask (widget->style->fg_gc[widget->state], NULL);
956 gtk_label_parse_uline (GtkLabel *label,
959 guint accel_key = GDK_VoidSymbol;
960 GdkWChar *p, *q, *string_wc;
963 gint length, wc_length;
966 g_return_val_if_fail(string != NULL, GDK_VoidSymbol);
968 /* Convert text to wide characters */
969 length = strlen (string);
970 string_wc = g_new (GdkWChar, length + 1);
971 wc_length = gdk_mbstowcs (string_wc, string, length + 1);
972 string_wc[wc_length] = '\0';
974 pattern = g_new (gchar, length+1);
990 if (accel_key == GDK_VoidSymbol)
991 accel_key = gdk_keyval_to_lower (*p);
1012 gtk_label_set_text_internal (label, gdk_wcstombs (string_wc), string_wc);
1013 gtk_label_set_pattern (label, pattern);