UNIXworkcode

1 /* 2 * Copyright 2020 Olaf Wintermann 3 * 4 * Permission is hereby granted, free of charge, to any person obtaining a 5 * copy of this software and associated documentation files (the "Software"), 6 * to deal in the Software without restriction, including without limitation 7 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 * and/or sell copies of the Software, and to permit persons to whom the 9 * Software is furnished to do so, subject to the following conditions: 10 * 11 * The above copyright notice and this permission notice shall be included in 12 * all copies or substantial portions of the Software. 13 * 14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 17 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 * DEALINGS IN THE SOFTWARE. 21 */ 22 23 #include "textfieldP.h" 24 #include "textfield.h" 25 #include <Xm/DrawP.h> 26 27 #include <stdio.h> 28 #include <ctype.h> 29 #include <string.h> 30 31 #include "../source/textBuf.h" /* Utf8CharLen */ 32 #include "../source/textSel.h" 33 #include "../source/textDisp.h" /* PixelToColor */ 34 #include "../source/text.h" /* TextPrintXIMError */ 35 36 #define TF_DEFAULT_FONT_NAME "Monospace:size=10" 37 38 #define TF_VPADDING 3 39 #define TF_HPADDING 2 40 41 #define TF_BUF_BLOCK 128 42 43 #define TF_TAB_STR " " 44 45 static NFont *defaultFont; 46 47 static void textfield_class_init(void); 48 49 static void mouse1DownAP(Widget w, XEvent *event, String *args, Cardinal *nArgs); 50 static void mouse1UpAP(Widget w, XEvent *event, String *args, Cardinal *nArgs); 51 static void adjustselectionAP(Widget w, XEvent *event, String *args, Cardinal *nArgs); 52 53 static void insertAP(Widget w, XEvent *event, String *args, Cardinal *nArgs); 54 static void actionAP(Widget w, XEvent *event, String *args, Cardinal *nArgs); 55 static void deletePrevCharAP(Widget w, XEvent *event, String *args, Cardinal *nArgs); 56 static void deleteNextCharAP(Widget w, XEvent *event, String *args, Cardinal *nArgs); 57 static void deletePrevWordAP(Widget w, XEvent *event, String *args, Cardinal *nArgs); 58 static void deleteNextWordAP(Widget w, XEvent *event, String *args, Cardinal *nArgs); 59 60 static void moveLeftAP(Widget w, XEvent *event, String *args, Cardinal *nArgs); 61 static void moveRightAP(Widget w, XEvent *event, String *args, Cardinal *nArgs); 62 static void moveLeftWordAP(Widget w, XEvent *event, String *args, Cardinal *nArgs); 63 static void moveRightWordAP(Widget w, XEvent *event, String *args, Cardinal *nArgs); 64 65 static void focusInAP(Widget w, XEvent *event, String *args, Cardinal *nArgs); 66 static void focusOutAP(Widget w, XEvent *event, String *args, Cardinal *nArgs); 67 68 static void enterAP(Widget w, XEvent *event, String *args, Cardinal *nArgs); 69 static void leaveAP(Widget w, XEvent *event, String *args, Cardinal *nArgs); 70 71 static void cutAP(Widget w, XEvent *event, String *args, Cardinal *nArgs); 72 static void copyAP(Widget w, XEvent *event, String *args, Cardinal *nArgs); 73 static void pasteAP(Widget w, XEvent *event, String *args, Cardinal *nArgs); 74 static void endLineAP(Widget w, XEvent *event, String *args, Cardinal *nArgs); 75 static void beginLineAP(Widget w, XEvent *event, String *args, Cardinal *nArgs); 76 static void selectAllAP(Widget w, XEvent *event, String *args, Cardinal *nArgs); 77 static void insertPrimaryAP(Widget w, XEvent *event, String *args, Cardinal *nArgs); 78 79 static void tfCalcCursorPos(TextFieldWidget tf); 80 81 static int tfPosToIndex(TextFieldWidget tf, int pos); 82 83 static int tfXToPos(TextFieldWidget tf, int x); 84 static int tfIndexToX(TextFieldWidget tf, int pos); 85 static void tfSelection(TextFieldWidget tf, int *start, int *end, int *startX, int *endX); 86 static void tfSelectionIndex(TextFieldWidget tf, int *start, int *end); 87 88 static void tfSetSelection(TextFieldWidget tf, int from, int to); 89 static void tfClearSelection(TextFieldWidget tf); 90 91 static void tfInsertPrimary(TextFieldWidget tf, XEvent *event); 92 93 static void TFInsert(TextFieldWidget tf, const char *chars, size_t nchars); 94 static int TFLeftPos(TextFieldWidget tf); 95 static int TFRightPos(TextFieldWidget tf); 96 static void TFDelete(TextFieldWidget tf, int from, int to); 97 98 static void wordbounds(TextFieldWidget tf, int index, int *out_wleft, int *out_wright); 99 100 static Dimension tfCalcHeight(TextFieldWidget tf); 101 102 static Atom aTargets; 103 static Atom aUtf8String; 104 105 static Boolean convertSelection( 106 Widget w, 107 Atom *seltype, 108 Atom *target, 109 Atom *type, 110 XtPointer *value, 111 unsigned long *length, 112 int *format); 113 114 static void loseSelection(Widget w, Atom *type); 115 116 static XtResource resources[] = { 117 {XmNtextRenderTable, XmCTextRenderTable, XmRString,sizeof(XmString),XtOffset(TextFieldWidget, textfield.renderTable), XmRString, NULL}, 118 {textNXftFont, textCXftFont, textTXftFont, sizeof(NFont *), XtOffset(TextFieldWidget, textfield.font), textTXftFont, &defaultFont}, 119 {XmNvalueChangedCallback, XmCCallback, XmRCallback, sizeof(XtCallbackList), XtOffset(TextFieldWidget, textfield.valueChangedCB), XmRCallback, NULL}, 120 {XmNfocusCallback, XmCCallback, XmRCallback, sizeof(XtCallbackList), XtOffset(TextFieldWidget, textfield.focusCB), XmRCallback, NULL}, 121 {XmNlosingFocusCallback, XmCCallback, XmRCallback, sizeof(XtCallbackList), XtOffset(TextFieldWidget, textfield.losingFocusCB), XmRCallback, NULL}, 122 {XmNactivateCallback, XmCCallback, XmRCallback, sizeof(XtCallbackList), XtOffset(TextFieldWidget, textfield.activateCB), XmRCallback, NULL}, 123 {XmNblinkRate, XmCBlinkRate , XmRInt, sizeof(int), XtOffset(TextFieldWidget, textfield.blinkrate), XmRImmediate, (XtPointer)500} 124 }; 125 126 static XtActionsRec actionslist[] = { 127 {"mouse1down",mouse1DownAP}, 128 {"mouse1up",mouse1UpAP}, 129 {"adjustselection",adjustselectionAP}, 130 {"insert",insertAP}, 131 {"action",actionAP}, 132 {"moveleft",moveLeftAP}, 133 {"moveright",moveRightAP}, 134 {"moveleftword",moveLeftWordAP}, 135 {"moverightword",moveRightWordAP}, 136 {"deleteprev",deletePrevCharAP}, 137 {"deletenext",deleteNextCharAP}, 138 {"deleteprevword",deletePrevWordAP}, 139 {"deletenextword",deleteNextWordAP}, 140 {"focusIn",focusInAP}, 141 {"focusOut",focusOutAP}, 142 {"enter",enterAP}, 143 {"leave",leaveAP}, 144 {"cut-clipboard",cutAP}, 145 {"copy-clipboard",copyAP}, 146 {"paste-clipboard",pasteAP}, 147 {"endLine",endLineAP}, 148 {"beginLine",beginLineAP}, 149 {"selectAll",selectAllAP}, 150 {"insertPrimary",insertPrimaryAP}, 151 {"NULL",NULL} 152 }; 153 154 155 static char defaultTranslations[] = "\ 156 <FocusIn>: focusIn()\n\ 157 <FocusOut>: focusOut()\n\ 158 <EnterWindow>: leave()\n\ 159 <LeaveWindow>: enter()\n\ 160 s ~m ~a <Key>Tab: PrimitivePrevTabGroup()\n\ 161 ~m ~a <Key>Tab: PrimitiveNextTabGroup()\n\ 162 Shift<Btn1Down>: mouse1down(\"select\")\n\ 163 <Btn1Down>: mouse1down()\n\ 164 <Btn1Up>: mouse1up()\n\ 165 <Btn2Up>: insertPrimary()\n\ 166 Ctrl<KeyPress>v: paste-clipboard()\n\ 167 Button1<MotionNotify>: adjustselection()\n\ 168 :<Key>KP_7: insert()\n\ 169 :<Key>KP_8: insert()\n\ 170 :<Key>KP_9: insert()\n\ 171 :<Key>KP_4: insert()\n\ 172 :<Key>KP_6: insert()\n\ 173 :<Key>KP_1: insert()\n\ 174 :<Key>KP_2: insert()\n\ 175 :<Key>KP_3: insert()\n\ 176 :<Key>KP_0: insert()\n\ 177 :<Key>KP_Separator: insert()\n\ 178 <KeyPress>Return: PrimitiveParentActivate() action()\n\ 179 <Key>osfActivate: PrimitiveParentActivate() action()\n\ 180 <Key>osfCancel: PrimitiveParentCancel()\n\ 181 Ctrl<KeyPress>osfBackSpace: deleteprevword()\n\ 182 Ctrl<KeyPress>osfDelete: deletenextword()\n\ 183 <KeyPress>osfBackSpace: deleteprev()\n\ 184 <KeyPress>osfDelete: deletenext()\n\ 185 Shift<Key>osfBeginLine: beginLine(l)\n\ 186 <Key>osfBeginLine: beginLine()\n\ 187 Shift<Key>osfEndLine: endLine(r)\n\ 188 <Key>osfEndLine: endLine()\n\ 189 Ctrl Shift<KeyPress>osfLeft: moveleftword(l)\n\ 190 Ctrl Shift<KeyPress>osfRight: moverightword(r)\n\ 191 Ctrl<KeyPress>osfLeft: moveleftword()\n\ 192 Ctrl<KeyPress>osfRight: moverightword()\n\ 193 Ctrl<Key>a: selectAll()\n\ 194 Shift<KeyPress>osfLeft: moveleft(l)\n\ 195 Shift<KeyPress>osfRight: moveright(r)\n\ 196 <KeyPress>osfLeft: moveleft()\n\ 197 <KeyPress>osfRight: moveright()\n\ 198 <KeyPress>: insert()"; 199 200 201 202 TextFieldClassRec tfWidgetClassRec = { 203 // Core Class 204 { 205 (WidgetClass)&xmPrimitiveClassRec, 206 "XmTextField", // class_name 207 sizeof(TextFieldRec), // widget_size 208 textfield_class_init, // class_initialize 209 NULL, // class_part_initialize 210 FALSE, // class_inited 211 textfield_init, // initialize 212 NULL, // initialize_hook 213 textfield_realize, // realize 214 actionslist, // actions 215 XtNumber(actionslist), // num_actions 216 resources, // resources 217 XtNumber(resources), // num_resources 218 NULLQUARK, // xrm_class 219 True, // compress_motion 220 True, // compress_exposure 221 True, // compress_enterleave 222 False, // visible_interest 223 textfield_destroy, // destroy 224 textfield_resize, // resize 225 textfield_expose, // expose 226 textfield_set_values, // set_values 227 NULL, // set_values_hook 228 XtInheritSetValuesAlmost, // set_values_almost 229 NULL, // get_values_hook 230 textfield_acceptfocus, // accept_focus 231 XtVersion, // version 232 NULL, // callback_offsets 233 defaultTranslations, // tm_table 234 XtInheritQueryGeometry, // query_geometry 235 NULL, // display_accelerator 236 NULL, // extension 237 }, 238 // XmPrimitive 239 { 240 (XtWidgetProc)_XtInherit, // border_highlight 241 (XtWidgetProc)_XtInherit, // border_unhighlight 242 NULL, // translations 243 NULL, // arm_and_activate 244 NULL, // syn_resources 245 0, // num_syn_resources 246 NULL // extension 247 }, 248 // TextField 249 { 250 0 251 } 252 }; 253 254 WidgetClass textfieldWidgetClass = (WidgetClass)&tfWidgetClassRec; 255 256 257 static Boolean XftFontConvert( 258 Display* dpy, 259 XrmValue* args, 260 Cardinal* num_args, 261 XrmValue* from, 262 XrmValue* to, 263 XtPointer* converter_data) 264 { 265 NFont *font = FontFromName(dpy, from->addr); 266 if(font) { 267 memcpy(to->addr, &font, sizeof(NFont*)); 268 return True; 269 } else { 270 to->addr = 0; 271 to->size = 0; 272 return True; 273 } 274 } 275 276 static void textfield_class_init(void) { 277 XtSetTypeConverter(XmRString, textTXftFont, XftFontConvert, NULL, 0, XtCacheNone, NULL); 278 } 279 280 Widget XNECreateTextField(Widget parent, char *name, ArgList arglist, Cardinal argcount) { 281 return XtCreateWidget(name, textfieldWidgetClass, parent, arglist, argcount); 282 } 283 284 285 void textfield_init(Widget request, Widget neww, ArgList args, Cardinal *num_args) { 286 TextFieldWidget tf = (TextFieldWidget)neww; 287 tf->textfield.alloc = TF_BUF_BLOCK; 288 tf->textfield.length = 0; 289 tf->textfield.pos = 0; 290 tf->textfield.buffer = XtMalloc(TF_BUF_BLOCK); 291 292 tf->textfield.posX = 0; 293 tf->textfield.posCalc = 0; 294 295 tf->textfield.scrollX = 0; 296 297 tf->textfield.hasSelection = 0; 298 tf->textfield.selStart = 0; 299 tf->textfield.selEnd = 0; 300 301 tf->textfield.btn1ClickPrev = 0; 302 tf->textfield.btn1ClickPrev2 = 0; 303 304 tf->textfield.blinkProcId = 0; 305 tf->textfield.cursorOn = 0; 306 307 if(aTargets == 0) { 308 aTargets = XInternAtom(XtDisplay(request), "TARGETS", 0); 309 } 310 if(aUtf8String == 0) { 311 aUtf8String = XInternAtom(XtDisplay(request), "UTF8_STRING", 0); 312 } 313 314 if(tf->textfield.font) { 315 tf->core.height = tfCalcHeight(tf); 316 } 317 } 318 319 static void tfInitXft(TextFieldWidget w) { 320 XWindowAttributes attributes; 321 XGetWindowAttributes(XtDisplay(w), XtWindow(w), &attributes); 322 323 Screen *screen = w->core.screen; 324 Visual *visual = screen->root_visual; 325 for(int i=0;i<screen->ndepths;i++) { 326 Depth d = screen->depths[i]; 327 if(d.depth == w->core.depth) { 328 visual = d.visuals; 329 break; 330 } 331 } 332 333 Display *dp = XtDisplay(w); 334 w->textfield.d = XftDrawCreate( 335 dp, 336 XtWindow(w), 337 visual, 338 w->core.colormap); 339 340 } 341 342 static void get_default_font(TextFieldWidget tf, Display *dp) { 343 int ret; 344 char *resName; 345 XrmValue value; 346 char *resourceType = NULL; 347 char *rtFontName = NULL; 348 char *rtFontSize = NULL; 349 350 // We don't use the Motif RenderTable directly, however in case the 351 // render table is configured to use Xft Fonts, we try to get the 352 // same font settings 353 // 354 // The textfield has a textRenderTable resource, which is just a string 355 // The textRenderTable name is used to get the rendertable font name 356 // and size from the resource database 357 358 char resNameBuf[256]; 359 resName = "defaultRT.fontName"; 360 if(tf->textfield.renderTable) { 361 ret = snprintf(resNameBuf, 256, "%s.fontName", tf->textfield.renderTable); 362 if(ret < 256) { 363 resName = resNameBuf; 364 } 365 } 366 if(XrmGetResource(XtDatabase(dp), resName, NULL, &resourceType, &value)) { 367 if(!strcmp(resourceType, "String")) { 368 rtFontName = value.addr; 369 } 370 } 371 resName = "defaultRT.fontSize"; 372 if(tf->textfield.renderTable) { 373 ret = snprintf(resNameBuf, 256, "%s.fontSize", tf->textfield.renderTable); 374 if(ret < 256) { 375 resName = resNameBuf; 376 } 377 } 378 if(XrmGetResource(XtDatabase(dp), "fixedRT.fontSize", NULL, &resourceType, &value)) { 379 if(!strcmp(resourceType, "String")) { 380 rtFontSize = value.addr; 381 } 382 } 383 384 char buf[256]; 385 char *fontname = TF_DEFAULT_FONT_NAME; 386 if(rtFontName && rtFontSize) { 387 ret = snprintf(buf, 256, "%s:size=%s", rtFontName, rtFontSize); 388 if(ret < 256) { 389 fontname = buf; 390 } 391 } 392 393 defaultFont = FontFromName(dp, fontname); 394 if(!defaultFont) { 395 defaultFont = FontFromName(dp, TF_DEFAULT_FONT_NAME); 396 } 397 } 398 399 void textfield_realize(Widget widget, XtValueMask *mask, XSetWindowAttributes *attributes) { 400 Display *dpy = XtDisplay(widget); 401 TextFieldWidget text = (TextFieldWidget)widget; 402 403 if(!defaultFont) { 404 get_default_font(text, dpy); 405 } 406 if(!text->textfield.font) { 407 text->textfield.font = defaultFont; 408 } 409 410 textfield_recalc_size((TextFieldWidget)widget); 411 (coreClassRec.core_class.realize)(widget, mask, attributes); 412 413 text->textfield.xim = XmImGetXIM(widget); 414 if(text->textfield.xim) { 415 Window win = XtWindow(widget); 416 XIMStyle style = XIMPreeditNothing | XIMStatusNothing; 417 text->textfield.xic = XCreateIC( 418 text->textfield.xim, 419 XNInputStyle, 420 style, 421 XNClientWindow, 422 win, 423 XNFocusWindow, 424 win, 425 NULL); 426 } 427 428 tfInitXft(text); 429 430 text->textfield.foregroundColor = PixelToColor(widget, text->primitive.foreground); 431 text->textfield.backgroundColor = PixelToColor(widget, text->core.background_pixel); 432 433 XGCValues gcvals; 434 gcvals.foreground = text->primitive.foreground; 435 gcvals.background = text->core.background_pixel; 436 text->textfield.gc = XCreateGC(dpy, XtWindow(widget), (GCForeground|GCBackground), &gcvals); 437 438 gcvals.foreground = text->core.background_pixel; 439 gcvals.background = text->primitive.foreground; 440 text->textfield.gcInv = XCreateGC(dpy, XtWindow(widget), (GCForeground|GCBackground), &gcvals); 441 442 gcvals.foreground = XtParent(text)->core.background_pixel; 443 gcvals.background = XtParent(text)->core.background_pixel; 444 text->textfield.highlightBackground = XCreateGC(dpy, XtWindow(widget), (GCForeground|GCBackground), &gcvals); 445 446 text->textfield.textarea_xoff = text->primitive.shadow_thickness + text->primitive.highlight_thickness + TF_HPADDING; 447 text->textfield.textarea_yoff = text->primitive.shadow_thickness + text->primitive.highlight_thickness + TF_VPADDING; 448 449 text->textfield.posX = text->textfield.textarea_xoff; 450 } 451 452 void textfield_destroy(Widget widget) { 453 TextFieldWidget tf = (TextFieldWidget)widget; 454 XtFree(tf->textfield.buffer); 455 if(tf->textfield.font != defaultFont) { 456 FontUnref(tf->textfield.font); 457 } 458 if(tf->textfield.gc) { 459 XFreeGC(XtDisplay(widget), tf->textfield.gc); 460 } 461 if(tf->textfield.gcInv) { 462 XFreeGC(XtDisplay(widget), tf->textfield.gcInv); 463 } 464 if(tf->textfield.highlightBackground) { 465 XFreeGC(XtDisplay(widget), tf->textfield.highlightBackground); 466 } 467 if(tf->textfield.blinkProcId != 0) { 468 XtRemoveTimeOut(tf->textfield.blinkProcId); 469 } 470 } 471 472 void textfield_resize(Widget widget) { 473 474 } 475 476 477 static int tfDrawString(TextFieldWidget tf, XftFont *font, XftColor *color, int x, const char *text, size_t len) { 478 NFontList *fl = tf->textfield.font->fonts; 479 480 int yoff = tf->textfield.textarea_yoff; 481 int area = tf->core.height - 2*yoff; 482 int pad = area - (fl->font->ascent + fl->font->descent); 483 int hpad = pad/2; 484 485 486 XftDrawStringUtf8( 487 tf->textfield.d, 488 color, 489 font, 490 x - tf->textfield.scrollX, 491 tf->core.height - tf->textfield.textarea_yoff -fl->font->descent - hpad, 492 (FcChar8*)text, 493 len); 494 495 XGlyphInfo extents; 496 XftTextExtentsUtf8(XtDisplay(tf), font, (FcChar8*)text, len, &extents); 497 return extents.xOff; 498 } 499 500 static void tfDrawCursor(TextFieldWidget tf) { 501 Display *dp = XtDisplay(tf); 502 Window w = XtWindow(tf); 503 int x = tf->textfield.posX - tf->textfield.scrollX; 504 int top = tf->textfield.textarea_yoff; 505 int bottom = tf->core.height-tf->textfield.textarea_yoff; 506 if(tf->textfield.hasFocus) { 507 XDrawLine( 508 dp, 509 w, 510 tf->textfield.cursorOn ? tf->textfield.gc : tf->textfield.gcInv, 511 x, 512 top, 513 x, 514 bottom); 515 } else { 516 int diff = bottom-top; 517 int max = (diff/2)+1; 518 XPoint *points = calloc(max, sizeof(XPoint)); 519 int y = top; 520 int i; 521 for(i=0;i<max && y <= bottom;i++) { 522 points[i].x = x; 523 points[i].y = y; 524 y += 2; 525 } 526 XDrawPoints(dp, w, tf->textfield.gc, points, i, CoordModeOrigin); 527 free(points); 528 } 529 530 } 531 532 static void tfRedrawText(TextFieldWidget tf) { 533 tfCalcCursorPos(tf); 534 535 int border = tf->primitive.shadow_thickness + tf->primitive.highlight_thickness; 536 537 XClearArea(XtDisplay(tf), XtWindow(tf), border, border, tf->core.width - 2*border, tf->core.height - 2*border, False); 538 539 int selStart, selEnd, selStartX, selEndX; 540 541 // draw selection 542 if(tf->textfield.hasSelection) { 543 tfSelection(tf, &selStart, &selEnd, &selStartX, &selEndX); 544 545 XftDrawRect( 546 tf->textfield.d, 547 &tf->textfield.foregroundColor, 548 selStartX - tf->textfield.scrollX, 549 tf->textfield.textarea_yoff, 550 selEndX - selStartX, 551 tf->core.height - 2*tf->textfield.textarea_yoff); 552 } 553 554 555 XRectangle rect; 556 rect.x = 0; 557 rect.y = 0; 558 rect.width = tf->core.width - 2 * tf->textfield.textarea_xoff; 559 rect.height = tf->core.height; 560 XftDrawSetClipRectangles(tf->textfield.d, tf->textfield.textarea_xoff, 0, &rect, 1); 561 562 XftFont *font = tf->textfield.font->fonts->font; 563 XftColor *color = &tf->textfield.foregroundColor; 564 565 const char *buf = tf->textfield.buffer; 566 size_t length = tf->textfield.length; 567 size_t start = 0; 568 569 int xoff = tf->textfield.textarea_xoff; 570 571 int charlen = 1; 572 int pos = 0; 573 for(int i=0;i<length;i+=charlen) { 574 FcChar32 c; 575 charlen = Utf8ToUcs4(buf + i, &c, length - i); 576 577 XftFont *cFont = FindFont(tf->textfield.font, c); 578 XftColor *cColor = &tf->textfield.foregroundColor; 579 if(tf->textfield.hasSelection) { 580 if(i >= selStart && i < selEnd) { 581 cColor = &tf->textfield.backgroundColor; 582 } 583 } 584 585 if(c == '\t' || cFont != font || color != cColor) { 586 // write chars from start to i-1 with previous font 587 size_t drawLen = i - start; 588 if(drawLen > 0) { 589 xoff += tfDrawString(tf, font, color, xoff, buf + start, drawLen); 590 start = i; 591 } 592 font = cFont; 593 color = cColor; 594 if(c == '\t') { 595 xoff += tfDrawString(tf, font, color, xoff, TF_TAB_STR, sizeof(TF_TAB_STR)-1); 596 start++; 597 } 598 } 599 600 pos++; 601 } 602 int drawLen = length - start; 603 if(drawLen > 0) { 604 tfDrawString(tf, font, color, xoff, buf + start, drawLen); 605 } 606 607 tfDrawCursor(tf); 608 } 609 610 static void tfDrawHighlight(TextFieldWidget tf) { 611 XmeDrawHighlight( 612 XtDisplay(tf), 613 XtWindow(tf), 614 tf->textfield.hasFocus ? tf->primitive.highlight_GC : tf->textfield.highlightBackground, 615 0, 616 0, 617 tf->core.width, 618 tf->core.height, 619 tf->primitive.highlight_thickness); 620 } 621 622 void textfield_expose(Widget widget, XEvent* event, Region region) { 623 TextFieldWidget tf = (TextFieldWidget)widget; 624 625 tfDrawHighlight(tf); 626 627 ///* 628 XmeDrawShadows( 629 XtDisplay(tf), 630 XtWindow(tf), 631 tf->primitive.bottom_shadow_GC, 632 tf->primitive.top_shadow_GC, 633 tf->primitive.highlight_thickness, 634 tf->primitive.highlight_thickness, 635 tf->core.width - (2 * tf->primitive.highlight_thickness), 636 tf->core.height - (2 * tf->primitive.highlight_thickness), 637 tf->primitive.shadow_thickness, 638 XmSHADOW_OUT); 639 //*/ 640 641 tfRedrawText((TextFieldWidget)widget); 642 } 643 644 Boolean textfield_set_values(Widget old, Widget request, Widget neww, ArgList args, Cardinal *num_args) { 645 Boolean redraw = False; 646 647 TextFieldWidget cur = (TextFieldWidget)old; 648 TextFieldWidget new = (TextFieldWidget)neww; 649 650 if(!new->textfield.font) { 651 if(!defaultFont) { 652 get_default_font(new, XtDisplay(neww)); 653 } 654 new->textfield.font = defaultFont; 655 } 656 657 if(cur->textfield.font != new->textfield.font) { 658 textfield_recalc_size(new); 659 redraw = True; 660 } 661 662 663 return redraw; 664 } 665 666 Boolean textfield_acceptfocus(Widget widget, Time *time) { 667 return 0; 668 } 669 670 static Dimension tfCalcHeight(TextFieldWidget tf) { 671 NFont *font = tf->textfield.font; 672 Dimension height = font->fonts->font->ascent + font->fonts->font->descent; 673 height += 2*(tf->primitive.highlight_thickness + tf->primitive.shadow_thickness) + 2*TF_VPADDING; 674 return height; 675 } 676 677 void textfield_recalc_size(TextFieldWidget w) { 678 int height = tfCalcHeight(w); 679 int width = w->core.width; 680 681 XtMakeResizeRequest((Widget)w, width, height, NULL, NULL); 682 } 683 684 685 // actions 686 687 static void ownSelection(TextFieldWidget tf) { 688 if(tf->textfield.selStart != tf->textfield.selEnd && !tf->textfield.hasSelection) { 689 XtOwnSelection((Widget)tf, XA_PRIMARY, XtLastTimestampProcessed(XtDisplay((Widget)tf)), convertSelection, loseSelection, NULL); 690 tf->textfield.hasSelection = 1; 691 } 692 } 693 694 static void adjustSelection(TextFieldWidget tf, int x) { 695 int pos = tfXToPos(tf, x); 696 int index = tfPosToIndex(tf, pos); 697 698 tf->textfield.selEnd = index; 699 tf->textfield.pos = index; 700 tf->textfield.selEndX = tfIndexToX(tf, index); 701 702 ownSelection(tf); 703 } 704 705 static void mouse1DownAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) { 706 TextFieldWidget tf = (TextFieldWidget)w; 707 708 int select = 0; 709 if(*nArgs >= 1) { 710 if(!strcmp(args[0], "select")) { 711 select = 1; 712 } 713 } 714 715 XmProcessTraversal(w, XmTRAVERSE_CURRENT); 716 717 int pos = tfXToPos(tf, event->xbutton.x); 718 int index = tfPosToIndex(tf, pos); 719 int prevPos = tf->textfield.pos; 720 tf->textfield.pos = index; 721 722 int selStart, selEnd; 723 724 Time t = event->xbutton.time; 725 int multiclicktime = XtGetMultiClickTime(XtDisplay(w)); 726 if(select) { 727 if(tf->textfield.hasSelection) { 728 if(index < tf->textfield.selStart) { 729 selStart = index; 730 selEnd = tf->textfield.selEnd; 731 } else { 732 selStart = tf->textfield.selStart; 733 selEnd = index; 734 } 735 } else { 736 if(prevPos > index) { 737 selStart = index; 738 selEnd = prevPos; 739 } else { 740 selStart = prevPos; 741 selEnd = index; 742 } 743 } 744 745 tf->textfield.dontAdjustSel = 1; 746 } else if(t - tf->textfield.btn1ClickPrev2 < 2*multiclicktime) { 747 // triple click 748 t = 0; 749 750 selStart = 0; 751 selEnd = tf->textfield.length; 752 tf->textfield.pos = selEnd; 753 tf->textfield.dontAdjustSel = 1; 754 } else if(t - tf->textfield.btn1ClickPrev < multiclicktime) { 755 // double click 756 757 int wleft, wright; 758 wordbounds(tf, index, &wleft, &wright); 759 760 selStart = wleft; 761 selEnd = wright; 762 tf->textfield.pos = wright; 763 tf->textfield.dontAdjustSel = 1; 764 } else { 765 selStart = index; 766 selEnd = index; 767 768 tf->textfield.dontAdjustSel = 0; 769 } 770 771 tf->textfield.btn1ClickPrev2 = tf->textfield.btn1ClickPrev; 772 tf->textfield.btn1ClickPrev = t; 773 774 tfSetSelection(tf, selStart, selEnd); 775 776 tfRedrawText(tf); 777 778 779 } 780 781 static void mouse1UpAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) { 782 TextFieldWidget tf = (TextFieldWidget)w; 783 if(tf->textfield.dontAdjustSel) return; 784 785 adjustSelection(tf, event->xbutton.x); 786 787 if(tf->textfield.selStart == tf->textfield.selEnd) { 788 tfClearSelection(tf); 789 } 790 791 tfRedrawText(tf); 792 } 793 794 static void adjustselectionAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) { 795 TextFieldWidget tf = (TextFieldWidget)w; 796 797 adjustSelection(tf, event->xbutton.x); 798 799 tfRedrawText(tf); 800 } 801 802 static void insertText(TextFieldWidget tf, char *chars, int nchars, XEvent *event) { 803 if(nchars == 0) return; 804 805 if(tf->textfield.hasSelection) { 806 int selStart, selEnd; 807 tfSelectionIndex(tf, &selStart, &selEnd); 808 TFDelete(tf, selStart, selEnd); 809 tfClearSelection(tf); 810 tf->textfield.pos = selStart; 811 } 812 TFInsert(tf, chars, nchars); 813 814 // value changed callback 815 XmAnyCallbackStruct cb; 816 cb.reason = XmCR_VALUE_CHANGED; 817 cb.event = event; 818 XtCallCallbacks((Widget)tf, XmNvalueChangedCallback, &cb); 819 820 821 tfRedrawText(tf); 822 } 823 824 static void insertAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) { 825 TextFieldWidget tf = (TextFieldWidget)w; 826 827 char chars[128]; 828 KeySym keysym; 829 int nchars; 830 int status; 831 static XComposeStatus compose = {NULL, 0}; 832 833 if(tf->textfield.xic) { 834 #ifdef X_HAVE_UTF8_STRING 835 nchars = Xutf8LookupString(tf->textfield.xic, &event->xkey, chars, 127, &keysym, &status); 836 #else 837 nchars = XmbLookupString(tf->textfield.xic, &event->xkey, chars, 127, &keysym, &status); 838 #endif 839 } else { 840 nchars = XLookupString(&event->xkey, chars, 127, &keysym, &compose); 841 } 842 843 insertText(tf, chars, nchars, event); 844 } 845 846 static void actionAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) { 847 TextFieldWidget tf = (TextFieldWidget)w; 848 XmAnyCallbackStruct cb; 849 cb.reason = XmCR_ACTIVATE; 850 cb.event = event; 851 XtCallCallbacks((Widget)tf, XmNactivateCallback, &cb); 852 } 853 854 static void deleteText(TextFieldWidget tf, int from, int to, XEvent *event) { 855 TFDelete(tf, from, to); 856 857 XmAnyCallbackStruct cb; 858 cb.reason = XmCR_VALUE_CHANGED; 859 cb.event = event; 860 XtCallCallbacks((Widget)tf, XmNvalueChangedCallback, &cb); 861 862 tf->textfield.pos = from; 863 tfRedrawText(tf); 864 } 865 866 static void deletePrevCharAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) { 867 TextFieldWidget tf = (TextFieldWidget)w; 868 869 int from; 870 int to; 871 if(tf->textfield.hasSelection) { 872 tfSelectionIndex(tf, &from, &to); 873 tfClearSelection(tf); 874 } else { 875 from = TFLeftPos(tf); 876 to = tf->textfield.pos; 877 } 878 879 deleteText(tf, from, to, event); 880 } 881 882 static void deleteNextCharAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) { 883 TextFieldWidget tf = (TextFieldWidget)w; 884 885 int from; 886 int to; 887 if(tf->textfield.hasSelection) { 888 tfSelectionIndex(tf, &from, &to); 889 tfClearSelection(tf); 890 } else { 891 from = tf->textfield.pos; 892 to = TFRightPos(tf); 893 } 894 895 deleteText(tf, from, to, event); 896 } 897 898 static void wordbounds(TextFieldWidget tf, int index, int *out_wleft, int *out_wright) { 899 // is current char space? 900 int spc = index < tf->textfield.length ? isspace(tf->textfield.buffer[index]) : 1; 901 902 int wleft, wright; 903 904 // get left word bound 905 if(out_wleft) { 906 for(wleft=index;wleft>=0;wleft--) { 907 if(isspace(tf->textfield.buffer[wleft]) != spc) { 908 wleft++; 909 break; 910 } 911 } 912 if(wleft < 0) wleft = 0; 913 *out_wleft = wleft; 914 } 915 916 // get right word bound 917 if(out_wright) { 918 for(wright=index;wright<tf->textfield.length;wright++) { 919 if(isspace(tf->textfield.buffer[wright]) != spc) { 920 break; 921 } 922 } 923 *out_wright = wright; 924 } 925 } 926 927 static void deletePrevWordAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) { 928 TextFieldWidget tf = (TextFieldWidget)w; 929 if(tf->textfield.pos == 0 || tf->textfield.length == 0) return; 930 931 int wleft; 932 int index = tf->textfield.pos > 0 ? tf->textfield.pos - 1 : 0; 933 for(int n=0;n<2;n++) { 934 wordbounds(tf, index, &wleft, NULL); 935 // in case we found only space, try wordbounds again 936 if(wleft == tf->textfield.length || !isspace(tf->textfield.buffer[wleft])) { 937 break; // char at wleft is not space 938 } 939 index = wleft > 0 ? wleft - 1 : 0; 940 } 941 942 TFDelete(tf, wleft, tf->textfield.pos); 943 tf->textfield.pos = wleft; 944 tfRedrawText(tf); 945 } 946 947 static void deleteNextWordAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) { 948 TextFieldWidget tf = (TextFieldWidget)w; 949 if(tf->textfield.pos == tf->textfield.length || tf->textfield.length == 0) return; 950 951 int wright; 952 int index = tf->textfield.pos; 953 wordbounds(tf, index, NULL, &wright); 954 955 TFDelete(tf, index, wright); 956 tfRedrawText(tf); 957 } 958 959 static void moveSelect(TextFieldWidget tf, String *args, Cardinal *nArgs, int old_pos, int new_pos) { 960 if(*nArgs == 1) { 961 String d = args[0]; 962 // select 963 if(tf->textfield.hasSelection) { 964 if(new_pos == (d[0] == 'l' ? tf->textfield.selStart : tf->textfield.selEnd)) { 965 tf->textfield.hasSelection = 0; 966 } else if(d[0] == 'r' && new_pos < tf->textfield.selEnd) { 967 tfSetSelection(tf, new_pos, tf->textfield.selEnd); 968 } else if(new_pos > tf->textfield.selStart) { 969 tfSetSelection(tf, tf->textfield.selStart, new_pos); 970 } else { 971 tfSetSelection(tf, new_pos, tf->textfield.selEnd); 972 } 973 } else { 974 tfSetSelection(tf, old_pos, new_pos); 975 } 976 } else { 977 tf->textfield.hasSelection = 0; 978 } 979 } 980 981 static void moveLeftAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) { 982 TextFieldWidget tf = (TextFieldWidget)w; 983 int old_pos = tf->textfield.pos; 984 tf->textfield.pos = TFLeftPos(tf); 985 moveSelect(tf, args, nArgs, old_pos, tf->textfield.pos); 986 tfRedrawText(tf); 987 } 988 989 static void moveRightAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) { 990 TextFieldWidget tf = (TextFieldWidget)w; 991 int old_pos = tf->textfield.pos; 992 tf->textfield.pos = TFRightPos(tf); 993 moveSelect(tf, args, nArgs, old_pos, tf->textfield.pos); 994 tfRedrawText(tf); 995 } 996 997 static void moveLeftWordAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) { 998 TextFieldWidget tf = (TextFieldWidget)w; 999 1000 int pos = tf->textfield.pos; 1001 int old_pos = pos; 1002 int word = 0; // cursor inside a word? 1003 while(pos > 0) { 1004 pos = TFLeftPos(tf); 1005 1006 if(isspace(tf->textfield.buffer[pos])) { 1007 if(word) { 1008 break; 1009 } 1010 } else { 1011 word = 1; 1012 } 1013 tf->textfield.pos = pos; 1014 } 1015 1016 int new_pos = tf->textfield.pos; 1017 1018 moveSelect(tf, args, nArgs, old_pos, new_pos); 1019 1020 tfRedrawText(tf); 1021 } 1022 1023 static void moveRightWordAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) { 1024 TextFieldWidget tf = (TextFieldWidget)w; 1025 int old_pos = tf->textfield.pos; 1026 1027 int space = 0; // cursor inside a word? 1028 while(tf->textfield.pos < tf->textfield.length) { 1029 tf->textfield.pos = TFRightPos(tf); 1030 1031 if(!isspace(tf->textfield.buffer[tf->textfield.pos])) { 1032 if(space) { 1033 break; 1034 } 1035 } else { 1036 space = 1; 1037 } 1038 } 1039 int new_pos = tf->textfield.pos; 1040 1041 moveSelect(tf, args, nArgs, old_pos, new_pos); 1042 1043 tfRedrawText(tf); 1044 } 1045 1046 static void blinkCB(XtPointer data, XtIntervalId *id) { 1047 TextFieldWidget tf = data; 1048 tf->textfield.cursorOn = !tf->textfield.cursorOn; 1049 1050 tfDrawCursor(tf); 1051 1052 tf->textfield.blinkProcId = XtAppAddTimeOut( 1053 XtWidgetToApplicationContext((Widget)tf), 1054 tf->textfield.blinkrate, 1055 blinkCB, 1056 tf); 1057 } 1058 1059 static void focusInAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) { 1060 TextFieldWidget tf = (TextFieldWidget)w; 1061 if(!event->xfocus.send_event) return; 1062 1063 tf->textfield.hasFocus = 1; 1064 1065 if(tf->textfield.xic) { 1066 XSetICFocus(tf->textfield.xic); 1067 } 1068 1069 // focus/losingFocus events 1070 XmAnyCallbackStruct cb; 1071 cb.reason = XmCR_FOCUS; 1072 cb.event = event; 1073 XtCallCallbackList (w, tf->textfield.focusCB, (XtPointer) &cb); 1074 1075 if(tf->textfield.blinkProcId == 0) { 1076 tf->textfield.blinkProcId = XtAppAddTimeOut( 1077 XtWidgetToApplicationContext(w), 1078 tf->textfield.blinkrate, 1079 blinkCB, 1080 tf); 1081 } 1082 1083 Region r = NULL; 1084 textfield_expose(w, event, r); 1085 } 1086 1087 static void focusOutAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) { 1088 TextFieldWidget tf = (TextFieldWidget)w; 1089 1090 if(tf->textfield.xic) { 1091 XUnsetICFocus(tf->textfield.xic); 1092 } 1093 1094 if(tf->textfield.blinkProcId != 0) { 1095 XtRemoveTimeOut(tf->textfield.blinkProcId); 1096 tf->textfield.blinkProcId = 0; 1097 } 1098 tf->textfield.cursorOn = 1; 1099 1100 tf->textfield.hasFocus = 0; 1101 1102 XmAnyCallbackStruct cb; 1103 cb.reason = XmCR_LOSING_FOCUS; 1104 cb.event = event; 1105 XtCallCallbackList (w, tf->textfield.losingFocusCB, (XtPointer) &cb); 1106 1107 Region r = NULL; 1108 textfield_expose(w, event, r); 1109 } 1110 1111 static void enterAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) { 1112 1113 } 1114 1115 static void leaveAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) { 1116 1117 } 1118 1119 static void cutAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) { 1120 TextFieldWidget tf = (TextFieldWidget)w; 1121 if(!tf->textfield.hasSelection) return; 1122 1123 int from, to; 1124 tfSelectionIndex(tf, &from, &to); 1125 1126 size_t len = to - from; 1127 1128 CopyStringToClipboard(w, event->xkey.time, tf->textfield.buffer + from, len); 1129 TFDelete(tf, from, to); 1130 tf->textfield.pos = from; 1131 1132 tfRedrawText(tf); 1133 } 1134 1135 static void copyAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) { 1136 TextFieldWidget tf = (TextFieldWidget)w; 1137 if(!tf->textfield.hasSelection) return; 1138 1139 int from, to; 1140 tfSelectionIndex(tf, &from, &to); 1141 1142 size_t len = to - from; 1143 1144 CopyStringToClipboard(w, event->xkey.time, tf->textfield.buffer + from, len); 1145 } 1146 1147 static void pasteAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) { 1148 TextFieldWidget tf = (TextFieldWidget)w; 1149 1150 char *clipboard = GetClipboard(w); 1151 if(!clipboard) return; 1152 1153 int len = strlen(clipboard); 1154 1155 insertText(tf, clipboard, len, event); 1156 XtFree(clipboard); 1157 } 1158 1159 static void endLineAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) { 1160 TextFieldWidget tf = (TextFieldWidget)w; 1161 int old_pos = tf->textfield.pos; 1162 tf->textfield.pos = tf->textfield.length; 1163 moveSelect(tf, args, nArgs, old_pos, tf->textfield.pos); 1164 tfRedrawText(tf); 1165 } 1166 1167 static void beginLineAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) { 1168 TextFieldWidget tf = (TextFieldWidget)w; 1169 int old_pos = tf->textfield.pos; 1170 tf->textfield.pos = 0; 1171 moveSelect(tf, args, nArgs, 0, old_pos); 1172 tfRedrawText(tf); 1173 } 1174 1175 static void selectAllAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) { 1176 TextFieldWidget tf = (TextFieldWidget)w; 1177 tfSetSelection(tf, 0, tf->textfield.length); 1178 tfRedrawText(tf); 1179 } 1180 1181 static void insertPrimaryAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) { 1182 TextFieldWidget tf = (TextFieldWidget)w; 1183 tfInsertPrimary(tf, event); 1184 } 1185 1186 static int tfIndexToX(TextFieldWidget tf, int pos) { 1187 XftFont *font = tf->textfield.font->fonts->font; 1188 const char *buf = tf->textfield.buffer; 1189 size_t length = tf->textfield.length; 1190 size_t start = 0; 1191 1192 XGlyphInfo extents; 1193 1194 int xoff = tf->textfield.textarea_xoff; 1195 1196 int i; 1197 int charlen = 1; 1198 for(i=0;i<pos;i+=charlen) { 1199 FcChar32 c; 1200 charlen = Utf8ToUcs4(buf + i, &c, length - i); 1201 1202 XftFont *cFont = FindFont(tf->textfield.font, c); 1203 if(c == '\t' || cFont != font) { 1204 // write chars from start to i-1 with previous font 1205 size_t drawLen = i - start; 1206 if(drawLen > 0) { 1207 XftTextExtentsUtf8( 1208 XtDisplay(tf), 1209 font, 1210 (FcChar8*)buf + start, 1211 drawLen, 1212 &extents 1213 ); 1214 xoff += extents.xOff; 1215 start = i; 1216 } 1217 font = cFont; 1218 if(c == '\t') { 1219 start++; 1220 XftTextExtentsUtf8( 1221 XtDisplay(tf), 1222 font, 1223 (FcChar8*)TF_TAB_STR, 1224 sizeof(TF_TAB_STR)-1, 1225 &extents 1226 ); 1227 xoff += extents.xOff; 1228 } 1229 } 1230 } 1231 int drawLen = i - start; 1232 if(drawLen > 0) { 1233 XftTextExtentsUtf8( 1234 XtDisplay(tf), 1235 font, 1236 (FcChar8*)buf + start, 1237 drawLen, 1238 &extents 1239 ); 1240 xoff += extents.xOff; 1241 } 1242 1243 return xoff; 1244 } 1245 1246 static void tfSelection(TextFieldWidget tf, int *start, int *end, int *startX, int *endX) { 1247 int selStart, selEnd, selStartX, selEndX; 1248 if(tf->textfield.selStart > tf->textfield.selEnd) { 1249 selStart = tf->textfield.selEnd; 1250 selEnd = tf->textfield.selStart; 1251 selStartX = tf->textfield.selEndX; 1252 selEndX = tf->textfield.selStartX; 1253 } else { 1254 selEnd = tf->textfield.selEnd; 1255 selStart = tf->textfield.selStart; 1256 selEndX = tf->textfield.selEndX; 1257 selStartX = tf->textfield.selStartX; 1258 } 1259 1260 if(start) *start = selStart; 1261 if(end) *end = selEnd; 1262 if(startX) *startX = selStartX; 1263 if(endX) *endX = selEndX; 1264 } 1265 1266 static void tfSelectionIndex(TextFieldWidget tf, int *start, int *end) { 1267 tfSelection(tf, start, end, NULL, NULL); 1268 } 1269 1270 static void tfSetSelection(TextFieldWidget tf, int from, int to) { 1271 if(from > to) { 1272 int f = from; 1273 from = to; 1274 to = f; 1275 } 1276 1277 tf->textfield.selStart = from; 1278 tf->textfield.selEnd = to > tf->textfield.length ? tf->textfield.length : to; 1279 tf->textfield.selStartX = tfIndexToX(tf, tf->textfield.selStart); 1280 tf->textfield.selEndX = tfIndexToX(tf, tf->textfield.selEnd); 1281 1282 ownSelection(tf); 1283 } 1284 1285 static void tfClearSelection(TextFieldWidget tf) { 1286 tf->textfield.hasSelection = 0; 1287 tf->textfield.selStart = 0; 1288 tf->textfield.selEnd = 0; 1289 } 1290 1291 static void tfCalcCursorPos(TextFieldWidget tf) { 1292 if(tf->textfield.pos == tf->textfield.posCalc) return; 1293 1294 int xoff = tfIndexToX(tf, tf->textfield.pos); 1295 1296 tf->textfield.posX = xoff; 1297 tf->textfield.posCalc = tf->textfield.pos; 1298 1299 int posX = tf->textfield.posX; 1300 int margin = tf->textfield.textarea_xoff; 1301 if(posX > tf->core.width + tf->textfield.scrollX - margin) { 1302 tf->textfield.scrollX = -tf->core.width + posX + margin; 1303 } else if(posX < tf->textfield.scrollX + margin) { 1304 tf->textfield.scrollX = posX - tf->textfield.textarea_xoff; 1305 } 1306 } 1307 1308 static int tfPosToIndex(TextFieldWidget tf, int pos) { 1309 const char *buf = tf->textfield.buffer; 1310 size_t length = tf->textfield.length; 1311 1312 int p = 0; 1313 int charlen = 1; 1314 int i; 1315 for(i=0;i<length;i+=charlen) { 1316 if(p == pos) break; 1317 1318 FcChar32 c; 1319 charlen = Utf8ToUcs4(buf + i, &c, length - i); 1320 1321 p++; 1322 } 1323 return i; 1324 } 1325 1326 static int tfXToPos(TextFieldWidget tf, int x) { 1327 const char *buf = tf->textfield.buffer; 1328 size_t length = tf->textfield.length; 1329 1330 x += tf->textfield.scrollX; 1331 1332 int pos = 0; 1333 1334 XGlyphInfo extents; 1335 1336 int xoff = tf->textfield.textarea_xoff; 1337 1338 int charlen = 1; 1339 for(int i=0;i<length;i+=charlen) { 1340 FcChar32 c; 1341 charlen = Utf8ToUcs4(buf + i, &c, length - i); 1342 1343 const char *str; 1344 size_t slen; 1345 if(c == '\t') { 1346 str = TF_TAB_STR; 1347 slen = sizeof(TF_TAB_STR)-1; 1348 } else { 1349 str = buf+i; 1350 slen = charlen; 1351 } 1352 1353 XftFont *font = FindFont(tf->textfield.font, c); 1354 XftTextExtentsUtf8( 1355 XtDisplay(tf), 1356 font, 1357 (FcChar8*)str, 1358 slen, 1359 &extents 1360 ); 1361 xoff += extents.xOff; 1362 if(xoff > x + (extents.xOff / 2)) { 1363 break; 1364 } 1365 pos++; 1366 } 1367 1368 return pos; 1369 } 1370 1371 1372 // ---------------- text buffer functions -------------------- 1373 1374 static void TFInsert(TextFieldWidget tf, const char *chars, size_t nchars) { 1375 // realloc buffer if needed 1376 if(tf->textfield.length + nchars >= tf->textfield.alloc) { 1377 tf->textfield.alloc += TF_BUF_BLOCK; 1378 tf->textfield.buffer = XtRealloc(tf->textfield.buffer, tf->textfield.alloc); 1379 } 1380 1381 if(tf->textfield.pos == tf->textfield.length) { 1382 // append 1383 memcpy(tf->textfield.buffer + tf->textfield.length, chars, nchars); 1384 } else { 1385 // insert 1386 char *insertpos = tf->textfield.buffer + tf->textfield.pos; 1387 memmove(insertpos + nchars, insertpos, tf->textfield.length - tf->textfield.pos); 1388 memmove(insertpos, chars, nchars); 1389 } 1390 1391 tf->textfield.length += nchars; 1392 tf->textfield.pos += nchars; 1393 1394 tfClearSelection(tf); 1395 } 1396 1397 static int TFLeftPos(TextFieldWidget tf) { 1398 int left = 0; 1399 int cur = 0; 1400 while(cur < tf->textfield.pos) { 1401 left = cur; 1402 cur += Utf8CharLen((unsigned char*)tf->textfield.buffer + cur); 1403 } 1404 return left; 1405 } 1406 1407 static int TFRightPos(TextFieldWidget tf) { 1408 int pos = tf->textfield.pos; 1409 int right = pos + Utf8CharLen((unsigned char*)tf->textfield.buffer + pos); 1410 return right > tf->textfield.length ? tf->textfield.length : right; 1411 } 1412 1413 static void TFDelete(TextFieldWidget tf, int from, int to) { 1414 if(from >= to) return; 1415 1416 if(to >= tf->textfield.length) { 1417 tf->textfield.length = from; 1418 return; 1419 } 1420 1421 int len = tf->textfield.length - to; 1422 memmove(tf->textfield.buffer + from, tf->textfield.buffer + to, len); 1423 1424 tf->textfield.length -= to - from; 1425 1426 tfClearSelection(tf); 1427 } 1428 1429 1430 struct PSelection { 1431 TextFieldWidget tf; 1432 XEvent *event; 1433 char *xastring; 1434 char *utf8string; 1435 int target; 1436 }; 1437 1438 static void getPrimary( 1439 Widget w, 1440 XtPointer clientData, 1441 Atom *selType, 1442 Atom *type, 1443 XtPointer value, 1444 unsigned long *length, 1445 int *format) 1446 { 1447 struct PSelection *sel = clientData; 1448 sel->target++; 1449 1450 if(value && *format == 8 && *length > 0 && (*type == XA_STRING || *type == aUtf8String)) { 1451 char *str = XtMalloc((*length) + 1); 1452 memcpy(str, value, *length); 1453 str[*length] = 0; 1454 1455 if(*type == aUtf8String) { 1456 sel->utf8string = str; 1457 } else { 1458 sel->xastring = str; 1459 } 1460 } 1461 1462 if(sel->target == 2) { 1463 char *insert = sel->utf8string ? sel->utf8string : sel->xastring; 1464 if(insert) { 1465 insertText(sel->tf, insert, strlen(insert), sel->event); 1466 } 1467 1468 if(sel->utf8string) { 1469 XtFree(sel->utf8string); 1470 } 1471 if(sel->xastring) { 1472 XtFree(sel->xastring); 1473 } 1474 XtFree((void*)sel); 1475 } 1476 } 1477 1478 static void tfInsertPrimary(TextFieldWidget tf, XEvent *event) { 1479 struct PSelection *sel = (void*)XtMalloc(sizeof(struct PSelection)); 1480 sel->tf = tf; 1481 sel->event = event; 1482 sel->target = 0; 1483 sel->xastring = NULL; 1484 sel->utf8string = NULL; 1485 1486 Atom targets[2] = {aUtf8String, XA_STRING}; 1487 Time time = XtLastTimestampProcessed(XtDisplay((Widget)tf)); 1488 1489 void *data[2] = { sel, sel }; 1490 1491 #ifdef __APPLE__ 1492 XtGetSelectionValue((Widget)tf, XA_PRIMARY, targets[0], getPrimary, sel, time); 1493 XtGetSelectionValue((Widget)tf, XA_PRIMARY, targets[1], getPrimary, sel, time); 1494 #else 1495 XtGetSelectionValues((Widget)tf, XA_PRIMARY, targets, 2, getPrimary, data, time); 1496 #endif 1497 } 1498 1499 1500 // Atoms: aTargets, XA_STRING, aUtf8String 1501 1502 static Boolean convertSelection( 1503 Widget w, 1504 Atom *seltype, 1505 Atom *target, 1506 Atom *type, 1507 XtPointer *value, 1508 unsigned long *length, 1509 int *format) 1510 { 1511 TextFieldWidget tf = (TextFieldWidget)w; 1512 1513 if(*target == aTargets) { 1514 Atom *retTargets = calloc(3, sizeof(Atom)); 1515 retTargets[0] = XA_STRING; 1516 retTargets[1] = aUtf8String; 1517 retTargets[2] = aTargets; 1518 *type = XA_ATOM; 1519 *value = retTargets; 1520 *length = 3; 1521 *format = 32; 1522 return True; 1523 } 1524 1525 if(*target == XA_STRING || *target == aUtf8String) { 1526 char *selectedText = NULL; 1527 size_t len = 0; 1528 1529 if(tf->textfield.hasSelection) { 1530 int from, to; 1531 tfSelectionIndex(tf, &from, &to); 1532 len = to - from; 1533 selectedText = XtMalloc(len + 1); 1534 memcpy(selectedText, tf->textfield.buffer + from, len); 1535 selectedText[len] = 0; 1536 } else { 1537 selectedText = XtMalloc(4); 1538 selectedText[0] = 0; 1539 } 1540 1541 *type = *target == aUtf8String ? aUtf8String : XA_STRING; 1542 *value = selectedText; 1543 *length = len; 1544 *format = 8; 1545 return True; 1546 } 1547 1548 return False; 1549 } 1550 1551 static void loseSelection(Widget w, Atom *type) { 1552 TextFieldWidget tf = (TextFieldWidget)w; 1553 tfClearSelection(tf); 1554 tfRedrawText(tf); 1555 } 1556 1557 1558 // --------------------- public API -------------------------- 1559 1560 void XNETextFieldSetString(Widget widget, char *value) { 1561 if(!value) { 1562 value = ""; 1563 } 1564 1565 size_t len = strlen(value); 1566 1567 TextFieldWidget tf = (TextFieldWidget)widget; 1568 if(len > tf->textfield.alloc) { 1569 size_t alloc = len + TF_BUF_BLOCK - (len % TF_BUF_BLOCK); 1570 tf->textfield.buffer = XtRealloc(tf->textfield.buffer, alloc); 1571 tf->textfield.alloc = alloc; 1572 } 1573 1574 memcpy(tf->textfield.buffer, value, len); 1575 1576 tf->textfield.length = len; 1577 tf->textfield.pos = 0; 1578 1579 tfClearSelection(tf); 1580 if(XtIsRealized(widget)) { 1581 tfRedrawText(tf); 1582 } 1583 } 1584 1585 char* XNETextFieldGetString(Widget widget) { 1586 TextFieldWidget tf = (TextFieldWidget)widget; 1587 1588 char *r = XtMalloc(tf->textfield.length + 1); 1589 memcpy(r, tf->textfield.buffer, tf->textfield.length); 1590 r[tf->textfield.length] = '\0'; 1591 return r; 1592 } 1593 1594 XmTextPosition XNETextFieldGetLastPosition(Widget widget) { 1595 TextFieldWidget tf = (TextFieldWidget)widget; 1596 return tf->textfield.length; 1597 } 1598 1599 void XNETextFieldSetInsertionPosition(Widget widget, XmTextPosition i) { 1600 TextFieldWidget tf = (TextFieldWidget)widget; 1601 tf->textfield.pos = i <= tf->textfield.length ? i : tf->textfield.length; 1602 if(XtIsRealized(widget)) { 1603 tfRedrawText(tf); 1604 } 1605 } 1606 1607 void XNETextFieldSetSelection(Widget w, XmTextPosition first, XmTextPosition last, Time sel_time) { 1608 TextFieldWidget tf = (TextFieldWidget)w; 1609 tfSetSelection(tf, first, last); 1610 if(XtIsRealized(w)) { 1611 tfRedrawText(tf); 1612 } 1613 } 1614