gtklabel.c 23.7 KB
Newer Older
1
/* GTK - The GIMP Toolkit
Elliot Lee's avatar
Elliot Lee committed
2 3 4 5 6 7 8 9 10
 * 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 Library 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
Owen Taylor's avatar
Owen Taylor committed
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Elliot Lee's avatar
Elliot Lee committed
12 13 14
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
Owen Taylor's avatar
Owen Taylor committed
15 16
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
Elliot Lee's avatar
Elliot Lee committed
17
 */
18 19 20 21 22 23 24 25

/*
 * Modified by the GTK+ Team and others 1997-1999.  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/. 
 */

26
#include <math.h>
Elliot Lee's avatar
Elliot Lee committed
27 28
#include <string.h>
#include "gtklabel.h"
29
#include "gdk/gdkkeysyms.h"
Owen Taylor's avatar
Owen Taylor committed
30
#include "gdk/gdki18n.h"
Elliot Lee's avatar
Elliot Lee committed
31

32 33 34
enum {
  ARG_0,
  ARG_LABEL,
35
  ARG_PATTERN,
36 37
  ARG_JUSTIFY,
  ARG_WRAP
38 39
};

Owen Taylor's avatar
Owen Taylor committed
40 41 42 43 44
typedef struct _GtkLabelULine GtkLabelULine;
struct _GtkLabelWord
{
  GdkWChar *beginning;
  gint length;
45
  
Owen Taylor's avatar
Owen Taylor committed
46 47 48 49 50 51 52
  /* FIXME:
   * We need (space,width) only before we've set (x,y), so to save
   * memory, these pairs should really be wrapped in a union.
   * I haven't yet figured out how to do this without making the code
   * look ugly. 
   */
  gint space;
53
  gint width;
Owen Taylor's avatar
Owen Taylor committed
54 55 56 57 58
  gint x;
  gint y;
  GtkLabelWord *next;
  gint uline_y;
  GtkLabelULine *uline;
59 60
};

Owen Taylor's avatar
Owen Taylor committed
61 62 63 64 65 66 67
struct _GtkLabelULine
{
  gint x1;
  gint x2;
  gint y;
  GtkLabelULine *next;
};
68

Elliot Lee's avatar
Elliot Lee committed
69
static void gtk_label_class_init   (GtkLabelClass  *klass);
Tim Janik's avatar
Tim Janik committed
70
static void gtk_label_init	   (GtkLabel	   *label);
71
static void gtk_label_set_arg	   (GtkObject	   *object,
Tim Janik's avatar
Tim Janik committed
72 73
				    GtkArg	   *arg,
				    guint	    arg_id);
74
static void gtk_label_get_arg	   (GtkObject      *object,
Tim Janik's avatar
Tim Janik committed
75 76
				    GtkArg	   *arg,
				    guint	    arg_id);
77
static void gtk_label_finalize	   (GObject	   *object);
Tim Janik's avatar
Tim Janik committed
78
static void gtk_label_size_request (GtkWidget	   *widget,
Elliot Lee's avatar
Elliot Lee committed
79
				    GtkRequisition *requisition);
80 81
static void gtk_label_style_set    (GtkWidget      *widget,
				    GtkStyle       *previous_style);
Tim Janik's avatar
Tim Janik committed
82
static gint gtk_label_expose	   (GtkWidget	   *widget,
Elliot Lee's avatar
Elliot Lee committed
83
				    GdkEventExpose *event);
Owen Taylor's avatar
Owen Taylor committed
84

85 86 87 88 89
static GtkLabelWord*  gtk_label_word_alloc          (void);
static GtkLabelULine* gtk_label_uline_alloc         (void);
static void           gtk_label_free_words          (GtkLabel     *label);
static void           gtk_label_free_ulines         (GtkLabelWord *word);
static gint           gtk_label_split_text          (GtkLabel     *label);
Elliot Lee's avatar
Elliot Lee committed
90 91 92 93


static GtkMiscClass *parent_class = NULL;

94 95
static GMemChunk *word_chunk = NULL;
static GMemChunk *uline_chunk = NULL;
Elliot Lee's avatar
Elliot Lee committed
96

Tim Janik's avatar
Tim Janik committed
97
GtkType
98
gtk_label_get_type (void)
Elliot Lee's avatar
Elliot Lee committed
99
{
Tim Janik's avatar
Tim Janik committed
100 101
  static GtkType label_type = 0;
  
Elliot Lee's avatar
Elliot Lee committed
102 103
  if (!label_type)
    {
104
      static const GTypeInfo label_info =
Elliot Lee's avatar
Elliot Lee committed
105 106
      {
	sizeof (GtkLabelClass),
107 108 109 110 111 112 113 114
	NULL,           /* base_init */
	NULL,           /* base_finalize */
	(GClassInitFunc) gtk_label_class_init,
	NULL,           /* class_finalize */
	NULL,           /* class_data */
	sizeof (GtkLabel),
	32,             /* n_preallocs */
	(GInstanceInitFunc) gtk_label_init,
Elliot Lee's avatar
Elliot Lee committed
115
      };
116 117

      label_type = g_type_register_static (GTK_TYPE_MISC, "GtkLabel", &label_info);
Elliot Lee's avatar
Elliot Lee committed
118
    }
Tim Janik's avatar
Tim Janik committed
119
  
Elliot Lee's avatar
Elliot Lee committed
120 121 122
  return label_type;
}

