1/* $Id$ 2 * 3 * text, image, and label elements. 4 * 5 * The label element combines text and image elements, 6 * with layout determined by the "-compound" option. 7 * 8 */ 9 10#include <tcl.h> 11#include <tk.h> 12#include "ttkTheme.h" 13 14/*---------------------------------------------------------------------- 15 * +++ Text element. 16 * 17 * This element displays a textual label in the foreground color. 18 * 19 * Optionally underlines the mnemonic character if the -underline resource 20 * is present and >= 0. 21 */ 22 23typedef struct { 24 /* 25 * Element options: 26 */ 27 Tcl_Obj *textObj; 28 Tcl_Obj *fontObj; 29 Tcl_Obj *foregroundObj; 30 Tcl_Obj *underlineObj; 31 Tcl_Obj *widthObj; 32 Tcl_Obj *anchorObj; 33 Tcl_Obj *justifyObj; 34 Tcl_Obj *wrapLengthObj; 35 Tcl_Obj *embossedObj; 36 37 /* 38 * Computed resources: 39 */ 40 Tk_Font tkfont; 41 Tk_TextLayout textLayout; 42 int width; 43 int height; 44 int embossed; 45 46} TextElement; 47 48/* Text element options table. 49 * NB: Keep in sync with label element option table. 50 */ 51static Ttk_ElementOptionSpec TextElementOptions[] = { 52 { "-text", TK_OPTION_STRING, 53 Tk_Offset(TextElement,textObj), "" }, 54 { "-font", TK_OPTION_FONT, 55 Tk_Offset(TextElement,fontObj), DEFAULT_FONT }, 56 { "-foreground", TK_OPTION_COLOR, 57 Tk_Offset(TextElement,foregroundObj), "black" }, 58 { "-underline", TK_OPTION_INT, 59 Tk_Offset(TextElement,underlineObj), "-1"}, 60 { "-width", TK_OPTION_INT, 61 Tk_Offset(TextElement,widthObj), "-1"}, 62 { "-anchor", TK_OPTION_ANCHOR, 63 Tk_Offset(TextElement,anchorObj), "w"}, 64 { "-justify", TK_OPTION_JUSTIFY, 65 Tk_Offset(TextElement,justifyObj), "left" }, 66 { "-wraplength", TK_OPTION_PIXELS, 67 Tk_Offset(TextElement,wrapLengthObj), "0" }, 68 { "-embossed", TK_OPTION_INT, 69 Tk_Offset(TextElement,embossedObj), "0"}, 70 { NULL, 0, 0, NULL } 71}; 72 73static int TextSetup(TextElement *text, Tk_Window tkwin) 74{ 75 const char *string = Tcl_GetString(text->textObj); 76 Tk_Justify justify = TK_JUSTIFY_LEFT; 77 int wrapLength = 0; 78 79 text->tkfont = Tk_GetFontFromObj(tkwin, text->fontObj); 80 Tk_GetJustifyFromObj(NULL, text->justifyObj, &justify); 81 Tk_GetPixelsFromObj(NULL, tkwin, text->wrapLengthObj, &wrapLength); 82 Tcl_GetBooleanFromObj(NULL, text->embossedObj, &text->embossed); 83 84 text->textLayout = Tk_ComputeTextLayout( 85 text->tkfont, string, -1/*numChars*/, wrapLength, justify, 86 0/*flags*/, &text->width, &text->height); 87 88 return 1; 89} 90 91/* 92 * TextReqWidth -- compute the requested width of a text element. 93 * 94 * If -width is positive, use that as the width 95 * If -width is negative, use that as the minimum width 96 * If not specified or empty, use the natural size of the text 97 */ 98 99static int TextReqWidth(TextElement *text) 100{ 101 int reqWidth; 102 103 if ( text->widthObj 104 && Tcl_GetIntFromObj(NULL, text->widthObj, &reqWidth) == TCL_OK) 105 { 106 int avgWidth = Tk_TextWidth(text->tkfont, "0", 1); 107 if (reqWidth <= 0) { 108 int specWidth = avgWidth * -reqWidth; 109 if (specWidth > text->width) 110 return specWidth; 111 } else { 112 return avgWidth * reqWidth; 113 } 114 } 115 return text->width; 116} 117 118static void TextCleanup(TextElement *text) 119{ 120 Tk_FreeTextLayout(text->textLayout); 121} 122 123/* 124 * TextDraw -- 125 * Draw a text element. 126 * Called by TextElementDraw() and LabelElementDraw(). 127 */ 128static void TextDraw(TextElement *text, Tk_Window tkwin, Drawable d, Ttk_Box b) 129{ 130 XColor *color = Tk_GetColorFromObj(tkwin, text->foregroundObj); 131 int underline = -1; 132 int lastChar = -1; 133 XGCValues gcValues; 134 GC gc1, gc2; 135 Tk_Anchor anchor = TK_ANCHOR_CENTER; 136 137 gcValues.font = Tk_FontId(text->tkfont); 138 gcValues.foreground = color->pixel; 139 gc1 = Tk_GetGC(tkwin, GCFont | GCForeground, &gcValues); 140 gcValues.foreground = WhitePixelOfScreen(Tk_Screen(tkwin)); 141 gc2 = Tk_GetGC(tkwin, GCFont | GCForeground, &gcValues); 142 143 /* 144 * Place text according to -anchor: 145 */ 146 Tk_GetAnchorFromObj(NULL, text->anchorObj, &anchor); 147 b = Ttk_AnchorBox(b, text->width, text->height, anchor); 148 149 /* 150 * Clip text if it's too wide: 151 * @@@ BUG: This will overclip multi-line text. 152 */ 153 if (b.width < text->width) { 154 lastChar = Tk_PointToChar(text->textLayout, b.width, 1) + 1; 155 } 156 157 if (text->embossed) { 158 Tk_DrawTextLayout(Tk_Display(tkwin), d, gc2, 159 text->textLayout, b.x+1, b.y+1, 0/*firstChar*/, lastChar); 160 } 161 Tk_DrawTextLayout(Tk_Display(tkwin), d, gc1, 162 text->textLayout, b.x, b.y, 0/*firstChar*/, lastChar); 163 164 Tcl_GetIntFromObj(NULL, text->underlineObj, &underline); 165 if (underline >= 0 && (lastChar == -1 || underline <= lastChar)) { 166 if (text->embossed) { 167 Tk_UnderlineTextLayout(Tk_Display(tkwin), d, gc2, 168 text->textLayout, b.x+1, b.y+1, underline); 169 } 170 Tk_UnderlineTextLayout(Tk_Display(tkwin), d, gc1, 171 text->textLayout, b.x, b.y, underline); 172 } 173 174 Tk_FreeGC(Tk_Display(tkwin), gc1); 175 Tk_FreeGC(Tk_Display(tkwin), gc2); 176} 177 178static void TextElementSize( 179 void *clientData, void *elementRecord, Tk_Window tkwin, 180 int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) 181{ 182 TextElement *text = elementRecord; 183 184 if (!TextSetup(text, tkwin)) 185 return; 186 187 *heightPtr = text->height; 188 *widthPtr = TextReqWidth(text); 189 190 TextCleanup(text); 191 192 return; 193} 194 195static void TextElementDraw( 196 void *clientData, void *elementRecord, Tk_Window tkwin, 197 Drawable d, Ttk_Box b, Ttk_State state) 198{ 199 TextElement *text = elementRecord; 200 if (TextSetup(text, tkwin)) { 201 TextDraw(text, tkwin, d, b); 202 TextCleanup(text); 203 } 204} 205 206static Ttk_ElementSpec TextElementSpec = { 207 TK_STYLE_VERSION_2, 208 sizeof(TextElement), 209 TextElementOptions, 210 TextElementSize, 211 TextElementDraw 212}; 213 214/*---------------------------------------------------------------------- 215 * +++ Image element. 216 * Draws an image. 217 */ 218 219typedef struct { 220 Tcl_Obj *imageObj; 221 Tcl_Obj *stippleObj; /* For TTK_STATE_DISABLED */ 222 Tcl_Obj *backgroundObj; /* " " */ 223 224 Ttk_ImageSpec *imageSpec; 225 Tk_Image tkimg; 226 int width; 227 int height; 228} ImageElement; 229 230/* ===> NB: Keep in sync with label element option table. <=== 231 */ 232static Ttk_ElementOptionSpec ImageElementOptions[] = { 233 { "-image", TK_OPTION_STRING, 234 Tk_Offset(ImageElement,imageObj), "" }, 235 { "-stipple", TK_OPTION_STRING, /* Really: TK_OPTION_BITMAP */ 236 Tk_Offset(ImageElement,stippleObj), "gray50" }, 237 { "-background", TK_OPTION_COLOR, 238 Tk_Offset(ImageElement,backgroundObj), DEFAULT_BACKGROUND }, 239 { NULL, 0, 0, NULL } 240}; 241 242/* 243 * ImageSetup() -- 244 * Look up the Tk_Image from the image element's imageObj resource. 245 * Caller must release the image with ImageCleanup(). 246 * 247 * Returns: 248 * 1 if successful, 0 if there was an error (unreported) 249 * or the image resource was not specified. 250 */ 251 252static int ImageSetup( 253 ImageElement *image, Tk_Window tkwin, Ttk_State state) 254{ 255 256 if (!image->imageObj) { 257 return 0; 258 } 259 image->imageSpec = TtkGetImageSpec(NULL, tkwin, image->imageObj); 260 if (!image->imageSpec) { 261 return 0; 262 } 263 image->tkimg = TtkSelectImage(image->imageSpec, state); 264 if (!image->tkimg) { 265 TtkFreeImageSpec(image->imageSpec); 266 return 0; 267 } 268 Tk_SizeOfImage(image->tkimg, &image->width, &image->height); 269 270 return 1; 271} 272 273static void ImageCleanup(ImageElement *image) 274{ 275 TtkFreeImageSpec(image->imageSpec); 276} 277 278/* 279 * StippleOver -- 280 * Draw a stipple over the image area, to make it look "grayed-out" 281 * when TTK_STATE_DISABLED is set. 282 */ 283static void StippleOver( 284 ImageElement *image, Tk_Window tkwin, Drawable d, int x, int y) 285{ 286 Pixmap stipple = Tk_AllocBitmapFromObj(NULL, tkwin, image->stippleObj); 287 XColor *color = Tk_GetColorFromObj(tkwin, image->backgroundObj); 288 289 if (stipple != None) { 290 unsigned long mask = GCFillStyle | GCStipple | GCForeground; 291 XGCValues gcvalues; 292 GC gc; 293 gcvalues.foreground = color->pixel; 294 gcvalues.fill_style = FillStippled; 295 gcvalues.stipple = stipple; 296 gc = Tk_GetGC(tkwin, mask, &gcvalues); 297 XFillRectangle(Tk_Display(tkwin),d,gc,x,y,image->width,image->height); 298 Tk_FreeGC(Tk_Display(tkwin), gc); 299 Tk_FreeBitmapFromObj(tkwin, image->stippleObj); 300 } 301} 302 303static void ImageDraw( 304 ImageElement *image, Tk_Window tkwin,Drawable d,Ttk_Box b,Ttk_State state) 305{ 306 int width = image->width, height = image->height; 307 308 /* Clip width and height to remain within window bounds: 309 */ 310 if (b.x + width > Tk_Width(tkwin)) { 311 width = Tk_Width(tkwin) - b.x; 312 } 313 if (b.y + height > Tk_Height(tkwin)) { 314 height = Tk_Height(tkwin) - b.y; 315 } 316 317 if (height <= 0 || width <= 0) { 318 /* Completely clipped - bail out. 319 */ 320 return; 321 } 322 323 Tk_RedrawImage(image->tkimg, 0,0, width, height, d, b.x, b.y); 324 325 /* If we're disabled there's no state-specific 'disabled' image, 326 * stipple the image. 327 * @@@ Possibly: Don't do disabled-stippling at all; 328 * @@@ it's ugly and out of fashion. 329 */ 330 if (state & TTK_STATE_DISABLED) { 331 if (TtkSelectImage(image->imageSpec, 0ul) == image->tkimg) { 332 StippleOver(image, tkwin, d, b.x,b.y); 333 } 334 } 335} 336 337static void ImageElementSize( 338 void *clientData, void *elementRecord, Tk_Window tkwin, 339 int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) 340{ 341 ImageElement *image = elementRecord; 342 343 if (ImageSetup(image, tkwin, 0)) { 344 *widthPtr = image->width; 345 *heightPtr = image->height; 346 ImageCleanup(image); 347 } 348} 349 350static void ImageElementDraw( 351 void *clientData, void *elementRecord, Tk_Window tkwin, 352 Drawable d, Ttk_Box b, Ttk_State state) 353{ 354 ImageElement *image = elementRecord; 355 356 if (ImageSetup(image, tkwin, state)) { 357 ImageDraw(image, tkwin, d, b, state); 358 ImageCleanup(image); 359 } 360} 361 362static Ttk_ElementSpec ImageElementSpec = { 363 TK_STYLE_VERSION_2, 364 sizeof(ImageElement), 365 ImageElementOptions, 366 ImageElementSize, 367 ImageElementDraw 368}; 369 370/*------------------------------------------------------------------------ 371 * +++ Label element. 372 * 373 * Displays an image and/or text, as determined by the -compound option. 374 * 375 * Differences from Tk 8.4 compound elements: 376 * 377 * This adds two new values for the -compound option, "text" 378 * and "image". (This is useful for configuring toolbars to 379 * display icons, text and icons, or text only, as found in 380 * many browsers.) 381 * 382 * "-compound none" is supported, but I'd like to get rid of it; 383 * it makes the logic more complex, and the only benefit is 384 * backwards compatibility with Tk < 8.3.0 scripts. 385 * 386 * This adds a new resource, -space, for determining how much 387 * space to leave between the text and image; Tk 8.4 reuses the 388 * -padx or -pady option for this purpose. 389 * 390 * -width always specifies the length in characters of the text part; 391 * in Tk 8.4 it's either characters or pixels, depending on the 392 * value of -compound. 393 * 394 * Negative values of -width are interpreted as a minimum width 395 * on all platforms, not just on Windows. 396 * 397 * Tk 8.4 ignores -padx and -pady if -compound is set to "none". 398 * Here, padding is handled by a different element. 399 */ 400 401typedef struct { 402 /* 403 * Element options: 404 */ 405 Tcl_Obj *compoundObj; 406 Tcl_Obj *spaceObj; 407 TextElement text; 408 ImageElement image; 409 410 /* 411 * Computed values (see LabelSetup) 412 */ 413 Ttk_Compound compound; 414 int space; 415 int totalWidth, totalHeight; 416} LabelElement; 417 418static Ttk_ElementOptionSpec LabelElementOptions[] = { 419 { "-compound", TK_OPTION_ANY, 420 Tk_Offset(LabelElement,compoundObj), "none" }, 421 { "-space", TK_OPTION_PIXELS, 422 Tk_Offset(LabelElement,spaceObj), "4" }, 423 424 /* Text element part: 425 * NB: Keep in sync with TextElementOptions. 426 */ 427 { "-text", TK_OPTION_STRING, 428 Tk_Offset(LabelElement,text.textObj), "" }, 429 { "-font", TK_OPTION_FONT, 430 Tk_Offset(LabelElement,text.fontObj), DEFAULT_FONT }, 431 { "-foreground", TK_OPTION_COLOR, 432 Tk_Offset(LabelElement,text.foregroundObj), "black" }, 433 { "-underline", TK_OPTION_INT, 434 Tk_Offset(LabelElement,text.underlineObj), "-1"}, 435 { "-width", TK_OPTION_INT, 436 Tk_Offset(LabelElement,text.widthObj), ""}, 437 { "-anchor", TK_OPTION_ANCHOR, 438 Tk_Offset(LabelElement,text.anchorObj), "w"}, 439 { "-justify", TK_OPTION_JUSTIFY, 440 Tk_Offset(LabelElement,text.justifyObj), "left" }, 441 { "-wraplength", TK_OPTION_PIXELS, 442 Tk_Offset(LabelElement,text.wrapLengthObj), "0" }, 443 { "-embossed", TK_OPTION_INT, 444 Tk_Offset(LabelElement,text.embossedObj), "0"}, 445 446 /* Image element part: 447 * NB: Keep in sync with ImageElementOptions. 448 */ 449 { "-image", TK_OPTION_STRING, 450 Tk_Offset(LabelElement,image.imageObj), "" }, 451 { "-stipple", TK_OPTION_STRING, /* Really: TK_OPTION_BITMAP */ 452 Tk_Offset(LabelElement,image.stippleObj), "gray50" }, 453 { "-background", TK_OPTION_COLOR, 454 Tk_Offset(LabelElement,image.backgroundObj), DEFAULT_BACKGROUND }, 455 { NULL, 0, 0, NULL } 456}; 457 458/* 459 * LabelSetup -- 460 * Fills in computed fields of the label element. 461 * 462 * Calculate the text, image, and total width and height. 463 */ 464 465#define MAX(a,b) ((a) > (b) ? a : b); 466static void LabelSetup( 467 LabelElement *c, Tk_Window tkwin, Ttk_State state) 468{ 469 Ttk_Compound *compoundPtr = &c->compound; 470 471 Tk_GetPixelsFromObj(NULL,tkwin,c->spaceObj,&c->space); 472 Ttk_GetCompoundFromObj(NULL,c->compoundObj,(int*)compoundPtr); 473 474 /* 475 * Deal with TTK_COMPOUND_NONE. 476 */ 477 if (c->compound == TTK_COMPOUND_NONE) { 478 if (ImageSetup(&c->image, tkwin, state)) { 479 c->compound = TTK_COMPOUND_IMAGE; 480 } else { 481 c->compound = TTK_COMPOUND_TEXT; 482 } 483 } else if (c->compound != TTK_COMPOUND_TEXT) { 484 if (!ImageSetup(&c->image, tkwin, state)) { 485 c->compound = TTK_COMPOUND_TEXT; 486 } 487 } 488 if (c->compound != TTK_COMPOUND_IMAGE) 489 TextSetup(&c->text, tkwin); 490 491 /* 492 * ASSERT: 493 * if c->compound != IMAGE, then TextSetup() has been called 494 * if c->compound != TEXT, then ImageSetup() has returned successfully 495 * c->compound != COMPOUND_NONE. 496 */ 497 498 switch (c->compound) 499 { 500 case TTK_COMPOUND_NONE: 501 /* Can't happen */ 502 break; 503 case TTK_COMPOUND_TEXT: 504 c->totalWidth = c->text.width; 505 c->totalHeight = c->text.height; 506 break; 507 case TTK_COMPOUND_IMAGE: 508 c->totalWidth = c->image.width; 509 c->totalHeight = c->image.height; 510 break; 511 case TTK_COMPOUND_CENTER: 512 c->totalWidth = MAX(c->image.width, c->text.width); 513 c->totalHeight = MAX(c->image.height, c->text.height); 514 break; 515 case TTK_COMPOUND_TOP: 516 case TTK_COMPOUND_BOTTOM: 517 c->totalWidth = MAX(c->image.width, c->text.width); 518 c->totalHeight = c->image.height + c->text.height + c->space; 519 break; 520 521 case TTK_COMPOUND_LEFT: 522 case TTK_COMPOUND_RIGHT: 523 c->totalWidth = c->image.width + c->text.width + c->space; 524 c->totalHeight = MAX(c->image.height, c->text.height); 525 break; 526 } 527} 528 529static void LabelCleanup(LabelElement *c) 530{ 531 if (c->compound != TTK_COMPOUND_TEXT) 532 ImageCleanup(&c->image); 533 if (c->compound != TTK_COMPOUND_IMAGE) 534 TextCleanup(&c->text); 535} 536 537static void LabelElementSize( 538 void *clientData, void *elementRecord, Tk_Window tkwin, 539 int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) 540{ 541 LabelElement *label = elementRecord; 542 int textReqWidth = 0; 543 544 LabelSetup(label, tkwin, 0); 545 546 *heightPtr = label->totalHeight; 547 548 /* Requested width based on -width option, not actual text width: 549 */ 550 if (label->compound != TTK_COMPOUND_IMAGE) 551 textReqWidth = TextReqWidth(&label->text); 552 553 switch (label->compound) 554 { 555 case TTK_COMPOUND_TEXT: 556 *widthPtr = textReqWidth; 557 break; 558 case TTK_COMPOUND_IMAGE: 559 *widthPtr = label->image.width; 560 break; 561 case TTK_COMPOUND_TOP: 562 case TTK_COMPOUND_BOTTOM: 563 case TTK_COMPOUND_CENTER: 564 *widthPtr = MAX(label->image.width, textReqWidth); 565 break; 566 case TTK_COMPOUND_LEFT: 567 case TTK_COMPOUND_RIGHT: 568 *widthPtr = label->image.width + textReqWidth + label->space; 569 break; 570 case TTK_COMPOUND_NONE: 571 break; /* Can't happen */ 572 } 573 574 LabelCleanup(label); 575} 576 577/* 578 * DrawCompound -- 579 * Helper routine for LabelElementDraw; 580 * Handles layout for -compound {left,right,top,bottom} 581 */ 582static void DrawCompound( 583 LabelElement *l, Ttk_Box b, Tk_Window tkwin, Drawable d, Ttk_State state, 584 int imageSide, int textSide) 585{ 586 Ttk_Box imageBox = 587 Ttk_PlaceBox(&b, l->image.width, l->image.height, imageSide, 0); 588 Ttk_Box textBox = 589 Ttk_PlaceBox(&b, l->text.width, l->text.height, textSide, 0); 590 ImageDraw(&l->image,tkwin,d,imageBox,state); 591 TextDraw(&l->text,tkwin,d,textBox); 592} 593 594static void LabelElementDraw( 595 void *clientData, void *elementRecord, Tk_Window tkwin, 596 Drawable d, Ttk_Box b, Ttk_State state) 597{ 598 LabelElement *l = elementRecord; 599 Tk_Anchor anchor = TK_ANCHOR_CENTER; 600 601 LabelSetup(l, tkwin, state); 602 603 /* 604 * Adjust overall parcel based on -anchor: 605 */ 606 Tk_GetAnchorFromObj(NULL, l->text.anchorObj, &anchor); 607 b = Ttk_AnchorBox(b, l->totalWidth, l->totalHeight, anchor); 608 609 /* 610 * Draw text and/or image parts based on -compound: 611 */ 612 switch (l->compound) 613 { 614 case TTK_COMPOUND_NONE: 615 /* Can't happen */ 616 break; 617 case TTK_COMPOUND_TEXT: 618 TextDraw(&l->text,tkwin,d,b); 619 break; 620 case TTK_COMPOUND_IMAGE: 621 ImageDraw(&l->image,tkwin,d,b,state); 622 break; 623 case TTK_COMPOUND_CENTER: 624 { 625 Ttk_Box pb = Ttk_AnchorBox( 626 b, l->image.width, l->image.height, TK_ANCHOR_CENTER); 627 ImageDraw(&l->image, tkwin, d, pb, state); 628 pb = Ttk_AnchorBox( 629 b, l->text.width, l->text.height, TK_ANCHOR_CENTER); 630 TextDraw(&l->text, tkwin, d, pb); 631 break; 632 } 633 case TTK_COMPOUND_TOP: 634 DrawCompound(l, b, tkwin, d, state, TTK_SIDE_TOP, TTK_SIDE_BOTTOM); 635 break; 636 case TTK_COMPOUND_BOTTOM: 637 DrawCompound(l, b, tkwin, d, state, TTK_SIDE_BOTTOM, TTK_SIDE_TOP); 638 break; 639 case TTK_COMPOUND_LEFT: 640 DrawCompound(l, b, tkwin, d, state, TTK_SIDE_LEFT, TTK_SIDE_RIGHT); 641 break; 642 case TTK_COMPOUND_RIGHT: 643 DrawCompound(l, b, tkwin, d, state, TTK_SIDE_RIGHT, TTK_SIDE_LEFT); 644 break; 645 } 646 647 LabelCleanup(l); 648} 649 650static Ttk_ElementSpec LabelElementSpec = { 651 TK_STYLE_VERSION_2, 652 sizeof(LabelElement), 653 LabelElementOptions, 654 LabelElementSize, 655 LabelElementDraw 656}; 657 658/*------------------------------------------------------------------------ 659 * +++ Initialization. 660 */ 661 662MODULE_SCOPE 663void TtkLabel_Init(Tcl_Interp *interp) 664{ 665 Ttk_Theme theme = Ttk_GetDefaultTheme(interp); 666 667 Ttk_RegisterElement(interp, theme, "text", &TextElementSpec, NULL); 668 Ttk_RegisterElement(interp, theme, "image", &ImageElementSpec, NULL); 669 Ttk_RegisterElement(interp, theme, "label", &LabelElementSpec, NULL); 670} 671 672