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 77 78
				    GtkArg	   *arg,
				    guint	    arg_id);
static void gtk_label_finalize	   (GtkObject	   *object);
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 GtkTypeInfo label_info =
Elliot Lee's avatar
Elliot Lee committed
105 106 107 108 109 110
      {
	"GtkLabel",
	sizeof (GtkLabel),
	sizeof (GtkLabelClass),
	(GtkClassInitFunc) gtk_label_class_init,
	(GtkObjectInitFunc) gtk_label_init,
111 112
        /* reserved_1 */ NULL,
	/* reserved_2 */ NULL,
113
	(GtkClassInitFunc) NULL,
Elliot Lee's avatar
Elliot Lee committed
114
      };
Tim Janik's avatar
Tim Janik committed
115
      
116
      label_type = gtk_type_unique (GTK_TYPE_MISC, &label_info);
117
      gtk_type_set_chunk_alloc (label_type, 32);
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 126 127
gtk_label_class_init (GtkLabelClass *class)
{
  GtkObjectClass *object_class;
  GtkWidgetClass *widget_class;
Tim Janik's avatar
Tim Janik committed
128
  
Elliot Lee's avatar
Elliot Lee committed
129 130
  object_class = (GtkObjectClass*) class;
  widget_class = (GtkWidgetClass*) class;
Tim Janik's avatar
Tim Janik committed
131
  
132
  parent_class = gtk_type_class (GTK_TYPE_MISC);
Tim Janik's avatar
Tim Janik committed
133
  
134
  gtk_object_add_arg_type ("GtkLabel::label", GTK_TYPE_STRING, GTK_ARG_READWRITE, ARG_LABEL);
135
  gtk_object_add_arg_type ("GtkLabel::pattern", GTK_TYPE_STRING, GTK_ARG_READWRITE, ARG_PATTERN);
136
  gtk_object_add_arg_type ("GtkLabel::justify", GTK_TYPE_JUSTIFICATION, GTK_ARG_READWRITE, ARG_JUSTIFY);
137
  gtk_object_add_arg_type ("GtkLabel::wrap", GTK_TYPE_BOOL, GTK_ARG_READWRITE, ARG_WRAP);
138
  
139 140
  object_class->set_arg = gtk_label_set_arg;
  object_class->get_arg = gtk_label_get_arg;
141
  object_class->finalize = gtk_label_finalize;
Tim Janik's avatar
Tim Janik committed
142
  
Elliot Lee's avatar
Elliot Lee committed
143
  widget_class->size_request = gtk_label_size_request;
144
  widget_class->style_set = gtk_label_style_set;
Elliot Lee's avatar
Elliot Lee committed
145 146 147
  widget_class->expose_event = gtk_label_expose;
}

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

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

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

Owen Taylor's avatar
Owen Taylor committed
214
  label->words = NULL;
215

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

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

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

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

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

  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);
270 271 272 273 274 275 276
      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);
277
    }
278 279
}

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

  gtk_widget_queue_resize (GTK_WIDGET (label));
Elliot Lee's avatar
Elliot Lee committed
292 293 294
}

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

      gtk_widget_queue_resize (GTK_WIDGET (label));
Elliot Lee's avatar
Elliot Lee committed
308 309 310 311
    }
}

void
312 313
gtk_label_set_line_wrap (GtkLabel *label,
			 gboolean  wrap)
Owen Taylor's avatar
Owen Taylor committed
314 315
{
  g_return_if_fail (GTK_IS_LABEL (label));
316
  
317 318 319 320 321 322 323 324 325 326
  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
327 328 329
}

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