123
static void
Elliot Lee's avatar
Elliot Lee committed
124 125
gtk_label_class_init (GtkLabelClass *class)
{
126
  GObjectClass *gobject_class = G_OBJECT_CLASS (class);
Elliot Lee's avatar
Elliot Lee committed
127 128
  GtkObjectClass *object_class;
  GtkWidgetClass *widget_class;
Tim Janik's avatar
Tim Janik committed
129
  
Elliot Lee's avatar
Elliot Lee committed
130 131
  object_class = (GtkObjectClass*) class;
  widget_class = (GtkWidgetClass*) class;
Tim Janik's avatar
Tim Janik committed
132
  
133
  parent_class = gtk_type_class (GTK_TYPE_MISC);
Tim Janik's avatar
Tim Janik committed
134
  
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);
139
  
140 141
  gobject_class->finalize = gtk_label_finalize;

142 143
  object_class->set_arg = gtk_label_set_arg;
  object_class->get_arg = gtk_label_get_arg;
Tim Janik's avatar
Tim Janik committed
144
  
Elliot Lee's avatar
Elliot Lee committed
145
  widget_class->size_request = gtk_label_size_request;
146
  widget_class->style_set = gtk_label_style_set;
Elliot Lee's avatar
Elliot Lee committed
147 148 149
  widget_class->expose_event = gtk_label_expose;
}

150
static void
151
gtk_label_set_arg (GtkObject	  *object,
Tim Janik's avatar
Tim Janik committed
152 153
		   GtkArg	  *arg,
		   guint	   arg_id)
154
{
155
  GtkLabel *label;
156
  
157
  label = GTK_LABEL (object);
158
  
159 160 161
  switch (arg_id)
    {
    case ARG_LABEL:
162
      gtk_label_set_text (label, GTK_VALUE_STRING (*arg));
163
      break;
164 165 166
    case ARG_PATTERN:
      gtk_label_set_pattern (label, GTK_VALUE_STRING (*arg));
      break;
167 168 169
    case ARG_JUSTIFY:
      gtk_label_set_justify (label, GTK_VALUE_ENUM (*arg));
      break;
170 171 172
    case ARG_WRAP:
      gtk_label_set_line_wrap (label, GTK_VALUE_BOOL (*arg));
      break;	  
Tim Janik's avatar
Tim Janik committed
173 174
    default:
      break;
175 176 177
    }
}

Tim Janik's avatar
Tim Janik committed
178
static void
179
gtk_label_get_arg (GtkObject	  *object,
Tim Janik's avatar
Tim Janik committed
180 181
		   GtkArg	  *arg,
		   guint	   arg_id)
182
{
183
  GtkLabel *label;
184
  
185
  label = GTK_LABEL (object);
186
  
187 188 189 190 191
  switch (arg_id)
    {
    case ARG_LABEL:
      GTK_VALUE_STRING (*arg) = g_strdup (label->label);
      break;
192 193 194
    case ARG_PATTERN:
      GTK_VALUE_STRING (*arg) = g_strdup (label->pattern);
      break;
195 196 197
    case ARG_JUSTIFY:
      GTK_VALUE_ENUM (*arg) = label->jtype;
      break;
198 199 200
    case ARG_WRAP:
      GTK_VALUE_BOOL (*arg) = label->wrap;
      break;
201 202 203 204 205 206
    default:
      arg->type = GTK_TYPE_INVALID;
      break;
    }
}

207
static void
Elliot Lee's avatar
Elliot Lee committed
208 209 210
gtk_label_init (GtkLabel *label)
{
  GTK_WIDGET_SET_FLAGS (label, GTK_NO_WINDOW);
Tim Janik's avatar
Tim Janik committed
211
  
Elliot Lee's avatar
Elliot Lee committed
212
  label->label = NULL;
213 214 215
  label->label_wc = NULL;
  label->pattern = NULL;

Owen Taylor's avatar
Owen Taylor committed
216
  label->words = NULL;
217

Tim Janik's avatar
Tim Janik committed
218
  label->max_width = 0;
Elliot Lee's avatar
Elliot Lee committed
219
  label->jtype = GTK_JUSTIFY_CENTER;
Owen Taylor's avatar
Owen Taylor committed
220
  label->wrap = FALSE;
221
  
Owen Taylor's avatar
Owen Taylor committed
222
  gtk_label_set_text (label, "");
Elliot Lee's avatar
Elliot Lee committed
223 224 225
}

GtkWidget*
226
gtk_label_new (const gchar *str)
Elliot Lee's avatar
Elliot Lee committed
227 228
{
  GtkLabel *label;
229
  
230 231 232 233
  label = gtk_type_new (GTK_TYPE_LABEL);

  if (str && *str)
    gtk_label_set_text (label, str);
234
  
Elliot Lee's avatar
Elliot Lee committed
235 236 237
  return GTK_WIDGET (label);
}

