1/* 2 * $Id$ 3 * 4 * DERIVED FROM: tk/generic/tkEntry.c r1.35. 5 * 6 * Copyright (c) 1990-1994 The Regents of the University of California. 7 * Copyright (c) 1994-1997 Sun Microsystems, Inc. 8 * Copyright (c) 2000 Ajuba Solutions. 9 * Copyright (c) 2002 ActiveState Corporation. 10 * Copyright (c) 2004 Joe English 11 */ 12 13#include <string.h> 14#include <stdio.h> 15#include <tk.h> 16#include <X11/Xatom.h> 17 18#include "ttkTheme.h" 19#include "ttkWidget.h" 20 21/* 22 * Extra bits for core.flags: 23 */ 24#define GOT_SELECTION (WIDGET_USER_FLAG<<1) 25#define SYNCING_VARIABLE (WIDGET_USER_FLAG<<2) 26#define VALIDATING (WIDGET_USER_FLAG<<3) 27#define VALIDATION_SET_VALUE (WIDGET_USER_FLAG<<4) 28 29/* 30 * Definitions for -validate option values: 31 */ 32typedef enum validateMode { 33 VMODE_ALL, VMODE_KEY, VMODE_FOCUS, VMODE_FOCUSIN, VMODE_FOCUSOUT, VMODE_NONE 34} VMODE; 35 36static const char *const validateStrings[] = { 37 "all", "key", "focus", "focusin", "focusout", "none", NULL 38}; 39 40/* 41 * Validation reasons: 42 */ 43typedef enum validateReason { 44 VALIDATE_INSERT, VALIDATE_DELETE, 45 VALIDATE_FOCUSIN, VALIDATE_FOCUSOUT, 46 VALIDATE_FORCED 47} VREASON; 48 49static const char *const validateReasonStrings[] = { 50 "key", "key", "focusin", "focusout", "forced", NULL 51}; 52 53/*------------------------------------------------------------------------ 54 * +++ Entry widget record. 55 * 56 * Dependencies: 57 * 58 * textVariableTrace : textVariableObj 59 * 60 * numBytes,numChars : string 61 * displayString : numChars, showChar 62 * layoutHeight, 63 * layoutWidth, 64 * textLayout : fontObj, displayString 65 * layoutX, layoutY : textLayout, justify, xscroll.first 66 * 67 * Invariants: 68 * 69 * 0 <= insertPos <= numChars 70 * 0 <= selectFirst < selectLast <= numChars || selectFirst == selectLast == -1 71 * displayString points to string if showChar == NULL, 72 * or to malloc'ed storage if showChar != NULL. 73 */ 74 75/* Style parameters: 76 */ 77typedef struct { 78 Tcl_Obj *foregroundObj; /* Foreground color for normal text */ 79 Tcl_Obj *backgroundObj; /* Entry widget background color */ 80 Tcl_Obj *selBorderObj; /* Border and background for selection */ 81 Tcl_Obj *selBorderWidthObj; /* Width of selection border */ 82 Tcl_Obj *selForegroundObj; /* Foreground color for selected text */ 83 Tcl_Obj *insertColorObj; /* Color of insertion cursor */ 84 Tcl_Obj *insertWidthObj; /* Insert cursor width */ 85} EntryStyleData; 86 87typedef struct { 88 /* 89 * Internal state: 90 */ 91 char *string; /* Storage for string (malloced) */ 92 int numBytes; /* Length of string in bytes. */ 93 int numChars; /* Length of string in characters. */ 94 95 int insertPos; /* Insert index */ 96 int selectFirst; /* Index of start of selection, or -1 */ 97 int selectLast; /* Index of end of selection, or -1 */ 98 99 Scrollable xscroll; /* Current scroll position */ 100 ScrollHandle xscrollHandle; 101 102 /* 103 * Options managed by Tk_SetOptions: 104 */ 105 Tcl_Obj *textVariableObj; /* Name of linked variable */ 106 int exportSelection; /* Tie internal selection to X selection? */ 107 108 VMODE validate; /* Validation mode */ 109 char *validateCmd; /* Validation script template */ 110 char *invalidCmd; /* Invalid callback script template */ 111 112 char *showChar; /* Used to derive displayString */ 113 114 Tcl_Obj *fontObj; /* Text font to use */ 115 Tcl_Obj *widthObj; /* Desired width of window (in avgchars) */ 116 Tk_Justify justify; /* Text justification */ 117 118 EntryStyleData styleData; /* Display style data (widget options) */ 119 EntryStyleData styleDefaults;/* Style defaults (fallback values) */ 120 121 Tcl_Obj *stateObj; /* Compatibility option -- see CheckStateObj */ 122 123 /* 124 * Derived resources: 125 */ 126 Ttk_TraceHandle *textVariableTrace; 127 128 char *displayString; /* String to use when displaying */ 129 Tk_TextLayout textLayout; /* Cached text layout information. */ 130 int layoutWidth; /* textLayout width */ 131 int layoutHeight; /* textLayout height */ 132 133 int layoutX, layoutY; /* Origin for text layout. */ 134 135} EntryPart; 136 137typedef struct { 138 WidgetCore core; 139 EntryPart entry; 140} Entry; 141 142/* 143 * Extra mask bits for Tk_SetOptions() 144 */ 145#define STATE_CHANGED (0x100) /* -state option changed */ 146#define TEXTVAR_CHANGED (0x200) /* -textvariable option changed */ 147#define SCROLLCMD_CHANGED (0x400) /* -xscrollcommand option changed */ 148 149/* 150 * Default option values: 151 */ 152#define DEF_SELECT_BG "#000000" 153#define DEF_SELECT_FG "#ffffff" 154#define DEF_INSERT_BG "black" 155#define DEF_ENTRY_WIDTH "20" 156#define DEF_ENTRY_FONT "TkTextFont" 157#define DEF_LIST_HEIGHT "10" 158 159static Tk_OptionSpec EntryOptionSpecs[] = { 160 WIDGET_TAKES_FOCUS, 161 162 {TK_OPTION_BOOLEAN, "-exportselection", "exportSelection", 163 "ExportSelection", "1", -1, Tk_Offset(Entry, entry.exportSelection), 164 0,0,0 }, 165 {TK_OPTION_FONT, "-font", "font", "Font", 166 DEF_ENTRY_FONT, Tk_Offset(Entry, entry.fontObj),-1, 167 0,0,GEOMETRY_CHANGED}, 168 {TK_OPTION_STRING, "-invalidcommand", "invalidCommand", "InvalidCommand", 169 NULL, -1, Tk_Offset(Entry, entry.invalidCmd), 170 TK_OPTION_NULL_OK, 0, 0}, 171 {TK_OPTION_JUSTIFY, "-justify", "justify", "Justify", 172 "left", -1, Tk_Offset(Entry, entry.justify), 173 0, 0, GEOMETRY_CHANGED}, 174 {TK_OPTION_STRING, "-show", "show", "Show", 175 NULL, -1, Tk_Offset(Entry, entry.showChar), 176 TK_OPTION_NULL_OK, 0, 0}, 177 {TK_OPTION_STRING, "-state", "state", "State", 178 "normal", Tk_Offset(Entry, entry.stateObj), -1, 179 0,0,STATE_CHANGED}, 180 {TK_OPTION_STRING, "-textvariable", "textVariable", "Variable", 181 NULL, Tk_Offset(Entry, entry.textVariableObj), -1, 182 TK_OPTION_NULL_OK,0,TEXTVAR_CHANGED}, 183 {TK_OPTION_STRING_TABLE, "-validate", "validate", "Validate", 184 "none", -1, Tk_Offset(Entry, entry.validate), 185 0, (ClientData) validateStrings, 0}, 186 {TK_OPTION_STRING, "-validatecommand", "validateCommand", "ValidateCommand", 187 NULL, -1, Tk_Offset(Entry, entry.validateCmd), 188 TK_OPTION_NULL_OK, 0, 0}, 189 {TK_OPTION_INT, "-width", "width", "Width", 190 DEF_ENTRY_WIDTH, Tk_Offset(Entry, entry.widthObj), -1, 191 0,0,GEOMETRY_CHANGED}, 192 {TK_OPTION_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand", 193 NULL, -1, Tk_Offset(Entry, entry.xscroll.scrollCmd), 194 TK_OPTION_NULL_OK, 0, SCROLLCMD_CHANGED}, 195 196 /* EntryStyleData options: 197 */ 198 {TK_OPTION_COLOR, "-foreground", "textColor", "TextColor", 199 NULL, Tk_Offset(Entry, entry.styleData.foregroundObj), -1, 200 TK_OPTION_NULL_OK,0,0}, 201 {TK_OPTION_COLOR, "-background", "windowColor", "WindowColor", 202 NULL, Tk_Offset(Entry, entry.styleData.backgroundObj), -1, 203 TK_OPTION_NULL_OK,0,0}, 204 205 WIDGET_INHERIT_OPTIONS(ttkCoreOptionSpecs) 206}; 207 208/*------------------------------------------------------------------------ 209 * +++ EntryStyleData management. 210 * This is still more awkward than it should be; 211 * it should be able to use the Element API instead. 212 */ 213 214/* EntryInitStyleDefaults -- 215 * Initialize EntryStyleData record to fallback values. 216 */ 217static void EntryInitStyleDefaults(EntryStyleData *es) 218{ 219#define INIT(member, value) \ 220 es->member = Tcl_NewStringObj(value, -1); \ 221 Tcl_IncrRefCount(es->member); 222 INIT(foregroundObj, DEFAULT_FOREGROUND) 223 INIT(selBorderObj, DEF_SELECT_BG) 224 INIT(selForegroundObj, DEF_SELECT_FG) 225 INIT(insertColorObj, DEFAULT_FOREGROUND) 226 INIT(selBorderWidthObj, "0") 227 INIT(insertWidthObj, "1") 228#undef INIT 229} 230 231static void EntryFreeStyleDefaults(EntryStyleData *es) 232{ 233 Tcl_DecrRefCount(es->foregroundObj); 234 Tcl_DecrRefCount(es->selBorderObj); 235 Tcl_DecrRefCount(es->selForegroundObj); 236 Tcl_DecrRefCount(es->insertColorObj); 237 Tcl_DecrRefCount(es->selBorderWidthObj); 238 Tcl_DecrRefCount(es->insertWidthObj); 239} 240 241/* 242 * EntryInitStyleData -- 243 * Look up style-specific data for an entry widget. 244 */ 245static void EntryInitStyleData(Entry *entryPtr, EntryStyleData *es) 246{ 247 Ttk_State state = entryPtr->core.state; 248 Ttk_ResourceCache cache = Ttk_GetResourceCache(entryPtr->core.interp); 249 Tk_Window tkwin = entryPtr->core.tkwin; 250 Tcl_Obj *tmp; 251 252 /* Initialize to fallback values: 253 */ 254 *es = entryPtr->entry.styleDefaults; 255 256# define INIT(member, name) \ 257 if ((tmp=Ttk_QueryOption(entryPtr->core.layout,name,state))) \ 258 es->member=tmp; 259 INIT(foregroundObj, "-foreground"); 260 INIT(selBorderObj, "-selectbackground") 261 INIT(selBorderWidthObj, "-selectborderwidth") 262 INIT(selForegroundObj, "-selectforeground") 263 INIT(insertColorObj, "-insertcolor") 264 INIT(insertWidthObj, "-insertwidth") 265#undef INIT 266 267 /* Reacquire color & border resources from resource cache. 268 */ 269 es->foregroundObj = Ttk_UseColor(cache, tkwin, es->foregroundObj); 270 es->selForegroundObj = Ttk_UseColor(cache, tkwin, es->selForegroundObj); 271 es->insertColorObj = Ttk_UseColor(cache, tkwin, es->insertColorObj); 272 es->selBorderObj = Ttk_UseBorder(cache, tkwin, es->selBorderObj); 273} 274 275/*------------------------------------------------------------------------ 276 * +++ Resource management. 277 */ 278 279/* EntryDisplayString -- 280 * Return a malloc'ed string consisting of 'numChars' copies 281 * of (the first character in the string) 'showChar'. 282 * Used to compute the displayString if -show is non-NULL. 283 */ 284static char *EntryDisplayString(const char *showChar, int numChars) 285{ 286 char *displayString, *p; 287 int size; 288 Tcl_UniChar ch; 289 char buf[TCL_UTF_MAX]; 290 291 Tcl_UtfToUniChar(showChar, &ch); 292 size = Tcl_UniCharToUtf(ch, buf); 293 p = displayString = ckalloc(numChars * size + 1); 294 295 while (numChars--) { 296 p += Tcl_UniCharToUtf(ch, p); 297 } 298 *p = '\0'; 299 300 return displayString; 301} 302 303/* EntryUpdateTextLayout -- 304 * Recompute textLayout, layoutWidth, and layoutHeight 305 * from displayString and fontObj. 306 */ 307static void EntryUpdateTextLayout(Entry *entryPtr) 308{ 309 Tk_FreeTextLayout(entryPtr->entry.textLayout); 310 entryPtr->entry.textLayout = Tk_ComputeTextLayout( 311 Tk_GetFontFromObj(entryPtr->core.tkwin, entryPtr->entry.fontObj), 312 entryPtr->entry.displayString, entryPtr->entry.numChars, 313 0/*wraplength*/, entryPtr->entry.justify, TK_IGNORE_NEWLINES, 314 &entryPtr->entry.layoutWidth, &entryPtr->entry.layoutHeight); 315} 316 317/* EntryEditable -- 318 * Returns 1 if the entry widget accepts user changes, 0 otherwise 319 */ 320static int 321EntryEditable(Entry *entryPtr) 322{ 323 return !(entryPtr->core.state & (TTK_STATE_DISABLED|TTK_STATE_READONLY)); 324} 325 326/*------------------------------------------------------------------------ 327 * +++ Selection management. 328 */ 329 330/* EntryFetchSelection -- 331 * Selection handler for entry widgets. 332 */ 333static int 334EntryFetchSelection( 335 ClientData clientData, int offset, char *buffer, int maxBytes) 336{ 337 Entry *entryPtr = (Entry *) clientData; 338 size_t byteCount; 339 const char *string; 340 const char *selStart, *selEnd; 341 342 if (entryPtr->entry.selectFirst < 0 || !entryPtr->entry.exportSelection) { 343 return -1; 344 } 345 string = entryPtr->entry.displayString; 346 347 selStart = Tcl_UtfAtIndex(string, entryPtr->entry.selectFirst); 348 selEnd = Tcl_UtfAtIndex(selStart, 349 entryPtr->entry.selectLast - entryPtr->entry.selectFirst); 350 byteCount = selEnd - selStart - offset; 351 if (byteCount > (size_t)maxBytes) { 352 /* @@@POSSIBLE BUG: Can transfer partial UTF-8 sequences. Is this OK? */ 353 byteCount = maxBytes; 354 } 355 if (byteCount <= 0) { 356 return 0; 357 } 358 memcpy(buffer, selStart + offset, byteCount); 359 buffer[byteCount] = '\0'; 360 return byteCount; 361} 362 363/* EntryLostSelection -- 364 * Tk_LostSelProc for Entry widgets; called when an entry 365 * loses ownership of the selection. 366 */ 367static void EntryLostSelection(ClientData clientData) 368{ 369 Entry *entryPtr = (Entry *) clientData; 370 entryPtr->core.flags &= ~GOT_SELECTION; 371 entryPtr->entry.selectFirst = entryPtr->entry.selectLast = -1; 372 TtkRedisplayWidget(&entryPtr->core); 373} 374 375/* EntryOwnSelection -- 376 * Assert ownership of the PRIMARY selection, 377 * if -exportselection set and selection is present. 378 */ 379static void EntryOwnSelection(Entry *entryPtr) 380{ 381 if (entryPtr->entry.exportSelection 382 && !(entryPtr->core.flags & GOT_SELECTION)) { 383 Tk_OwnSelection(entryPtr->core.tkwin, XA_PRIMARY, EntryLostSelection, 384 (ClientData) entryPtr); 385 entryPtr->core.flags |= GOT_SELECTION; 386 } 387} 388 389/*------------------------------------------------------------------------ 390 * +++ Validation. 391 */ 392 393/* ExpandPercents -- 394 * Expand an entry validation script template (-validatecommand 395 * or -invalidcommand). 396 */ 397static void 398ExpandPercents( 399 Entry *entryPtr, /* Entry that needs validation. */ 400 const char *template, /* Script template */ 401 const char *new, /* Potential new value of entry string */ 402 int index, /* index of insert/delete */ 403 int count, /* #changed characters */ 404 VREASON reason, /* Reason for change */ 405 Tcl_DString *dsPtr) /* Result of %-substitutions */ 406{ 407 int spaceNeeded, cvtFlags; 408 int number, length; 409 const char *string; 410 int stringLength; 411 Tcl_UniChar ch; 412 char numStorage[2*TCL_INTEGER_SPACE]; 413 414 while (*template) { 415 /* Find everything up to the next % character and append it 416 * to the result string. 417 */ 418 string = Tcl_UtfFindFirst(template, '%'); 419 if (string == NULL) { 420 /* No more %-sequences to expand. 421 * Copy the rest of the template. 422 */ 423 Tcl_DStringAppend(dsPtr, template, -1); 424 return; 425 } 426 if (string != template) { 427 Tcl_DStringAppend(dsPtr, template, string - template); 428 template = string; 429 } 430 431 /* There's a percent sequence here. Process it. 432 */ 433 ++template; /* skip over % */ 434 if (*template != '\0') { 435 template += Tcl_UtfToUniChar(template, &ch); 436 } else { 437 ch = '%'; 438 } 439 440 stringLength = -1; 441 switch (ch) { 442 case 'd': /* Type of call that caused validation */ 443 if (reason == VALIDATE_INSERT) { 444 number = 1; 445 } else if (reason == VALIDATE_DELETE) { 446 number = 0; 447 } else { 448 number = -1; 449 } 450 sprintf(numStorage, "%d", number); 451 string = numStorage; 452 break; 453 case 'i': /* index of insert/delete */ 454 sprintf(numStorage, "%d", index); 455 string = numStorage; 456 break; 457 case 'P': /* 'Peeked' new value of the string */ 458 string = new; 459 break; 460 case 's': /* Current string value */ 461 string = entryPtr->entry.string; 462 break; 463 case 'S': /* string to be inserted/deleted, if any */ 464 if (reason == VALIDATE_INSERT) { 465 string = Tcl_UtfAtIndex(new, index); 466 stringLength = Tcl_UtfAtIndex(string, count) - string; 467 } else if (reason == VALIDATE_DELETE) { 468 string = Tcl_UtfAtIndex(entryPtr->entry.string, index); 469 stringLength = Tcl_UtfAtIndex(string, count) - string; 470 } else { 471 string = ""; 472 stringLength = 0; 473 } 474 break; 475 case 'v': /* type of validation currently set */ 476 string = validateStrings[entryPtr->entry.validate]; 477 break; 478 case 'V': /* type of validation in effect */ 479 string = validateReasonStrings[reason]; 480 break; 481 case 'W': /* widget name */ 482 string = Tk_PathName(entryPtr->core.tkwin); 483 break; 484 default: 485 length = Tcl_UniCharToUtf(ch, numStorage); 486 numStorage[length] = '\0'; 487 string = numStorage; 488 break; 489 } 490 491 spaceNeeded = Tcl_ScanCountedElement(string, stringLength, &cvtFlags); 492 length = Tcl_DStringLength(dsPtr); 493 Tcl_DStringSetLength(dsPtr, length + spaceNeeded); 494 spaceNeeded = Tcl_ConvertCountedElement(string, stringLength, 495 Tcl_DStringValue(dsPtr) + length, 496 cvtFlags | TCL_DONT_USE_BRACES); 497 Tcl_DStringSetLength(dsPtr, length + spaceNeeded); 498 } 499} 500 501/* RunValidationScript -- 502 * Build and evaluate an entry validation script. 503 * If the script raises an error, disable validation 504 * by setting '-validate none' 505 */ 506static int RunValidationScript( 507 Tcl_Interp *interp, /* Interpreter to use */ 508 Entry *entryPtr, /* Entry being validated */ 509 const char *template, /* Script template */ 510 const char *optionName, /* "-validatecommand", "-invalidcommand" */ 511 const char *new, /* Potential new value of entry string */ 512 int index, /* index of insert/delete */ 513 int count, /* #changed characters */ 514 VREASON reason) /* Reason for change */ 515{ 516 Tcl_DString script; 517 int code; 518 519 Tcl_DStringInit(&script); 520 ExpandPercents(entryPtr, template, new, index, count, reason, &script); 521 code = Tcl_EvalEx(interp, 522 Tcl_DStringValue(&script), Tcl_DStringLength(&script), 523 TCL_EVAL_GLOBAL); 524 Tcl_DStringFree(&script); 525 if (WidgetDestroyed(&entryPtr->core)) 526 return TCL_ERROR; 527 528 if (code != TCL_OK && code != TCL_RETURN) { 529 Tcl_AddErrorInfo(interp, "\n\t(in "); 530 Tcl_AddErrorInfo(interp, optionName); 531 Tcl_AddErrorInfo(interp, " validation command executed by "); 532 Tcl_AddErrorInfo(interp, Tk_PathName(entryPtr->core.tkwin)); 533 Tcl_AddErrorInfo(interp, ")"); 534 entryPtr->entry.validate = VMODE_NONE; 535 return TCL_ERROR; 536 } 537 return TCL_OK; 538} 539 540/* EntryNeedsValidation -- 541 * Determine whether the specified VREASON should trigger validation 542 * in the current VMODE. 543 */ 544static int EntryNeedsValidation(VMODE vmode, VREASON reason) 545{ 546 return (reason == VALIDATE_FORCED) 547 || (vmode == VMODE_ALL) 548 || (reason == VALIDATE_FOCUSIN 549 && (vmode == VMODE_FOCUSIN || vmode == VMODE_FOCUS)) 550 || (reason == VALIDATE_FOCUSOUT 551 && (vmode == VMODE_FOCUSOUT || vmode == VMODE_FOCUS)) 552 || (reason == VALIDATE_INSERT && vmode == VMODE_KEY) 553 || (reason == VALIDATE_DELETE && vmode == VMODE_KEY) 554 ; 555} 556 557/* EntryValidateChange -- 558 * Validate a proposed change to the entry widget's value if required. 559 * Call the -invalidcommand if validation fails. 560 * 561 * Returns: 562 * TCL_OK if the change is accepted 563 * TCL_BREAK if the change is rejected 564 * TCL_ERROR if any errors occured 565 * 566 * The change will be rejected if -validatecommand returns 0, 567 * or if -validatecommand or -invalidcommand modifies the value. 568 */ 569static int 570EntryValidateChange( 571 Entry *entryPtr, /* Entry that needs validation. */ 572 const char *newValue, /* Potential new value of entry string */ 573 int index, /* index of insert/delete, -1 otherwise */ 574 int count, /* #changed characters */ 575 VREASON reason) /* Reason for change */ 576{ 577 Tcl_Interp *interp = entryPtr->core.interp; 578 VMODE vmode = entryPtr->entry.validate; 579 int code, change_ok; 580 581 if ( (entryPtr->entry.validateCmd == NULL) 582 || (entryPtr->core.flags & VALIDATING) 583 || !EntryNeedsValidation(vmode, reason) ) 584 { 585 return TCL_OK; 586 } 587 588 entryPtr->core.flags |= VALIDATING; 589 590 /* Run -validatecommand and check return value: 591 */ 592 code = RunValidationScript(interp, entryPtr, 593 entryPtr->entry.validateCmd, "-validatecommand", 594 newValue, index, count, reason); 595 if (code != TCL_OK) { 596 goto done; 597 } 598 599 code = Tcl_GetBooleanFromObj(interp,Tcl_GetObjResult(interp), &change_ok); 600 if (code != TCL_OK) { 601 entryPtr->entry.validate = VMODE_NONE; /* Disable validation */ 602 Tcl_AddErrorInfo(interp, 603 "\n(validation command did not return valid boolean)"); 604 goto done; 605 } 606 607 /* Run the -invalidcommand if validation failed: 608 */ 609 if (!change_ok && entryPtr->entry.invalidCmd != NULL) { 610 code = RunValidationScript(interp, entryPtr, 611 entryPtr->entry.invalidCmd, "-invalidcommand", 612 newValue, index, count, reason); 613 if (code != TCL_OK) { 614 goto done; 615 } 616 } 617 618 /* Reject the pending change if validation failed 619 * or if a validation script changed the value. 620 */ 621 if (!change_ok || (entryPtr->core.flags & VALIDATION_SET_VALUE)) { 622 code = TCL_BREAK; 623 } 624 625done: 626 entryPtr->core.flags &= ~(VALIDATING|VALIDATION_SET_VALUE); 627 return code; 628} 629 630/* EntryRevalidate -- 631 * Revalidate the current value of an entry widget, 632 * update the TTK_STATE_INVALID bit. 633 * 634 * Returns: 635 * TCL_OK if valid, TCL_BREAK if invalid, TCL_ERROR on error. 636 */ 637static int EntryRevalidate(Tcl_Interp *interp, Entry *entryPtr, VREASON reason) 638{ 639 int code = EntryValidateChange( 640 entryPtr, entryPtr->entry.string, -1,0, reason); 641 642 if (code == TCL_BREAK) { 643 TtkWidgetChangeState(&entryPtr->core, TTK_STATE_INVALID, 0); 644 } else if (code == TCL_OK) { 645 TtkWidgetChangeState(&entryPtr->core, 0, TTK_STATE_INVALID); 646 } 647 648 return code; 649} 650 651/* EntryRevalidateBG -- 652 * Revalidate in the background (called from event handler). 653 */ 654static void EntryRevalidateBG(Entry *entryPtr, VREASON reason) 655{ 656 Tcl_Interp *interp = entryPtr->core.interp; 657 if (EntryRevalidate(interp, entryPtr, reason) == TCL_ERROR) { 658 Tcl_BackgroundError(interp); 659 } 660} 661 662/*------------------------------------------------------------------------ 663 * +++ Entry widget modification. 664 */ 665 666/* AdjustIndex -- 667 * Adjust index to account for insertion (nChars > 0) 668 * or deletion (nChars < 0) at specified index. 669 */ 670static int AdjustIndex(int i0, int index, int nChars) 671{ 672 if (i0 >= index) { 673 i0 += nChars; 674 if (i0 < index) { /* index was inside deleted range */ 675 i0 = index; 676 } 677 } 678 return i0; 679} 680 681/* AdjustIndices -- 682 * Adjust all internal entry indexes to account for change. 683 * Note that insertPos, and selectFirst have "right gravity", 684 * while leftIndex (=xscroll.first) and selectLast have "left gravity". 685 */ 686static void AdjustIndices(Entry *entryPtr, int index, int nChars) 687{ 688 EntryPart *e = &entryPtr->entry; 689 int g = nChars > 0; /* left gravity adjustment */ 690 691 e->insertPos = AdjustIndex(e->insertPos, index, nChars); 692 e->selectFirst = AdjustIndex(e->selectFirst, index, nChars); 693 e->selectLast = AdjustIndex(e->selectLast, index+g, nChars); 694 e->xscroll.first= AdjustIndex(e->xscroll.first, index+g, nChars); 695 696 if (e->selectLast <= e->selectFirst) 697 e->selectFirst = e->selectLast = -1; 698} 699 700/* EntryStoreValue -- 701 * Replace the contents of a text entry with a given value, 702 * recompute dependent resources, and schedule a redisplay. 703 * 704 * See also: EntrySetValue(). 705 */ 706static void 707EntryStoreValue(Entry *entryPtr, const char *value) 708{ 709 size_t numBytes = strlen(value); 710 int numChars = Tcl_NumUtfChars(value, numBytes); 711 712 if (entryPtr->core.flags & VALIDATING) 713 entryPtr->core.flags |= VALIDATION_SET_VALUE; 714 715 /* Make sure all indices remain in bounds: 716 */ 717 if (numChars < entryPtr->entry.numChars) 718 AdjustIndices(entryPtr, numChars, numChars - entryPtr->entry.numChars); 719 720 /* Free old value: 721 */ 722 if (entryPtr->entry.displayString != entryPtr->entry.string) 723 ckfree(entryPtr->entry.displayString); 724 ckfree(entryPtr->entry.string); 725 726 /* Store new value: 727 */ 728 entryPtr->entry.string = ckalloc(numBytes + 1); 729 strcpy(entryPtr->entry.string, value); 730 entryPtr->entry.numBytes = numBytes; 731 entryPtr->entry.numChars = numChars; 732 733 entryPtr->entry.displayString 734 = entryPtr->entry.showChar 735 ? EntryDisplayString(entryPtr->entry.showChar, numChars) 736 : entryPtr->entry.string 737 ; 738 739 /* Update layout, schedule redisplay: 740 */ 741 EntryUpdateTextLayout(entryPtr); 742 TtkRedisplayWidget(&entryPtr->core); 743} 744 745/* EntrySetValue -- 746 * Stores a new value in the entry widget and updates the 747 * linked -textvariable, if any. The write trace on the 748 * text variable is temporarily disabled; however, other 749 * write traces may change the value of the variable. 750 * If so, the widget is updated again with the new value. 751 * 752 * Returns: 753 * TCL_OK if successful, TCL_ERROR otherwise. 754 */ 755static int EntrySetValue(Entry *entryPtr, const char *value) 756{ 757 EntryStoreValue(entryPtr, value); 758 759 if (entryPtr->entry.textVariableObj) { 760 const char *textVarName = 761 Tcl_GetString(entryPtr->entry.textVariableObj); 762 if (textVarName && *textVarName) { 763 entryPtr->core.flags |= SYNCING_VARIABLE; 764 value = Tcl_SetVar(entryPtr->core.interp, textVarName, 765 value, TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG); 766 entryPtr->core.flags &= ~SYNCING_VARIABLE; 767 if (!value || WidgetDestroyed(&entryPtr->core)) { 768 return TCL_ERROR; 769 } else if (strcmp(value, entryPtr->entry.string) != 0) { 770 /* Some write trace has changed the variable value. 771 */ 772 EntryStoreValue(entryPtr, value); 773 } 774 } 775 } 776 777 return TCL_OK; 778} 779 780/* EntryTextVariableTrace -- 781 * Variable trace procedure for entry -textvariable 782 */ 783static void EntryTextVariableTrace(void *recordPtr, const char *value) 784{ 785 Entry *entryPtr = recordPtr; 786 787 if (WidgetDestroyed(&entryPtr->core)) { 788 return; 789 } 790 791 if (entryPtr->core.flags & SYNCING_VARIABLE) { 792 /* Trace was fired due to Tcl_SetVar call in EntrySetValue. 793 * Don't do anything. 794 */ 795 return; 796 } 797 798 EntryStoreValue(entryPtr, value ? value : ""); 799} 800 801/*------------------------------------------------------------------------ 802 * +++ Insertion and deletion. 803 */ 804 805/* InsertChars -- 806 * Add new characters to an entry widget. 807 */ 808static int 809InsertChars( 810 Entry *entryPtr, /* Entry that is to get the new elements. */ 811 int index, /* Insert before this index */ 812 const char *value) /* New characters to add */ 813{ 814 char *string = entryPtr->entry.string; 815 size_t byteIndex = Tcl_UtfAtIndex(string, index) - string; 816 size_t byteCount = strlen(value); 817 int charsAdded = Tcl_NumUtfChars(value, byteCount); 818 size_t newByteCount = entryPtr->entry.numBytes + byteCount + 1; 819 char *new; 820 int code; 821 822 if (byteCount == 0) { 823 return TCL_OK; 824 } 825 826 new = ckalloc(newByteCount); 827 memcpy(new, string, byteIndex); 828 strcpy(new + byteIndex, value); 829 strcpy(new + byteIndex + byteCount, string + byteIndex); 830 831 code = EntryValidateChange( 832 entryPtr, new, index, charsAdded, VALIDATE_INSERT); 833 834 if (code == TCL_OK) { 835 AdjustIndices(entryPtr, index, charsAdded); 836 code = EntrySetValue(entryPtr, new); 837 } else if (code == TCL_BREAK) { 838 code = TCL_OK; 839 } 840 841 ckfree(new); 842 return code; 843} 844 845/* DeleteChars -- 846 * Remove one or more characters from an entry widget. 847 */ 848static int 849DeleteChars( 850 Entry *entryPtr, /* Entry widget to modify. */ 851 int index, /* Index of first character to delete. */ 852 int count) /* How many characters to delete. */ 853{ 854 char *string = entryPtr->entry.string; 855 size_t byteIndex, byteCount, newByteCount; 856 char *new; 857 int code; 858 859 if (index < 0) { 860 index = 0; 861 } 862 if (count > entryPtr->entry.numChars - index) { 863 count = entryPtr->entry.numChars - index; 864 } 865 if (count <= 0) { 866 return TCL_OK; 867 } 868 869 byteIndex = Tcl_UtfAtIndex(string, index) - string; 870 byteCount = Tcl_UtfAtIndex(string+byteIndex, count) - (string+byteIndex); 871 872 newByteCount = entryPtr->entry.numBytes + 1 - byteCount; 873 new = ckalloc(newByteCount); 874 memcpy(new, string, byteIndex); 875 strcpy(new + byteIndex, string + byteIndex + byteCount); 876 877 code = EntryValidateChange( 878 entryPtr, new, index, count, VALIDATE_DELETE); 879 880 if (code == TCL_OK) { 881 AdjustIndices(entryPtr, index, -count); 882 code = EntrySetValue(entryPtr, new); 883 } else if (code == TCL_BREAK) { 884 code = TCL_OK; 885 } 886 ckfree(new); 887 888 return code; 889} 890 891/*------------------------------------------------------------------------ 892 * +++ Event handler. 893 */ 894 895/* EntryEventProc -- 896 * Extra event handling for entry widgets: 897 * Triggers validation on FocusIn and FocusOut events. 898 */ 899#define EntryEventMask (FocusChangeMask) 900static void 901EntryEventProc(ClientData clientData, XEvent *eventPtr) 902{ 903 Entry *entryPtr = (Entry *) clientData; 904 905 Tcl_Preserve(clientData); 906 switch (eventPtr->type) { 907 case DestroyNotify: 908 Tk_DeleteEventHandler(entryPtr->core.tkwin, 909 EntryEventMask, EntryEventProc, clientData); 910 break; 911 case FocusIn: 912 EntryRevalidateBG(entryPtr, VALIDATE_FOCUSIN); 913 break; 914 case FocusOut: 915 EntryRevalidateBG(entryPtr, VALIDATE_FOCUSOUT); 916 break; 917 } 918 Tcl_Release(clientData); 919} 920 921/*------------------------------------------------------------------------ 922 * +++ Initialization and cleanup. 923 */ 924 925static void 926EntryInitialize(Tcl_Interp *interp, void *recordPtr) 927{ 928 Entry *entryPtr = recordPtr; 929 930 Tk_CreateEventHandler( 931 entryPtr->core.tkwin, EntryEventMask, EntryEventProc, entryPtr); 932 Tk_CreateSelHandler(entryPtr->core.tkwin, XA_PRIMARY, XA_STRING, 933 EntryFetchSelection, (ClientData) entryPtr, XA_STRING); 934 TtkBlinkCursor(&entryPtr->core); 935 936 entryPtr->entry.string = ckalloc(1); 937 *entryPtr->entry.string = '\0'; 938 entryPtr->entry.displayString = entryPtr->entry.string; 939 entryPtr->entry.textVariableTrace = 0; 940 entryPtr->entry.numBytes = entryPtr->entry.numChars = 0; 941 942 EntryInitStyleDefaults(&entryPtr->entry.styleDefaults); 943 944 entryPtr->entry.xscrollHandle = 945 TtkCreateScrollHandle(&entryPtr->core, &entryPtr->entry.xscroll); 946 947 entryPtr->entry.insertPos = 0; 948 entryPtr->entry.selectFirst = -1; 949 entryPtr->entry.selectLast = -1; 950} 951 952static void 953EntryCleanup(void *recordPtr) 954{ 955 Entry *entryPtr = recordPtr; 956 957 if (entryPtr->entry.textVariableTrace) 958 Ttk_UntraceVariable(entryPtr->entry.textVariableTrace); 959 960 TtkFreeScrollHandle(entryPtr->entry.xscrollHandle); 961 962 EntryFreeStyleDefaults(&entryPtr->entry.styleDefaults); 963 964 Tk_DeleteSelHandler(entryPtr->core.tkwin, XA_PRIMARY, XA_STRING); 965 966 Tk_FreeTextLayout(entryPtr->entry.textLayout); 967 if (entryPtr->entry.displayString != entryPtr->entry.string) 968 ckfree(entryPtr->entry.displayString); 969 ckfree(entryPtr->entry.string); 970} 971 972/* EntryConfigure -- 973 * Configure hook for Entry widgets. 974 */ 975static int EntryConfigure(Tcl_Interp *interp, void *recordPtr, int mask) 976{ 977 Entry *entryPtr = recordPtr; 978 Tcl_Obj *textVarName = entryPtr->entry.textVariableObj; 979 Ttk_TraceHandle *vt = 0; 980 981 if (mask & TEXTVAR_CHANGED) { 982 if (textVarName && *Tcl_GetString(textVarName)) { 983 vt = Ttk_TraceVariable(interp, 984 textVarName,EntryTextVariableTrace,entryPtr); 985 if (!vt) return TCL_ERROR; 986 } 987 } 988 989 if (TtkCoreConfigure(interp, recordPtr, mask) != TCL_OK) { 990 if (vt) Ttk_UntraceVariable(vt); 991 return TCL_ERROR; 992 } 993 994 /* Update derived resources: 995 */ 996 if (mask & TEXTVAR_CHANGED) { 997 if (entryPtr->entry.textVariableTrace) 998 Ttk_UntraceVariable(entryPtr->entry.textVariableTrace); 999 entryPtr->entry.textVariableTrace = vt; 1000 } 1001 1002 /* Claim the selection, in case we've suddenly started exporting it. 1003 */ 1004 if (entryPtr->entry.exportSelection && entryPtr->entry.selectFirst != -1) { 1005 EntryOwnSelection(entryPtr); 1006 } 1007 1008 /* Handle -state compatibility option: 1009 */ 1010 if (mask & STATE_CHANGED) { 1011 TtkCheckStateOption(&entryPtr->core, entryPtr->entry.stateObj); 1012 } 1013 1014 /* Force scrollbar update if needed: 1015 */ 1016 if (mask & SCROLLCMD_CHANGED) { 1017 TtkScrollbarUpdateRequired(entryPtr->entry.xscrollHandle); 1018 } 1019 1020 /* Recompute the displayString, in case showChar changed: 1021 */ 1022 if (entryPtr->entry.displayString != entryPtr->entry.string) 1023 ckfree(entryPtr->entry.displayString); 1024 1025 entryPtr->entry.displayString 1026 = entryPtr->entry.showChar 1027 ? EntryDisplayString(entryPtr->entry.showChar, entryPtr->entry.numChars) 1028 : entryPtr->entry.string 1029 ; 1030 1031 /* Update textLayout: 1032 */ 1033 EntryUpdateTextLayout(entryPtr); 1034 return TCL_OK; 1035} 1036 1037/* EntryPostConfigure -- 1038 * Post-configuration hook for entry widgets. 1039 */ 1040static int EntryPostConfigure(Tcl_Interp *interp, void *recordPtr, int mask) 1041{ 1042 Entry *entryPtr = recordPtr; 1043 int status = TCL_OK; 1044 1045 if ((mask & TEXTVAR_CHANGED) && entryPtr->entry.textVariableTrace != NULL) { 1046 status = Ttk_FireTrace(entryPtr->entry.textVariableTrace); 1047 } 1048 1049 return status; 1050} 1051 1052/*------------------------------------------------------------------------ 1053 * +++ Layout and display. 1054 */ 1055 1056/* EntryCharPosition -- 1057 * Return the X coordinate of the specified character index. 1058 * Precondition: textLayout and layoutX up-to-date. 1059 */ 1060static int 1061EntryCharPosition(Entry *entryPtr, int index) 1062{ 1063 int xPos; 1064 Tk_CharBbox(entryPtr->entry.textLayout, index, &xPos, NULL, NULL, NULL); 1065 return xPos + entryPtr->entry.layoutX; 1066} 1067 1068/* EntryDoLayout -- 1069 * Layout hook for entry widgets. 1070 * 1071 * Determine position of textLayout based on xscroll.first, justify, 1072 * and display area. 1073 * 1074 * Recalculates layoutX, layoutY, and rightIndex, 1075 * and updates xscroll accordingly. 1076 * May adjust xscroll.first to ensure the maximum #characters are onscreen. 1077 */ 1078static void 1079EntryDoLayout(void *recordPtr) 1080{ 1081 Entry *entryPtr = recordPtr; 1082 WidgetCore *corePtr = &entryPtr->core; 1083 Tk_TextLayout textLayout = entryPtr->entry.textLayout; 1084 int leftIndex = entryPtr->entry.xscroll.first; 1085 int rightIndex; 1086 Ttk_Box textarea; 1087 1088 Ttk_PlaceLayout(corePtr->layout,corePtr->state,Ttk_WinBox(corePtr->tkwin)); 1089 textarea = Ttk_ClientRegion(corePtr->layout, "textarea"); 1090 1091 /* Center the text vertically within the available parcel: 1092 */ 1093 entryPtr->entry.layoutY = textarea.y + 1094 (textarea.height - entryPtr->entry.layoutHeight)/2; 1095 1096 /* Recompute where the leftmost character on the display will 1097 * be drawn (layoutX) and adjust leftIndex if necessary. 1098 */ 1099 if (entryPtr->entry.layoutWidth <= textarea.width) { 1100 /* Everything fits. Set leftIndex to zero (no need to scroll), 1101 * and compute layoutX based on -justify. 1102 */ 1103 int extraSpace = textarea.width - entryPtr->entry.layoutWidth; 1104 leftIndex = 0; 1105 rightIndex = entryPtr->entry.numChars; 1106 entryPtr->entry.layoutX = textarea.x; 1107 if (entryPtr->entry.justify == TK_JUSTIFY_RIGHT) { 1108 entryPtr->entry.layoutX += extraSpace; 1109 } else if (entryPtr->entry.justify == TK_JUSTIFY_CENTER) { 1110 entryPtr->entry.layoutX += extraSpace / 2; 1111 } 1112 } else { 1113 /* The whole string doesn't fit in the window. 1114 * Limit leftIndex to leave at most one character's worth 1115 * of empty space on the right. 1116 */ 1117 int overflow = entryPtr->entry.layoutWidth - textarea.width; 1118 int maxLeftIndex = 1 + Tk_PointToChar(textLayout, overflow, 0); 1119 int leftX; 1120 1121 if (leftIndex > maxLeftIndex) { 1122 leftIndex = maxLeftIndex; 1123 } 1124 1125 /* Compute layoutX and rightIndex. 1126 * rightIndex is set to one past the last fully-visible character. 1127 */ 1128 Tk_CharBbox(textLayout, leftIndex, &leftX, NULL, NULL, NULL); 1129 rightIndex = Tk_PointToChar(textLayout, leftX + textarea.width, 0); 1130 entryPtr->entry.layoutX = textarea.x - leftX; 1131 } 1132 1133 TtkScrolled(entryPtr->entry.xscrollHandle, 1134 leftIndex, rightIndex, entryPtr->entry.numChars); 1135} 1136 1137/* EntryGetGC -- Helper routine. 1138 * Get a GC using the specified foreground color and the entry's font. 1139 * Result must be freed with Tk_FreeGC(). 1140 */ 1141static GC EntryGetGC(Entry *entryPtr, Tcl_Obj *colorObj) 1142{ 1143 Tk_Window tkwin = entryPtr->core.tkwin; 1144 Tk_Font font = Tk_GetFontFromObj(tkwin, entryPtr->entry.fontObj); 1145 XColor *colorPtr; 1146 unsigned long mask = 0ul; 1147 XGCValues gcValues; 1148 1149 gcValues.line_width = 1; mask |= GCLineWidth; 1150 gcValues.font = Tk_FontId(font); mask |= GCFont; 1151 if (colorObj != 0 && (colorPtr=Tk_GetColorFromObj(tkwin,colorObj)) != 0) { 1152 gcValues.foreground = colorPtr->pixel; 1153 mask |= GCForeground; 1154 } 1155 return Tk_GetGC(entryPtr->core.tkwin, mask, &gcValues); 1156} 1157 1158/* EntryDisplay -- 1159 * Redraws the contents of an entry window. 1160 */ 1161static void EntryDisplay(void *clientData, Drawable d) 1162{ 1163 Entry *entryPtr = clientData; 1164 Tk_Window tkwin = entryPtr->core.tkwin; 1165 int leftIndex = entryPtr->entry.xscroll.first, 1166 rightIndex = entryPtr->entry.xscroll.last, 1167 selFirst = entryPtr->entry.selectFirst, 1168 selLast = entryPtr->entry.selectLast; 1169 EntryStyleData es; 1170 GC gc; 1171 int showSelection, showCursor; 1172 1173 EntryInitStyleData(entryPtr, &es); 1174 1175 showCursor = 1176 (entryPtr->core.flags & CURSOR_ON) != 0 1177 && EntryEditable(entryPtr) 1178 && entryPtr->entry.insertPos >= leftIndex 1179 && entryPtr->entry.insertPos <= rightIndex 1180 ; 1181 showSelection = 1182 (entryPtr->core.state & TTK_STATE_DISABLED) == 0 1183 && selFirst > -1 1184 && selLast > leftIndex 1185 && selFirst <= rightIndex 1186 ; 1187 1188 /* Adjust selection range to keep in display bounds. 1189 */ 1190 if (showSelection) { 1191 if (selFirst < leftIndex) 1192 selFirst = leftIndex; 1193 if (selLast > rightIndex) 1194 selLast = rightIndex; 1195 } 1196 1197 /* Draw widget background & border 1198 */ 1199 Ttk_DrawLayout(entryPtr->core.layout, entryPtr->core.state, d); 1200 1201 /* Draw selection background 1202 */ 1203 if (showSelection && es.selBorderObj) { 1204 Tk_3DBorder selBorder = Tk_Get3DBorderFromObj(tkwin, es.selBorderObj); 1205 int selStartX = EntryCharPosition(entryPtr, selFirst); 1206 int selEndX = EntryCharPosition(entryPtr, selLast); 1207 int borderWidth = 1; 1208 1209 Tcl_GetIntFromObj(NULL, es.selBorderWidthObj, &borderWidth); 1210 1211 if (selBorder) { 1212 Tk_Fill3DRectangle(tkwin, d, selBorder, 1213 selStartX - borderWidth, entryPtr->entry.layoutY - borderWidth, 1214 selEndX - selStartX + 2*borderWidth, 1215 entryPtr->entry.layoutHeight + 2*borderWidth, 1216 borderWidth, TK_RELIEF_RAISED); 1217 } 1218 } 1219 1220 /* Draw cursor: 1221 */ 1222 if (showCursor) { 1223 int cursorX = EntryCharPosition(entryPtr, entryPtr->entry.insertPos), 1224 cursorY = entryPtr->entry.layoutY, 1225 cursorHeight = entryPtr->entry.layoutHeight, 1226 cursorWidth = 1; 1227 1228 Tcl_GetIntFromObj(NULL,es.insertWidthObj,&cursorWidth); 1229 if (cursorWidth <= 0) { 1230 cursorWidth = 1; 1231 } 1232 1233 /* @@@ should: maybe: SetCaretPos even when blinked off */ 1234 Tk_SetCaretPos(tkwin, cursorX, cursorY, cursorHeight); 1235 1236 gc = EntryGetGC(entryPtr, es.insertColorObj); 1237 XFillRectangle(Tk_Display(tkwin), d, gc, 1238 cursorX-cursorWidth/2, cursorY, cursorWidth, cursorHeight); 1239 Tk_FreeGC(Tk_Display(tkwin), gc); 1240 } 1241 1242 /* Draw the text: 1243 */ 1244 gc = EntryGetGC(entryPtr, es.foregroundObj); 1245 Tk_DrawTextLayout( 1246 Tk_Display(tkwin), d, gc, entryPtr->entry.textLayout, 1247 entryPtr->entry.layoutX, entryPtr->entry.layoutY, 1248 leftIndex, rightIndex); 1249 Tk_FreeGC(Tk_Display(tkwin), gc); 1250 1251 /* Overwrite the selected portion (if any) in the -selectforeground color: 1252 */ 1253 if (showSelection) { 1254 gc = EntryGetGC(entryPtr, es.selForegroundObj); 1255 Tk_DrawTextLayout( 1256 Tk_Display(tkwin), d, gc, entryPtr->entry.textLayout, 1257 entryPtr->entry.layoutX, entryPtr->entry.layoutY, 1258 selFirst, selLast); 1259 Tk_FreeGC(Tk_Display(tkwin), gc); 1260 } 1261} 1262 1263/*------------------------------------------------------------------------ 1264 * +++ Widget commands. 1265 */ 1266 1267/* EntryIndex -- 1268 * Parse an index into an entry and return either its value 1269 * or an error. 1270 * 1271 * Results: 1272 * A standard Tcl result. If all went well, then *indexPtr is 1273 * filled in with the character index (into entryPtr) corresponding to 1274 * string. The index value is guaranteed to lie between 0 and 1275 * the number of characters in the string, inclusive. If an 1276 * error occurs then an error message is left in the interp's result. 1277 */ 1278static int 1279EntryIndex( 1280 Tcl_Interp *interp, /* For error messages. */ 1281 Entry *entryPtr, /* Entry widget to query */ 1282 Tcl_Obj *indexObj, /* Symbolic index name */ 1283 int *indexPtr) /* Return value */ 1284{ 1285# define EntryWidth(e) (Tk_Width(entryPtr->core.tkwin)) /* Not Right */ 1286 int length; 1287 const char *string = Tcl_GetStringFromObj(indexObj, &length); 1288 1289 if (strncmp(string, "end", length) == 0) { 1290 *indexPtr = entryPtr->entry.numChars; 1291 } else if (strncmp(string, "insert", length) == 0) { 1292 *indexPtr = entryPtr->entry.insertPos; 1293 } else if (strncmp(string, "left", length) == 0) { /* for debugging */ 1294 *indexPtr = entryPtr->entry.xscroll.first; 1295 } else if (strncmp(string, "right", length) == 0) { /* for debugging */ 1296 *indexPtr = entryPtr->entry.xscroll.last; 1297 } else if (strncmp(string, "sel.", 4) == 0) { 1298 if (entryPtr->entry.selectFirst < 0) { 1299 Tcl_ResetResult(interp); 1300 Tcl_AppendResult(interp, "selection isn't in widget ", 1301 Tk_PathName(entryPtr->core.tkwin), NULL); 1302 return TCL_ERROR; 1303 } 1304 if (strncmp(string, "sel.first", length) == 0) { 1305 *indexPtr = entryPtr->entry.selectFirst; 1306 } else if (strncmp(string, "sel.last", length) == 0) { 1307 *indexPtr = entryPtr->entry.selectLast; 1308 } else { 1309 goto badIndex; 1310 } 1311 } else if (string[0] == '@') { 1312 int roundUp = 0; 1313 int maxWidth = EntryWidth(entryPtr); 1314 int x; 1315 1316 if (Tcl_GetInt(interp, string + 1, &x) != TCL_OK) { 1317 goto badIndex; 1318 } 1319 if (x > maxWidth) { 1320 x = maxWidth; 1321 roundUp = 1; 1322 } 1323 *indexPtr = Tk_PointToChar(entryPtr->entry.textLayout, 1324 x - entryPtr->entry.layoutX, 0); 1325 1326 if (*indexPtr < entryPtr->entry.xscroll.first) { 1327 *indexPtr = entryPtr->entry.xscroll.first; 1328 } 1329 1330 /* 1331 * Special trick: if the x-position was off-screen to the right, 1332 * round the index up to refer to the character just after the 1333 * last visible one on the screen. This is needed to enable the 1334 * last character to be selected, for example. 1335 */ 1336 1337 if (roundUp && (*indexPtr < entryPtr->entry.numChars)) { 1338 *indexPtr += 1; 1339 } 1340 } else { 1341 if (Tcl_GetInt(interp, string, indexPtr) != TCL_OK) { 1342 goto badIndex; 1343 } 1344 if (*indexPtr < 0) { 1345 *indexPtr = 0; 1346 } else if (*indexPtr > entryPtr->entry.numChars) { 1347 *indexPtr = entryPtr->entry.numChars; 1348 } 1349 } 1350 return TCL_OK; 1351 1352badIndex: 1353 Tcl_ResetResult(interp); 1354 Tcl_AppendResult(interp, "bad entry index \"", string, "\"", NULL); 1355 return TCL_ERROR; 1356} 1357 1358/* $entry bbox $index -- 1359 * Return the bounding box of the character at the specified index. 1360 */ 1361static int 1362EntryBBoxCommand( 1363 void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) 1364{ 1365 Entry *entryPtr = recordPtr; 1366 Ttk_Box b; 1367 int index; 1368 1369 if (objc != 3) { 1370 Tcl_WrongNumArgs(interp, 2, objv, "index"); 1371 return TCL_ERROR; 1372 } 1373 if (EntryIndex(interp, entryPtr, objv[2], &index) != TCL_OK) { 1374 return TCL_ERROR; 1375 } 1376 if ((index == entryPtr->entry.numChars) && (index > 0)) { 1377 index--; 1378 } 1379 Tk_CharBbox(entryPtr->entry.textLayout, index, 1380 &b.x, &b.y, &b.width, &b.height); 1381 b.x += entryPtr->entry.layoutX; 1382 b.y += entryPtr->entry.layoutY; 1383 Tcl_SetObjResult(interp, Ttk_NewBoxObj(b)); 1384 return TCL_OK; 1385} 1386 1387/* $entry delete $from ?$to? -- 1388 * Delete the characters in the range [$from,$to). 1389 * $to defaults to $from+1 if not specified. 1390 */ 1391static int 1392EntryDeleteCommand( 1393 void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) 1394{ 1395 Entry *entryPtr = recordPtr; 1396 int first, last; 1397 1398 if ((objc < 3) || (objc > 4)) { 1399 Tcl_WrongNumArgs(interp, 2, objv, "firstIndex ?lastIndex?"); 1400 return TCL_ERROR; 1401 } 1402 if (EntryIndex(interp, entryPtr, objv[2], &first) != TCL_OK) { 1403 return TCL_ERROR; 1404 } 1405 if (objc == 3) { 1406 last = first + 1; 1407 } else if (EntryIndex(interp, entryPtr, objv[3], &last) != TCL_OK) { 1408 return TCL_ERROR; 1409 } 1410 1411 if (last >= first && EntryEditable(entryPtr)) { 1412 return DeleteChars(entryPtr, first, last - first); 1413 } 1414 return TCL_OK; 1415} 1416 1417/* $entry get -- 1418 * Return the current value of the entry widget. 1419 */ 1420static int 1421EntryGetCommand( 1422 void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) 1423{ 1424 Entry *entryPtr = recordPtr; 1425 if (objc != 2) { 1426 Tcl_WrongNumArgs(interp, 2, objv, NULL); 1427 return TCL_ERROR; 1428 } 1429 Tcl_SetResult(interp, entryPtr->entry.string, TCL_VOLATILE); 1430 return TCL_OK; 1431} 1432 1433/* $entry icursor $index -- 1434 * Set the insert cursor position. 1435 */ 1436static int 1437EntryICursorCommand( 1438 void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) 1439{ 1440 Entry *entryPtr = recordPtr; 1441 if (objc != 3) { 1442 Tcl_WrongNumArgs(interp, 2, objv, "pos"); 1443 return TCL_ERROR; 1444 } 1445 if (EntryIndex(interp, entryPtr, objv[2], 1446 &entryPtr->entry.insertPos) != TCL_OK) { 1447 return TCL_ERROR; 1448 } 1449 TtkRedisplayWidget(&entryPtr->core); 1450 return TCL_OK; 1451} 1452 1453/* $entry index $index -- 1454 * Return numeric value (0..numChars) of the specified index. 1455 */ 1456static int 1457EntryIndexCommand( 1458 void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) 1459{ 1460 Entry *entryPtr = recordPtr; 1461 int index; 1462 1463 if (objc != 3) { 1464 Tcl_WrongNumArgs(interp, 2, objv, "string"); 1465 return TCL_ERROR; 1466 } 1467 if (EntryIndex(interp, entryPtr, objv[2], &index) != TCL_OK) { 1468 return TCL_ERROR; 1469 } 1470 Tcl_SetObjResult(interp, Tcl_NewIntObj(index)); 1471 return TCL_OK; 1472} 1473 1474/* $entry insert $index $text -- 1475 * Insert $text after position $index. 1476 * Silent no-op if the entry is disabled or read-only. 1477 */ 1478static int 1479EntryInsertCommand( 1480 void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) 1481{ 1482 Entry *entryPtr = recordPtr; 1483 int index; 1484 1485 if (objc != 4) { 1486 Tcl_WrongNumArgs(interp, 2, objv, "index text"); 1487 return TCL_ERROR; 1488 } 1489 if (EntryIndex(interp, entryPtr, objv[2], &index) != TCL_OK) { 1490 return TCL_ERROR; 1491 } 1492 if (EntryEditable(entryPtr)) { 1493 return InsertChars(entryPtr, index, Tcl_GetString(objv[3])); 1494 } 1495 return TCL_OK; 1496} 1497 1498/* $entry selection clear -- 1499 * Clear selection. 1500 */ 1501static int EntrySelectionClearCommand( 1502 void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) 1503{ 1504 Entry *entryPtr = recordPtr; 1505 1506 if (objc != 3) { 1507 Tcl_WrongNumArgs(interp, 3, objv, NULL); 1508 return TCL_ERROR; 1509 } 1510 entryPtr->entry.selectFirst = entryPtr->entry.selectLast = -1; 1511 TtkRedisplayWidget(&entryPtr->core); 1512 return TCL_OK; 1513} 1514 1515/* $entry selection present -- 1516 * Returns 1 if any characters are selected, 0 otherwise. 1517 */ 1518static int EntrySelectionPresentCommand( 1519 void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) 1520{ 1521 Entry *entryPtr = recordPtr; 1522 if (objc != 3) { 1523 Tcl_WrongNumArgs(interp, 3, objv, NULL); 1524 return TCL_ERROR; 1525 } 1526 Tcl_SetObjResult(interp, 1527 Tcl_NewBooleanObj(entryPtr->entry.selectFirst >= 0)); 1528 return TCL_OK; 1529} 1530 1531/* $entry selection range $start $end -- 1532 * Explicitly set the selection range. 1533 */ 1534static int EntrySelectionRangeCommand( 1535 void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) 1536{ 1537 Entry *entryPtr = recordPtr; 1538 int start, end; 1539 if (objc != 5) { 1540 Tcl_WrongNumArgs(interp, 3, objv, "start end"); 1541 return TCL_ERROR; 1542 } 1543 if ( EntryIndex(interp, entryPtr, objv[3], &start) != TCL_OK 1544 || EntryIndex(interp, entryPtr, objv[4], &end) != TCL_OK) { 1545 return TCL_ERROR; 1546 } 1547 if (entryPtr->core.state & TTK_STATE_DISABLED) { 1548 return TCL_OK; 1549 } 1550 1551 if (start >= end) { 1552 entryPtr->entry.selectFirst = entryPtr->entry.selectLast = -1; 1553 } else { 1554 entryPtr->entry.selectFirst = start; 1555 entryPtr->entry.selectLast = end; 1556 EntryOwnSelection(entryPtr); 1557 } 1558 TtkRedisplayWidget(&entryPtr->core); 1559 return TCL_OK; 1560} 1561 1562static const Ttk_Ensemble EntrySelectionCommands[] = { 1563 { "clear", EntrySelectionClearCommand,0 }, 1564 { "present", EntrySelectionPresentCommand,0 }, 1565 { "range", EntrySelectionRangeCommand,0 }, 1566 { 0,0,0 } 1567}; 1568 1569/* $entry set $value 1570 * Sets the value of an entry widget. 1571 */ 1572static int EntrySetCommand( 1573 void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) 1574{ 1575 Entry *entryPtr = recordPtr; 1576 if (objc != 3) { 1577 Tcl_WrongNumArgs(interp, 2, objv, "value"); 1578 return TCL_ERROR; 1579 } 1580 EntrySetValue(entryPtr, Tcl_GetString(objv[2])); 1581 return TCL_OK; 1582} 1583 1584/* $entry validate -- 1585 * Trigger forced validation. Returns 1/0 if validation succeeds/fails 1586 * or error status from -validatecommand / -invalidcommand. 1587 */ 1588static int EntryValidateCommand( 1589 void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) 1590{ 1591 Entry *entryPtr = recordPtr; 1592 int code; 1593 1594 if (objc != 2) { 1595 Tcl_WrongNumArgs(interp, 2, objv, NULL); 1596 return TCL_ERROR; 1597 } 1598 1599 code = EntryRevalidate(interp, entryPtr, VALIDATE_FORCED); 1600 1601 if (code == TCL_ERROR) 1602 return code; 1603 1604 Tcl_SetObjResult(interp, Tcl_NewBooleanObj(code == TCL_OK)); 1605 return TCL_OK; 1606} 1607 1608/* $entry xview -- horizontal scrolling interface 1609 */ 1610static int EntryXViewCommand( 1611 void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) 1612{ 1613 Entry *entryPtr = recordPtr; 1614 return TtkScrollviewCommand(interp, objc, objv, entryPtr->entry.xscrollHandle); 1615} 1616 1617static const Ttk_Ensemble EntryCommands[] = { 1618 { "bbox", EntryBBoxCommand,0 }, 1619 { "cget", TtkWidgetCgetCommand,0 }, 1620 { "configure", TtkWidgetConfigureCommand,0 }, 1621 { "delete", EntryDeleteCommand,0 }, 1622 { "get", EntryGetCommand,0 }, 1623 { "icursor", EntryICursorCommand,0 }, 1624 { "identify", TtkWidgetIdentifyCommand,0 }, 1625 { "index", EntryIndexCommand,0 }, 1626 { "insert", EntryInsertCommand,0 }, 1627 { "instate", TtkWidgetInstateCommand,0 }, 1628 { "selection", 0,EntrySelectionCommands }, 1629 { "state", TtkWidgetStateCommand,0 }, 1630 { "validate", EntryValidateCommand,0 }, 1631 { "xview", EntryXViewCommand,0 }, 1632 { 0,0,0 } 1633}; 1634 1635/*------------------------------------------------------------------------ 1636 * +++ Entry widget definition. 1637 */ 1638 1639static WidgetSpec EntryWidgetSpec = { 1640 "TEntry", /* className */ 1641 sizeof(Entry), /* recordSize */ 1642 EntryOptionSpecs, /* optionSpecs */ 1643 EntryCommands, /* subcommands */ 1644 EntryInitialize, /* initializeProc */ 1645 EntryCleanup, /* cleanupProc */ 1646 EntryConfigure, /* configureProc */ 1647 EntryPostConfigure, /* postConfigureProc */ 1648 TtkWidgetGetLayout, /* getLayoutProc */ 1649 TtkWidgetSize, /* sizeProc */ 1650 EntryDoLayout, /* layoutProc */ 1651 EntryDisplay /* displayProc */ 1652}; 1653 1654/*------------------------------------------------------------------------ 1655 * +++ Combobox widget record. 1656 */ 1657 1658typedef struct { 1659 Tcl_Obj *postCommandObj; 1660 Tcl_Obj *valuesObj; 1661 Tcl_Obj *heightObj; 1662 int currentIndex; 1663} ComboboxPart; 1664 1665typedef struct { 1666 WidgetCore core; 1667 EntryPart entry; 1668 ComboboxPart combobox; 1669} Combobox; 1670 1671static Tk_OptionSpec ComboboxOptionSpecs[] = { 1672 {TK_OPTION_STRING, "-height", "height", "Height", 1673 DEF_LIST_HEIGHT, Tk_Offset(Combobox, combobox.heightObj), -1, 1674 0,0,0 }, 1675 {TK_OPTION_STRING, "-postcommand", "postCommand", "PostCommand", 1676 "", Tk_Offset(Combobox, combobox.postCommandObj), -1, 1677 0,0,0 }, 1678 {TK_OPTION_STRING, "-values", "values", "Values", 1679 "", Tk_Offset(Combobox, combobox.valuesObj), -1, 1680 0,0,0 }, 1681 WIDGET_INHERIT_OPTIONS(EntryOptionSpecs) 1682}; 1683 1684/* ComboboxInitialize -- 1685 * Initialization hook for combobox widgets. 1686 */ 1687static void 1688ComboboxInitialize(Tcl_Interp *interp, void *recordPtr) 1689{ 1690 Combobox *cb = recordPtr; 1691 1692 cb->combobox.currentIndex = -1; 1693 TtkTrackElementState(&cb->core); 1694 EntryInitialize(interp, recordPtr); 1695} 1696 1697/* ComboboxConfigure -- 1698 * Configuration hook for combobox widgets. 1699 */ 1700static int 1701ComboboxConfigure(Tcl_Interp *interp, void *recordPtr, int mask) 1702{ 1703 Combobox *cbPtr = recordPtr; 1704 int unused; 1705 1706 /* Make sure -values is a valid list: 1707 */ 1708 if (Tcl_ListObjLength(interp,cbPtr->combobox.valuesObj,&unused) != TCL_OK) 1709 return TCL_ERROR; 1710 1711 return EntryConfigure(interp, recordPtr, mask); 1712} 1713 1714/* $cb current ?newIndex? -- get or set current index. 1715 * Setting the current index updates the combobox value, 1716 * but the value and -values may be changed independently 1717 * of the index. Instead of trying to keep currentIndex 1718 * in sync at all times, [$cb current] double-checks 1719 */ 1720static int ComboboxCurrentCommand( 1721 void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) 1722{ 1723 Combobox *cbPtr = recordPtr; 1724 int currentIndex = cbPtr->combobox.currentIndex; 1725 const char *currentValue = cbPtr->entry.string; 1726 int nValues; 1727 Tcl_Obj **values; 1728 1729 Tcl_ListObjGetElements(interp,cbPtr->combobox.valuesObj,&nValues,&values); 1730 1731 if (objc == 2) { 1732 /* Check if currentIndex still valid: 1733 */ 1734 if ( currentIndex < 0 1735 || currentIndex >= nValues 1736 || strcmp(currentValue,Tcl_GetString(values[currentIndex])) 1737 ) 1738 { 1739 /* Not valid. Check current value against each element in -values: 1740 */ 1741 for (currentIndex = 0; currentIndex < nValues; ++currentIndex) { 1742 if (!strcmp(currentValue,Tcl_GetString(values[currentIndex]))) { 1743 break; 1744 } 1745 } 1746 if (currentIndex >= nValues) { 1747 /* Not found */ 1748 currentIndex = -1; 1749 } 1750 } 1751 cbPtr->combobox.currentIndex = currentIndex; 1752 Tcl_SetObjResult(interp, Tcl_NewIntObj(currentIndex)); 1753 return TCL_OK; 1754 } else if (objc == 3) { 1755 if (Tcl_GetIntFromObj(interp, objv[2], ¤tIndex) != TCL_OK) { 1756 return TCL_ERROR; 1757 } 1758 if (currentIndex < 0 || currentIndex >= nValues) { 1759 Tcl_AppendResult(interp, 1760 "Index ", Tcl_GetString(objv[2]), " out of range", 1761 NULL); 1762 return TCL_ERROR; 1763 } 1764 1765 cbPtr->combobox.currentIndex = currentIndex; 1766 1767 return EntrySetValue(recordPtr, Tcl_GetString(values[currentIndex])); 1768 } else { 1769 Tcl_WrongNumArgs(interp, 2, objv, "?newIndex?"); 1770 return TCL_ERROR; 1771 } 1772 return TCL_OK; 1773} 1774 1775/*------------------------------------------------------------------------ 1776 * +++ Combobox widget definition. 1777 */ 1778static const Ttk_Ensemble ComboboxCommands[] = { 1779 { "bbox", EntryBBoxCommand,0 }, 1780 { "cget", TtkWidgetCgetCommand,0 }, 1781 { "configure", TtkWidgetConfigureCommand,0 }, 1782 { "current", ComboboxCurrentCommand,0 }, 1783 { "delete", EntryDeleteCommand,0 }, 1784 { "get", EntryGetCommand,0 }, 1785 { "icursor", EntryICursorCommand,0 }, 1786 { "identify", TtkWidgetIdentifyCommand,0 }, 1787 { "index", EntryIndexCommand,0 }, 1788 { "insert", EntryInsertCommand,0 }, 1789 { "instate", TtkWidgetInstateCommand,0 }, 1790 { "selection", 0,EntrySelectionCommands }, 1791 { "state", TtkWidgetStateCommand,0 }, 1792 { "set", EntrySetCommand,0 }, 1793 { "xview", EntryXViewCommand,0 }, 1794 { 0,0,0 } 1795}; 1796 1797static WidgetSpec ComboboxWidgetSpec = { 1798 "TCombobox", /* className */ 1799 sizeof(Combobox), /* recordSize */ 1800 ComboboxOptionSpecs, /* optionSpecs */ 1801 ComboboxCommands, /* subcommands */ 1802 ComboboxInitialize, /* initializeProc */ 1803 EntryCleanup, /* cleanupProc */ 1804 ComboboxConfigure, /* configureProc */ 1805 EntryPostConfigure, /* postConfigureProc */ 1806 TtkWidgetGetLayout, /* getLayoutProc */ 1807 TtkWidgetSize, /* sizeProc */ 1808 EntryDoLayout, /* layoutProc */ 1809 EntryDisplay /* displayProc */ 1810}; 1811 1812/*------------------------------------------------------------------------ 1813 * +++ Spinbox widget. 1814 */ 1815 1816typedef struct { 1817 Tcl_Obj *valuesObj; 1818 1819 Tcl_Obj *fromObj; 1820 Tcl_Obj *toObj; 1821 Tcl_Obj *incrementObj; 1822 Tcl_Obj *formatObj; 1823 1824 Tcl_Obj *wrapObj; 1825 Tcl_Obj *commandObj; 1826} SpinboxPart; 1827 1828typedef struct { 1829 WidgetCore core; 1830 EntryPart entry; 1831 SpinboxPart spinbox; 1832} Spinbox; 1833 1834static Tk_OptionSpec SpinboxOptionSpecs[] = { 1835 {TK_OPTION_STRING, "-values", "values", "Values", 1836 "", Tk_Offset(Spinbox, spinbox.valuesObj), -1, 1837 0,0,0 }, 1838 1839 {TK_OPTION_DOUBLE, "-from", "from", "From", 1840 "0", Tk_Offset(Spinbox,spinbox.fromObj), -1, 1841 0,0,0 }, 1842 {TK_OPTION_DOUBLE, "-to", "to", "To", 1843 "0", Tk_Offset(Spinbox,spinbox.toObj), -1, 1844 0,0,0 }, 1845 {TK_OPTION_DOUBLE, "-increment", "increment", "Increment", 1846 "1", Tk_Offset(Spinbox,spinbox.incrementObj), -1, 1847 0,0,0 }, 1848 {TK_OPTION_STRING, "-format", "format", "Format", 1849 "", Tk_Offset(Spinbox, spinbox.formatObj), -1, 1850 0,0,0 }, 1851 1852 {TK_OPTION_STRING, "-command", "command", "Command", 1853 "", Tk_Offset(Spinbox, spinbox.commandObj), -1, 1854 0,0,0 }, 1855 {TK_OPTION_BOOLEAN, "-wrap", "wrap", "Wrap", 1856 "0", Tk_Offset(Spinbox,spinbox.wrapObj), -1, 1857 0,0,0 }, 1858 1859 WIDGET_INHERIT_OPTIONS(EntryOptionSpecs) 1860}; 1861 1862/* SpinboxInitialize -- 1863 * Initialization hook for spinbox widgets. 1864 */ 1865static void 1866SpinboxInitialize(Tcl_Interp *interp, void *recordPtr) 1867{ 1868 Spinbox *sb = recordPtr; 1869 TtkTrackElementState(&sb->core); 1870 EntryInitialize(interp, recordPtr); 1871} 1872 1873/* SpinboxConfigure -- 1874 * Configuration hook for spinbox widgets. 1875 */ 1876static int 1877SpinboxConfigure(Tcl_Interp *interp, void *recordPtr, int mask) 1878{ 1879 Spinbox *sb = recordPtr; 1880 int unused; 1881 1882 /* Make sure -values is a valid list: 1883 */ 1884 if (Tcl_ListObjLength(interp,sb->spinbox.valuesObj,&unused) != TCL_OK) 1885 return TCL_ERROR; 1886 1887 return EntryConfigure(interp, recordPtr, mask); 1888} 1889 1890static const Ttk_Ensemble SpinboxCommands[] = { 1891 { "bbox", EntryBBoxCommand,0 }, 1892 { "cget", TtkWidgetCgetCommand,0 }, 1893 { "configure", TtkWidgetConfigureCommand,0 }, 1894 { "delete", EntryDeleteCommand,0 }, 1895 { "get", EntryGetCommand,0 }, 1896 { "icursor", EntryICursorCommand,0 }, 1897 { "identify", TtkWidgetIdentifyCommand,0 }, 1898 { "index", EntryIndexCommand,0 }, 1899 { "insert", EntryInsertCommand,0 }, 1900 { "instate", TtkWidgetInstateCommand,0 }, 1901 { "selection", 0,EntrySelectionCommands }, 1902 { "state", TtkWidgetStateCommand,0 }, 1903 { "set", EntrySetCommand,0 }, 1904 { "validate", EntryValidateCommand,0 }, 1905 { "xview", EntryXViewCommand,0 }, 1906 { 0,0,0 } 1907}; 1908 1909static WidgetSpec SpinboxWidgetSpec = { 1910 "TSpinbox", /* className */ 1911 sizeof(Spinbox), /* recordSize */ 1912 SpinboxOptionSpecs, /* optionSpecs */ 1913 SpinboxCommands, /* subcommands */ 1914 SpinboxInitialize, /* initializeProc */ 1915 EntryCleanup, /* cleanupProc */ 1916 SpinboxConfigure, /* configureProc */ 1917 EntryPostConfigure, /* postConfigureProc */ 1918 TtkWidgetGetLayout, /* getLayoutProc */ 1919 TtkWidgetSize, /* sizeProc */ 1920 EntryDoLayout, /* layoutProc */ 1921 EntryDisplay /* displayProc */ 1922}; 1923 1924/*------------------------------------------------------------------------ 1925 * +++ Textarea element. 1926 * 1927 * Text display area for Entry widgets. 1928 * Just computes requested size; display is handled by the widget itself. 1929 */ 1930 1931typedef struct { 1932 Tcl_Obj *fontObj; 1933 Tcl_Obj *widthObj; 1934} TextareaElement; 1935 1936static Ttk_ElementOptionSpec TextareaElementOptions[] = { 1937 { "-font", TK_OPTION_FONT, 1938 Tk_Offset(TextareaElement,fontObj), DEF_ENTRY_FONT }, 1939 { "-width", TK_OPTION_INT, 1940 Tk_Offset(TextareaElement,widthObj), "20" }, 1941 { NULL, 0, 0, NULL } 1942}; 1943 1944static void TextareaElementSize( 1945 void *clientData, void *elementRecord, Tk_Window tkwin, 1946 int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) 1947{ 1948 TextareaElement *textarea = elementRecord; 1949 Tk_Font font = Tk_GetFontFromObj(tkwin, textarea->fontObj); 1950 int avgWidth = Tk_TextWidth(font, "0", 1); 1951 Tk_FontMetrics fm; 1952 int prefWidth = 1; 1953 1954 Tk_GetFontMetrics(font, &fm); 1955 Tcl_GetIntFromObj(NULL, textarea->widthObj, &prefWidth); 1956 if (prefWidth <= 0) 1957 prefWidth = 1; 1958 1959 *heightPtr = fm.linespace; 1960 *widthPtr = prefWidth * avgWidth; 1961} 1962 1963static Ttk_ElementSpec TextareaElementSpec = { 1964 TK_STYLE_VERSION_2, 1965 sizeof(TextareaElement), 1966 TextareaElementOptions, 1967 TextareaElementSize, 1968 TtkNullElementDraw 1969}; 1970 1971/*------------------------------------------------------------------------ 1972 * +++ Widget layouts. 1973 */ 1974 1975TTK_BEGIN_LAYOUT(EntryLayout) 1976 TTK_GROUP("Entry.field", TTK_FILL_BOTH|TTK_BORDER, 1977 TTK_GROUP("Entry.padding", TTK_FILL_BOTH, 1978 TTK_NODE("Entry.textarea", TTK_FILL_BOTH))) 1979TTK_END_LAYOUT 1980 1981TTK_BEGIN_LAYOUT(ComboboxLayout) 1982 TTK_GROUP("Combobox.field", TTK_FILL_BOTH, 1983 TTK_NODE("Combobox.downarrow", TTK_PACK_RIGHT|TTK_FILL_Y) 1984 TTK_GROUP("Combobox.padding", TTK_FILL_BOTH|TTK_PACK_LEFT|TTK_EXPAND, 1985 TTK_NODE("Combobox.textarea", TTK_FILL_BOTH))) 1986TTK_END_LAYOUT 1987 1988TTK_BEGIN_LAYOUT(SpinboxLayout) 1989 TTK_GROUP("Spinbox.field", TTK_PACK_TOP|TTK_FILL_X, 1990 TTK_GROUP("null", TTK_PACK_RIGHT, 1991 TTK_NODE("Spinbox.uparrow", TTK_PACK_TOP|TTK_STICK_E) 1992 TTK_NODE("Spinbox.downarrow", TTK_PACK_BOTTOM|TTK_STICK_E)) 1993 TTK_GROUP("Spinbox.padding", TTK_FILL_BOTH, 1994 TTK_NODE("Spinbox.textarea", TTK_FILL_BOTH))) 1995TTK_END_LAYOUT 1996 1997/*------------------------------------------------------------------------ 1998 * +++ Initialization. 1999 */ 2000MODULE_SCOPE 2001void TtkEntry_Init(Tcl_Interp *interp) 2002{ 2003 Ttk_Theme themePtr = Ttk_GetDefaultTheme(interp); 2004 2005 Ttk_RegisterElement(interp, themePtr, "textarea", &TextareaElementSpec, 0); 2006 2007 Ttk_RegisterLayout(themePtr, "TEntry", EntryLayout); 2008 Ttk_RegisterLayout(themePtr, "TCombobox", ComboboxLayout); 2009 Ttk_RegisterLayout(themePtr, "TSpinbox", SpinboxLayout); 2010 2011 RegisterWidget(interp, "ttk::entry", &EntryWidgetSpec); 2012 RegisterWidget(interp, "ttk::combobox", &ComboboxWidgetSpec); 2013 RegisterWidget(interp, "ttk::spinbox", &SpinboxWidgetSpec); 2014} 2015 2016/*EOF*/ 2017