340 341 342 343 344 345 346 347 348
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
349
static void
350
gtk_label_finalize (GtkObject *object)
Elliot Lee's avatar
Elliot Lee committed
351 352
{
  GtkLabel *label;
353
  
Elliot Lee's avatar
Elliot Lee committed
354 355
  g_return_if_fail (object != NULL);
  g_return_if_fail (GTK_IS_LABEL (object));
356
  
Elliot Lee's avatar
Elliot Lee committed
357
  label = GTK_LABEL (object);
358
  
Elliot Lee's avatar
Elliot Lee committed
359
  g_free (label->label);
Owen Taylor's avatar
Owen Taylor committed
360
  g_free (label->label_wc);
361 362
  g_free (label->pattern);

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

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

368 369
static GtkLabelULine*
gtk_label_uline_alloc (void)
370
{
371
  GtkLabelULine *uline;
372
  
373 374 375 376
  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
377
  
378
  uline->next = NULL;
379
  
380
  return uline;
Owen Taylor's avatar
Owen Taylor committed
381
}
382

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

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

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

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

static void
413
gtk_label_free_words (GtkLabel *label)
Owen Taylor's avatar
Owen Taylor committed
414
{
415
  while (label->words)
Owen Taylor's avatar
Owen Taylor committed
416
    {
417 418 419 420 421 422 423
      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
424 425
    }
}
426

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

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

587
/* gtk_label_pick_width
Owen Taylor's avatar
Owen Taylor committed
588 589 590 591 592 593 594 595 596 597 598
 *
 * 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
599 600 601
gtk_label_pick_width (GtkLabel *label,
		      gint      min_width,
		      gint      max_width)
Owen Taylor's avatar
Owen Taylor committed
602 603 604 605 606
{
  GtkLabelWord *word;
  gint width, line_width;
  
  g_return_val_if_fail (label->wrap, min_width);
607
  
Owen Taylor's avatar
Owen Taylor committed
608 609 610 611 612 613 614 615 616 617 618 619 620 621 622
  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;
    }
623
  
Owen Taylor's avatar
Owen Taylor committed
624 625 626
  return MAX (width, line_width);
}

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

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

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

Owen Taylor's avatar
Owen Taylor committed
794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813
  /*
   * 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.
   */
814
  
Owen Taylor's avatar
Owen Taylor committed
815
  if (label->wrap)
Elliot Lee's avatar
Elliot Lee committed
816
    {
Owen Taylor's avatar
Owen Taylor committed
817 818 819 820 821 822 823 824
      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)
	{
825
	  label->max_width = MAX (aux_info->width - 2 * label->misc.xpad, 1);
Owen Taylor's avatar
Owen Taylor committed
826 827 828 829 830
	  gtk_label_split_text_wrapped (label);
	}
      else
	{
	  label->max_width = gdk_string_width (GTK_WIDGET (label)->style->font,
831
					       "This is a good enough length for any line to have.");
Owen Taylor's avatar
Owen Taylor committed
832 833 834 835 836 837 838 839 840
	  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,
841 842
						       perfect_width,
						       label->max_width);
Owen Taylor's avatar
Owen Taylor committed
843 844
	    }
	}
845
      gtk_label_finalize_lines_wrap (label, requisition, label->max_width);
Owen Taylor's avatar
Owen Taylor committed
846
    }
847
  else if (!label->words)
Owen Taylor's avatar
Owen Taylor committed
848 849
    {
      label->max_width = gtk_label_split_text (label);
850
      gtk_label_finalize_lines (label, requisition, label->max_width);
Owen Taylor's avatar
Owen Taylor committed
851 852
    }
}
853

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

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

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

Elliot Lee's avatar
Elliot Lee committed
900
static gint
Owen Taylor's avatar
Owen Taylor committed
901
gtk_label_expose (GtkWidget      *widget,
Elliot Lee's avatar
Elliot Lee committed
902 903 904 905
		  GdkEventExpose *event)
{
  GtkLabel *label;
  GtkMisc *misc;
Owen Taylor's avatar
Owen Taylor committed
906
  GtkLabelWord *word;
Elliot Lee's avatar
Elliot Lee committed
907
  gint x, y;
908
  
Elliot Lee's avatar
Elliot Lee committed
909 910
  g_return_val_if_fail (GTK_IS_LABEL (widget), FALSE);
  g_return_val_if_fail (event != NULL, FALSE);
911
  
Arturo Espinosa's avatar
Arturo Espinosa committed
912
  label = GTK_LABEL (widget);
Tim Janik's avatar
Tim Janik committed
913
  
914 915
  if (GTK_WIDGET_VISIBLE (widget) && GTK_WIDGET_MAPPED (widget) &&
      label->label && (*label->label != '\0'))
Elliot Lee's avatar
Elliot Lee committed
916 917
    {
      misc = GTK_MISC (widget);
918
      
919 920
      /*
       * GC Clipping
Elliot Lee's avatar
Elliot Lee committed
921
       */
922
      gdk_gc_set_clip_rectangle (widget->style->white_gc, &event->area);
923
      gdk_gc_set_clip_rectangle (widget->style->fg_gc[widget->state], &event->area);
924
      
925 926 927 928 929 930 931 932 933 934
      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
935
      for (word = label->words; word; word = word->next)
936
	{
Owen Taylor's avatar
Owen Taylor committed
937 938 939 940
	  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;
941
	}
942
      
943
      gdk_gc_set_clip_mask (widget->style->white_gc, NULL);
944
      gdk_gc_set_clip_mask (widget->style->fg_gc[widget->state], NULL);
945
    }
946

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

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