238
static inline void
239
gtk_label_set_text_internal (GtkLabel *label,
240
			     gchar    *str,
241
			     GdkWChar *str_wc)
Elliot Lee's avatar
Elliot Lee committed
242
{
243 244 245 246
  gtk_label_free_words (label);
      
  g_free (label->label);
  g_free (label->label_wc);
247
  
248 249
  label->label = str;
  label->label_wc = str_wc;
250
  
251
  gtk_widget_queue_resize (GTK_WIDGET (label));
252 253
}

254
void
255 256
gtk_label_set_text (GtkLabel    *label,
		    const gchar *str)
257 258 259 260 261 262
{
  GdkWChar *str_wc;
  gint len;
  gint wc_len;
  
  g_return_if_fail (GTK_IS_LABEL (label));
263 264
  if (!str)
    str = "";
265 266 267 268 269 270 271

  if (!label->label || strcmp (label->label, str))
    {
      /* Convert text to wide characters */
      len = strlen (str);
      str_wc = g_new (GdkWChar, len + 1);
      wc_len = gdk_mbstowcs (str_wc, str, len + 1);
272 273 274 275 276 277 278
      if (wc_len >= 0)
	{
	  str_wc[wc_len] = '\0';
	  gtk_label_set_text_internal (label, g_strdup (str), str_wc);
	}
      else
	g_free (str_wc);
279
    }
280 281
}

282 283 284 285 286 287
void
gtk_label_set_pattern (GtkLabel	   *label,
		       const gchar *pattern)
{
  g_return_if_fail (GTK_IS_LABEL (label));
  
288
  gtk_label_free_words (label);
Tim Janik's avatar
Tim Janik committed
289
  
290 291 292 293
  g_free (label->pattern);
  label->pattern = g_strdup (pattern);

  gtk_widget_queue_resize (GTK_WIDGET (label));
Elliot Lee's avatar
Elliot Lee committed
294 295 296
}

void
297 298
gtk_label_set_justify (GtkLabel        *label,
		       GtkJustification jtype)
Elliot Lee's avatar
Elliot Lee committed
299 300
{
  g_return_if_fail (GTK_IS_LABEL (label));
301
  g_return_if_fail (jtype >= GTK_JUSTIFY_LEFT && jtype <= GTK_JUSTIFY_FILL);
302
  
Elliot Lee's avatar
Elliot Lee committed
303 304
  if ((GtkJustification) label->jtype != jtype)
    {
305
      gtk_label_free_words (label);
306
      
Elliot Lee's avatar
Elliot Lee committed
307
      label->jtype = jtype;
308 309

      gtk_widget_queue_resize (GTK_WIDGET (label));
Elliot Lee's avatar
Elliot Lee committed
310 311 312 313
    }
}

void
314 315
gtk_label_set_line_wrap (GtkLabel *label,
			 gboolean  wrap)
Owen Taylor's avatar
Owen Taylor committed
316 317
{
  g_return_if_fail (GTK_IS_LABEL (label));
318
  
319 320 321 322 323 324 325 326 327 328
  wrap = wrap != FALSE;
  
  if (label->wrap != wrap)
    {
      gtk_label_free_words (label);

      label->wrap = wrap;

      gtk_widget_queue_resize (GTK_WIDGET (label));
    }
Owen Taylor's avatar
Owen Taylor committed
329 330 331
}

void
332 333
gtk_label_get (GtkLabel *label,
	       gchar   **str)
Elliot Lee's avatar
Elliot Lee committed
334 335 336 337
{
  g_return_if_fail (label != NULL);
  g_return_if_fail (GTK_IS_LABEL (label));
  g_return_if_fail (str != NULL);
338
  
Elliot Lee's avatar
Elliot Lee committed
339 340 341
  *str = label->label;
}

342 343 344 345 346 347 348 349 350
gchar *
gtk_label_get_text (GtkLabel *label)
{
  g_return_val_if_fail (label != NULL, NULL);
  g_return_val_if_fail (GTK_IS_LABEL (label), NULL);

  return g_strdup (label->label);
}

Elliot Lee's avatar
Elliot Lee committed
351
static void
352
gtk_label_finalize (GObject *object)
Elliot Lee's avatar
Elliot Lee committed
353 354
{
  GtkLabel *label;
355
  
Elliot Lee's avatar
Elliot Lee committed
356
  g_return_if_fail (GTK_IS_LABEL (object));
357
  
Elliot Lee's avatar
Elliot Lee committed
358
  label = GTK_LABEL (object);
359
  
Elliot Lee's avatar
Elliot Lee committed
360
  g_free (label->label);
Owen Taylor's avatar
Owen Taylor committed
361
  g_free (label->label_wc);
362 363
  g_free (label->pattern);

Owen Taylor's avatar
Owen Taylor committed
364
  gtk_label_free_words (label);
365

366
  G_OBJECT_CLASS (parent_class)->finalize (object);
Elliot Lee's avatar
Elliot Lee committed
367 368
}

369 370
static GtkLabelULine*
gtk_label_uline_alloc (void)
371
{
372
  GtkLabelULine *uline;
373
  
374 375 376 377
  if (!uline_chunk)
    uline_chunk = g_mem_chunk_create (GtkLabelWord, 32, G_ALLOC_AND_FREE);

  uline = g_chunk_new0 (GtkLabelULine, uline_chunk);
Owen Taylor's avatar
Owen Taylor committed
378
  
379
  uline->next = NULL;
380
  
381
  return uline;
Owen Taylor's avatar
Owen Taylor committed
382
}
383

Owen Taylor's avatar
Owen Taylor committed
384
static void
385
gtk_label_free_ulines (GtkLabelWord *word)
Owen Taylor's avatar
Owen Taylor committed
386
{
387
  while (word->uline)
Owen Taylor's avatar
Owen Taylor committed
388
    {
389 390 391 392
      GtkLabelULine *uline = word->uline;

      word->uline = uline->next;
      g_chunk_free (uline, uline_chunk);
Owen Taylor's avatar
Owen Taylor committed
393 394
    }
}
395 396 397

static GtkLabelWord*
gtk_label_word_alloc (void)
Owen Taylor's avatar
Owen Taylor committed
398
{
399
  GtkLabelWord * word;
400
  
401 402
  if (!word_chunk)
    word_chunk = g_mem_chunk_create (GtkLabelWord, 32, G_ALLOC_AND_FREE);
403
  
404
  word = g_chunk_new0 (GtkLabelWord, word_chunk);
405
  
406 407 408 409 410
  word->beginning = NULL;
  word->next = NULL;
  word->uline = NULL;

  return word;
Owen Taylor's avatar
Owen Taylor committed
411 412 413
}

static void
414
gtk_label_free_words (GtkLabel *label)
Owen Taylor's avatar
Owen Taylor committed
415
{
416
  while (label->words)
Owen Taylor's avatar
Owen Taylor committed
417
    {
418 419 420 421 422 423 424
      GtkLabelWord *word = label->words;

      label->words = word->next;

      gtk_label_free_ulines (word);

      g_chunk_free (word, word_chunk);
Owen Taylor's avatar
Owen Taylor committed
425 426
    }
}
427

Owen Taylor's avatar
Owen Taylor committed
428
static gint
429
gtk_label_split_text (GtkLabel *label)
Owen Taylor's avatar
Owen Taylor committed
430 431 432 433 434 435
{
  GtkLabelWord *word, **tailp;
  gint space_width, line_width, max_line_width;
  GdkWChar *str, *p;
  
  gtk_label_free_words (label);
436 437
  if (label->label == NULL)
    return 0;
Owen Taylor's avatar
Owen Taylor committed
438
  
439
  /* Split text at new-lines. */
Owen Taylor's avatar
Owen Taylor committed
440
  space_width = gdk_string_width (GTK_WIDGET (label)->style->font, " ");
441
  
Owen Taylor's avatar
Owen Taylor committed
442 443 444 445
  line_width = 0;
  max_line_width = 0;
  tailp = &label->words;
  str = label->label_wc;
446
  
Owen Taylor's avatar
Owen Taylor committed
447
  while (*str)
448
    {
Owen Taylor's avatar
Owen Taylor committed
449
      word = gtk_label_word_alloc ();
450 451
      
      if (str == label->label_wc || str[-1] == '\n')
Owen Taylor's avatar
Owen Taylor committed
452 453 454
	{
	  /* Paragraph break */
	  word->space = 0;
455
	  
Owen Taylor's avatar
Owen Taylor committed
456 457 458 459 460 461 462 463 464 465 466
	  max_line_width = MAX (line_width, max_line_width);
	  line_width = 0;
	}
      else if (str[0] == ' ')
	{
	  while (str[0] == ' ')
	    {
	      str++;
	      word->space += space_width;
	    }
	}
467
      else
Owen Taylor's avatar
Owen Taylor committed
468 469 470 471
	{
	  /* Regular inter-word space */
	  word->space = space_width;
	}
472
      
Owen Taylor's avatar
Owen Taylor committed
473 474 475 476
      word->beginning = str;
      
      word->length = 0;
      p = word->beginning;
477
      while (*p && *p != '\n')
478
	{
Owen Taylor's avatar
Owen Taylor committed
479 480 481
	  word->length++;
	  p++;
	}
482
      
Owen Taylor's avatar
Owen Taylor committed
483
      word->width = gdk_text_width_wc (GTK_WIDGET (label)->style->font, str, word->length);
484
      
Owen Taylor's avatar
Owen Taylor committed
485 486
      str += word->length;
      if (*str)
487
	str++;
Owen Taylor's avatar
Owen Taylor committed
488 489
      
      line_width += word->space + word->width;
490
      
Owen Taylor's avatar
Owen Taylor committed
491 492 493
      *tailp = word;
      tailp = &word->next;
    }
494
  
495 496
  /* Add an empty word to represent an empty line
   */
497
  if (str == label->label_wc || str[-1] == '\n')
498 499
    {
      word = gtk_label_word_alloc ();
500
      
501 502 503 504 505 506 507 508
      word->space = 0;
      word->beginning = str;
      word->length = 0;
      word->width = 0;
      
      *tailp = word;
      tailp = &word->next;
    }
509
  
Owen Taylor's avatar
Owen Taylor committed
510 511 512
  return MAX (line_width, max_line_width);
}

513
/* this needs to handle white space better. */
Owen Taylor's avatar
Owen Taylor committed
514
static gint
515
gtk_label_split_text_wrapped (GtkLabel *label)
Owen Taylor's avatar
Owen Taylor committed
516 517 518 519 520 521
{
  GtkLabelWord *word, **tailp;
  gint space_width, line_width, max_line_width;
  GdkWChar *str, *p;
  
  gtk_label_free_words (label);
522 523
  if (label->label == NULL)
    return 0;
Owen Taylor's avatar
Owen Taylor committed
524
  
525
  /* Split text at new-lines.  (Or at spaces in the case of paragraphs). */
Owen Taylor's avatar
Owen Taylor committed
526
  space_width = gdk_string_width (GTK_WIDGET (label)->style->font, " ");
527
  
Owen Taylor's avatar
Owen Taylor committed
528 529 530 531 532 533 534
  line_width = 0;
  max_line_width = 0;
  tailp = &label->words;
  str = label->label_wc;
  while (*str)
    {
      word = gtk_label_word_alloc ();
535
      
Owen Taylor's avatar
Owen Taylor committed
536 537 538 539
      if (str == label->label_wc || str[-1] == '\n')
	{
	  /* Paragraph break */
	  word->space = 0;
540
	  
Owen Taylor's avatar
Owen Taylor committed
541 542 543 544 545 546
	  max_line_width = MAX (line_width, max_line_width);
	  line_width = 0;
	}
      else if (str[0] == ' ')
	{
	  gint nspaces = 0;
547
	  
Owen Taylor's avatar
Owen Taylor committed
548
	  while (str[0] == ' ')
549
	    {
Owen Taylor's avatar
Owen Taylor committed
550 551
	      nspaces++;
	      str++;
552
	    }
553
	  
Owen Taylor's avatar
Owen Taylor committed
554 555 556
	  if (label->jtype == GTK_JUSTIFY_FILL)
	    word->space = (space_width * 3 + 1) / 2;
	  else
557
	    word->space = space_width * nspaces;
Owen Taylor's avatar
Owen Taylor committed
558 559 560 561 562 563
	}
      else
	{
	  /* Regular inter-word space */
	  word->space = space_width;
	}
564
      
Owen Taylor's avatar
Owen Taylor committed
565 566 567
      word->beginning = str;
      word->length = 0;
      p = word->beginning;
568
      while (*p && !gdk_iswspace (*p))
Owen Taylor's avatar
Owen Taylor committed
569 570 571 572 573
	{
	  word->length++;
	  p++;
	}
      word->width = gdk_text_width_wc (GTK_WIDGET (label)->style->font, str, word->length);
574
      
Owen Taylor's avatar
Owen Taylor committed
575 576
      str += word->length;
      if (*str)
577
	str++;
Owen Taylor's avatar
Owen Taylor committed
578 579
      
      line_width += word->space + word->width;
580
      
Owen Taylor's avatar
Owen Taylor committed
581 582 583
      *tailp = word;
      tailp = &word->next;
    }
584
  
Owen Taylor's avatar
Owen Taylor committed
585 586 587
  return MAX (line_width, max_line_width);
}

588
/* gtk_label_pick_width
Owen Taylor's avatar
Owen Taylor committed
589 590 591 592 593 594 595 596 597 598 599
 *
 * Split paragraphs, trying to make each line at least min_width,
 * and trying even harder to make each line no longer than max_width.
 *
 * Returns the length of the longest resulting line.
 *
 * (The reason we go to all this effort to pick a paragraph width is to
 * try to avoid the lame look of a short paragraph with a
 * short final line.)
 */
static gint
600 601 602
gtk_label_pick_width (GtkLabel *label,
		      gint      min_width,
		      gint      max_width)
Owen Taylor's avatar
Owen Taylor committed
603 604 605 606 607
{
  GtkLabelWord *word;
  gint width, line_width;
  
  g_return_val_if_fail (label->wrap, min_width);
608
  
Owen Taylor's avatar
Owen Taylor committed
609 610 611 612 613 614 615 616 617 618 619 620 621 622 623
  line_width = 0;
  width = 0;
  for (word = label->words; word; word = word->next)
    {
      if (word->space == 0
	  || (line_width
	      && (line_width >= min_width
		  || line_width + word->width + word->space > max_width)))
	{
	  /* New line */
	  width = MAX (width, line_width);
	  line_width = 0;
	}
      line_width += word->space + word->width;
    }
624
  
Owen Taylor's avatar
Owen Taylor committed
625 626 627
  return MAX (width, line_width);
}

628
/* Here, we finalize the lines.
Owen Taylor's avatar
Owen Taylor committed
629 630 631 632
 * This is only for non-wrap labels.  Wrapped labels
 * use gtk_label_finalize_wrap instead.
 */
static void
633 634 635
gtk_label_finalize_lines (GtkLabel       *label,
			  GtkRequisition *requisition,
			  gint            max_line_width)
Owen Taylor's avatar
Owen Taylor committed
636
{
637
  GtkLabelWord *line;
Owen Taylor's avatar
Owen Taylor committed
638 639 640 641 642 643
  gint y, baseline_skip, y_max;
  gint i, j;
  gchar *ptrn;
  
  g_return_if_fail (!label->wrap);
  ptrn = label->pattern;
644
  
Owen Taylor's avatar
Owen Taylor committed
645
  y = 0;
646 647
  baseline_skip = (GTK_WIDGET (label)->style->font->ascent +
		   GTK_WIDGET (label)->style->font->descent + 2);
Owen Taylor's avatar
Owen Taylor committed
648 649 650 651
  
  for (line = label->words; line; line = line->next)
    {
      if (label->jtype == GTK_JUSTIFY_CENTER)
652
	line->x = (max_line_width - line->width) / 2;
Owen Taylor's avatar
Owen Taylor committed
653
      else if (label->jtype == GTK_JUSTIFY_RIGHT)
654
	line->x = max_line_width - line->width;
Owen Taylor's avatar
Owen Taylor committed
655
      else
656 657
	line->x = 0;
      
Owen Taylor's avatar
Owen Taylor committed
658 659
      line->y = y + GTK_WIDGET (label)->style->font->ascent + 1;
      y_max = 0;
660
      
Owen Taylor's avatar
Owen Taylor committed
661
      /* now we deal with the underline stuff; */
662
      if (ptrn && ptrn[0] != '\0')
Owen Taylor's avatar
Owen Taylor committed
663
	{
664
	  for (i = 0; i < line->length; i++)
665
	    {
Owen Taylor's avatar
Owen Taylor committed
666 667 668 669 670 671 672 673 674 675
	      if (ptrn[i] == '\0')
		break;
	      else if (ptrn[i] == '_')
		{
		  gint descent;
		  gint rbearing;
		  gint lbearing;
		  gint width;
		  gint offset;
		  GtkLabelULine *uline;
676 677
		  
		  for (j = i + 1; j < line->length; j++)
Owen Taylor's avatar
Owen Taylor committed
678 679 680 681 682 683
		    {
		      if (ptrn[j] == '\0')
			break;
		      else if (ptrn[j] == ' ')
			break;
		    }
684
		  
685
		  /* good.  Now we have an underlined segment.
Owen Taylor's avatar
Owen Taylor committed
686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704
		   * let's measure it and record it.
		   */
		  offset = gdk_text_width_wc (GTK_WIDGET (label)->style->font,
					      line->beginning,
					      i);
		  gdk_text_extents_wc (GTK_WIDGET (label)->style->font,
				       line->beginning+i,
				       j-i, &lbearing,
				       &rbearing, &width, NULL,
				       &descent);
		  y_max = MAX (descent + 2, y_max);
		  uline = gtk_label_uline_alloc ();
		  uline->x1 = offset + line->x + lbearing - 1;
		  uline->x2 = offset + line->x + rbearing;
		  uline->y = line->y + descent + 2;
		  uline->next = line->uline;
		  line->uline = uline;
		  i = j - 1;
		}
705
	    }
Owen Taylor's avatar
Owen Taylor committed
706 707 708 709 710 711 712 713 714
	  if (strlen (ptrn) > line->length)
	    /* the + 1 is for line breaks. */
	    ptrn += line->length + 1;
	  else
	    ptrn = NULL;
	}
      y += (baseline_skip + y_max);
    }
  
715 716 717
  label->max_width = max_line_width;
  requisition->width = max_line_width + 2 * label->misc.xpad;
  requisition->height = y + 2 * label->misc.ypad;
Owen Taylor's avatar
Owen Taylor committed
718 719 720 721
}

/* this finalizes word-wrapped words */
static void
722 723 724
gtk_label_finalize_lines_wrap (GtkLabel       *label,
			       GtkRequisition *requisition,
			       gint            max_line_width)
Owen Taylor's avatar
Owen Taylor committed
725 726 727 728 729 730 731
{
  GtkLabelWord *word, *line, *next_line;
  GtkWidget *widget;
  gchar *ptrn;
  gint x, y, space, extra_width, add_space, baseline_skip;
  
  g_return_if_fail (label->wrap);
732
  
Owen Taylor's avatar
Owen Taylor committed
733 734
  ptrn = label->pattern;
  y = 0;
735 736
  baseline_skip = (GTK_WIDGET (label)->style->font->ascent +
		   GTK_WIDGET (label)->style->font->descent + 1);
Owen Taylor's avatar
Owen Taylor committed
737 738 739 740
  
  for (line = label->words; line != 0; line = next_line)
    {
      space = 0;
741
      extra_width = max_line_width - line->width;
Owen Taylor's avatar
Owen Taylor committed
742 743 744 745
      
      for (next_line = line->next; next_line; next_line = next_line->next)
	{
	  if (next_line->space == 0)
746
	    break;		/* New paragraph */
Owen Taylor's avatar
Owen Taylor committed
747
	  if (next_line->space + next_line->width > extra_width)
748
	    break;
Owen Taylor's avatar
Owen Taylor committed
749 750 751
	  extra_width -= next_line->space + next_line->width;
	  space += next_line->space;
	}
752
      
Owen Taylor's avatar
Owen Taylor committed
753 754 755 756
      line->x = 0;
      line->y = y + GTK_WIDGET (label)->style->font->ascent + 1;
      x = line->width;
      add_space = 0;
757
      
Owen Taylor's avatar
Owen Taylor committed
758 759 760
      for (word = line->next; word != next_line; word = word->next)
	{
	  if (next_line && next_line->space)
761
	    {
Owen Taylor's avatar
Owen Taylor committed
762 763 764 765 766 767
	      /* Not last line of paragraph --- fill line if needed */
	      if (label->jtype == GTK_JUSTIFY_FILL) {
		add_space = (extra_width * word->space + space / 2) / space;
		extra_width -= add_space;
		space -= word->space;
	      }
768
	    }
Owen Taylor's avatar
Owen Taylor committed
769 770 771 772
	  
	  word->x = x + word->space + add_space;
	  word->y = line->y;
	  x = word->x + word->width;
773
	}
774
      
Owen Taylor's avatar
Owen Taylor committed
775
      y += (baseline_skip);
776
    }
777
  
778
  label->max_width = max_line_width;
Owen Taylor's avatar
Owen Taylor committed
779
  widget = GTK_WIDGET (label);
780 781
  requisition->width = max_line_width + 2 * label->misc.xpad;
  requisition->height = y + 2 * label->misc.ypad + 1;
782
}
783

Elliot Lee's avatar
Elliot Lee committed
784 785 786 787 788
static void
gtk_label_size_request (GtkWidget      *widget,
			GtkRequisition *requisition)
{
  GtkLabel *label;
Tim Janik's avatar
Tim Janik committed
789
  
Elliot Lee's avatar
Elliot Lee committed
790 791
  g_return_if_fail (GTK_IS_LABEL (widget));
  g_return_if_fail (requisition != NULL);
792
  
Elliot Lee's avatar
Elliot Lee committed
793
  label = GTK_LABEL (widget);
794

Owen Taylor's avatar
Owen Taylor committed
795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814
  /*
   * There are a number of conditions which will necessitate re-filling
   * our text:
   *
   *     1. text changed.
   *     2. justification changed either from to to GTK_JUSTIFY_FILL.
   *     3. font changed.
   *
   * These have been detected elsewhere, and label->words will be zero,
   * if one of the above has occured.
   *
   * Additionally, though, if GTK_JUSTIFY_FILL, we need to re-fill if:
   *
   *     4. gtk_widget_set_usize has changed the requested width.
   *     5. gtk_misc_set_padding has changed xpad.
   *     6.  maybe others?...
   *
   * Too much of a pain to detect all these case, so always re-fill.  I
   * don't think it's really that slow.
   */
815
  
Owen Taylor's avatar
Owen Taylor committed
816
  if (label->wrap)
Elliot Lee's avatar
Elliot Lee committed
817
    {
Owen Taylor's avatar
Owen Taylor committed
818 819 820 821 822 823 824 825
      GtkWidgetAuxInfo *aux_info;
      gint longest_paragraph;
      
      longest_paragraph = gtk_label_split_text_wrapped (label);
      
      aux_info = gtk_object_get_data (GTK_OBJECT (widget), "gtk-aux-info");
      if (aux_info && aux_info->width > 0)
	{
826
	  label->max_width = MAX (aux_info->width - 2 * label->misc.xpad, 1);
Owen Taylor's avatar
Owen Taylor committed
827 828 829 830 831
	  gtk_label_split_text_wrapped (label);
	}
      else
	{
	  label->max_width = gdk_string_width (GTK_WIDGET (label)->style->font,
832
					       "This is a good enough length for any line to have.");
Owen Taylor's avatar
Owen Taylor committed
833 834 835 836 837 838 839 840 841
	  label->max_width = MIN (label->max_width, (gdk_screen_width () + 1) / 2);
	  label->max_width = MIN (label->max_width, longest_paragraph);
	  if (longest_paragraph > 0)
	    {
	      gint nlines, perfect_width;
	      
	      nlines = (longest_paragraph + label->max_width - 1) / label->max_width;
	      perfect_width = (longest_paragraph + nlines - 1) / nlines;
	      label->max_width = gtk_label_pick_width (label,
842 843
						       perfect_width,
						       label->max_width);
Owen Taylor's avatar
Owen Taylor committed
844 845
	    }
	}
846
      gtk_label_finalize_lines_wrap (label, requisition, label->max_width);
Owen Taylor's avatar
Owen Taylor committed
847
    }
848
  else if (!label->words)
Owen Taylor's avatar
Owen Taylor committed
849 850
    {
      label->max_width = gtk_label_split_text (label);
851
      gtk_label_finalize_lines (label, requisition, label->max_width);
Owen Taylor's avatar
Owen Taylor committed
852 853
    }
}
854

855
static void 
856 857
gtk_label_style_set (GtkWidget *widget,
		     GtkStyle  *previous_style)
858 859 860 861
{
  GtkLabel *label;

  g_return_if_fail (GTK_IS_LABEL (widget));
862
  
863 864
  label = GTK_LABEL (widget);
  
865 866 867
  /* Clear the list of words so that they are recomputed on
   * size_request
   */
868 869 870 871
  if (previous_style && label->words)
    gtk_label_free_words (label);
}

Owen Taylor's avatar
Owen Taylor committed
872
static void
873 874 875
gtk_label_paint_word (GtkLabel     *label,
		      gint          x,
		      gint          y,
Owen Taylor's avatar
Owen Taylor committed
876 877 878 879 880 881
		      GtkLabelWord *word,
		      GdkRectangle *area)
{
  GtkWidget *widget = GTK_WIDGET (label);
  GtkLabelULine *uline;
  gchar *tmp_str;
882
  
Owen Taylor's avatar
Owen Taylor committed
883
  tmp_str = gdk_wcstombs (word->beginning);
884 885 886 887 888 889 890 891 892
  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);
    }
Owen Taylor's avatar
Owen Taylor committed
893
  
894
  for (uline = word->uline; uline; uline = uline->next)
Owen Taylor's avatar
Owen Taylor committed
895 896 897
    gtk_paint_hline (widget->style, widget->window, 
		     widget->state, area,
		     widget, "label", 
898
		     x + uline->x1, x + uline->x2, y + uline->y);
Elliot Lee's avatar
Elliot Lee committed
899
}
900

Elliot Lee's avatar
Elliot Lee committed
901
static gint
Owen Taylor's avatar
Owen Taylor committed
902
gtk_label_expose (GtkWidget      *widget,
Elliot Lee's avatar
Elliot Lee committed
903 904 905 906
		  GdkEventExpose *event)
{
  GtkLabel *label;
  GtkMisc *misc;
Owen Taylor's avatar
Owen Taylor committed
907
  GtkLabelWord *word;
Elliot Lee's avatar
Elliot Lee committed
908
  gint x, y;
909
  
Elliot Lee's avatar
Elliot Lee committed
910 911
  g_return_val_if_fail (GTK_IS_LABEL (widget), FALSE);
  g_return_val_if_fail (event != NULL, FALSE);
912
  
Arturo Espinosa's avatar
Arturo Espinosa committed
913
  label = GTK_LABEL (widget);
Tim Janik's avatar
Tim Janik committed
914
  
915 916
  if (GTK_WIDGET_VISIBLE (widget) && GTK_WIDGET_MAPPED (widget) &&
      label->label && (*label->label != '\0'))
Elliot Lee's avatar
Elliot Lee committed
917 918
    {
      misc = GTK_MISC (widget);
919
      
920 921
      /*
       * GC Clipping
Elliot Lee's avatar
Elliot Lee committed
922
       */
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);
925
      
926 927 928 929 930 931 932 933 934 935
      x = floor (widget->allocation.x + (gint)misc->xpad
		 + (((gint)widget->allocation.width - 
		    (gint)label->max_width - 2 * (gint)misc->xpad)
		    * misc->xalign) + 0.5);
      
      y = floor (widget->allocation.y + (gint)misc->ypad 
		 + (((gint)widget->allocation.height
		     - (gint)widget->requisition.height)
		    * misc->yalign) + 0.5);

Owen Taylor's avatar
Owen Taylor committed
936
      for (word = label->words; word; word = word->next)
937
	{
Owen Taylor's avatar
Owen Taylor committed
938 939 940 941
	  gchar save = word->beginning[word->length];
	  word->beginning[word->length] = '\0';
	  gtk_label_paint_word (label, x, y, word, &event->area);
	  word->beginning[word->length] = save;
942
	}
943
      
944
      gdk_gc_set_clip_mask (widget->style->white_gc, NULL);
945
      gdk_gc_set_clip_mask (widget->style->fg_gc[widget->state], NULL);
946
    }
947

Elliot Lee's avatar
Elliot Lee committed
948 949
  return TRUE;
}
950

951
guint      
952 953
gtk_label_parse_uline (GtkLabel    *label,
		       const gchar *string)
954 955
{
  guint accel_key = GDK_VoidSymbol;
956
  GdkWChar *p, *q, *string_wc;
Owen Taylor's avatar
Owen Taylor committed
957 958
  gchar *r;
  gchar *pattern;
959
  gint length, wc_length;
960
  gboolean underscore;
961
  
962 963
  g_return_val_if_fail (GTK_IS_LABEL (label), GDK_VoidSymbol);
  g_return_val_if_fail (string != NULL, GDK_VoidSymbol);