X11workbench Toolkit  1.0
text_object.c
Go to the documentation of this file.
1 // //
3 // _ _ _ _ _ //
4 // | |_ ___ __ __| |_ ___ | |__ (_) ___ ___ | |_ ___ //
5 // | __|/ _ \\ \/ /| __| / _ \ | '_ \ | | / _ \ / __|| __| / __| //
6 // | |_| __/ > < | |_ | (_) || |_) || || __/| (__ | |_ _| (__ //
7 // \__|\___|/_/\_\ \__|_____\___/ |_.__/_/ | \___| \___| \__|(_)\___| //
8 // |_____| |__/ //
9 // //
10 // //
12 
13 
14 /*****************************************************************************
15 
16  X11workbench - X11 programmer's 'work bench' application and toolkit
17  Copyright (c) 2010-2019 by Bob Frazier (aka 'Big Bad Bombastic Bob')
18 
19 
20  DISCLAIMER: The X11workbench application and toolkit software are supplied
21  'as-is', with no warranties, either implied or explicit.
22  Any claims to alleged functionality or features should be
23  considered 'preliminary', and might not function as advertised.
24 
25  MIT-like license:
26 
27  There is no restriction as to what you can do with this software, so long
28  as you include the above copyright notice and DISCLAIMER for any distributed
29  work that is equal to or derived from this one, along with this paragraph
30  that explains the terms of the license if the source is also being made
31  available. A "derived work" describes a work that uses a significant portion
32  of the source files or algorithms that are included with this one.
33  Specifically excluded from this are files that were generated by the software,
34  or anything that is included with the software that is part of another package
35  (such as files that were created or added during the 'configure' process).
36  Specifically included is the use of part or all of any of the X11 workbench
37  toolkit source or header files in your distributed application. If you do not
38  ship the source, the above copyright statement is still required to be placed
39  in a reasonably prominent place, such as documentation, splash screens, and/or
40  'about the application' dialog boxes.
41 
42  Use and distribution are in accordance with GPL, LGPL, and/or the above
43  MIT-like license. See COPYING and README files for more information.
44 
45 
46  Additional information at http://sourceforge.net/projects/X11workbench
47 
48 ******************************************************************************/
49 
59 #include <stdio.h>
60 #include <stdlib.h>
61 #include <unistd.h>
62 #include <memory.h>
63 #include <string.h>
64 #include <strings.h>
65 #include <errno.h>
66 #include <limits.h>
67 
68 #include "draw_text.h"
69 #include "text_object.h"
70 #include "conf_help.h"
71 
72 
73 // INTERNAL STRUCTURES
74 
79 {
80  struct s_internal_undo_redo_buffer *pNext; // singly linked list [for now]
81  // NOTE: a background process can trim this to a reasonable size
82 
83  // NOTE: for a simple row/col insert or paste, left=right, top=bottom
84  // for all other operations, the rctSel will apply accordingly
85  WB_RECT rctSelOld; // original select rectangle (as applicable)
86  WB_RECT rctSelNew; // new select rectangle after the operation
87 
88  int iOperation; // see 'enum e_undo_operation' below
89  int iSelMode; // selection mode
90 
91  int nOld; // size of 'old' buffer (zero if none)
92  int nNew; // size of 'new' buffer (zero if none)
93 
94  char aData[2]; // actual data for operation
95 };
96 
101 typedef enum e_undo_operation
102 {
103  UNDO_NONE=-1, /* for initialization purposes only */
104  UNDO_SELECT=0,
105  UNDO_DELETE=1,
106  UNDO_INSERT=2,
107  UNDO_REPLACE=3, /* currently not used */
108  UNDO_INDENT=4,
109  UNDO_LAST=4
110 };
111 
116 
117 
118 
119 #define SEL_RECT_ALL(X) ((X)->rctSel.left < 0)
120 #define SEL_RECT_EMPTY(X) (!SEL_RECT_ALL(X) && ((X)->rctSel.left == (X)->rctSel.right && (X)->rctSel.bottom == (X)->rctSel.top))
121 #define NORMALIZE_SEL_RECT(X) {if(( (X).top > (X).bottom ) || ( (X).top == (X).bottom && (X).left > (X).right) ) \
122  { int i1 = (X).left; (X).left = (X).right; (X).right = i1; \
123  i1 = (X).top; (X).top = (X).bottom; (X).bottom = i1; }}
124 
125 
126 #define CURSOR_BLINK_RESET 0 /* shows blinking cursor for max time */
127 #define CURSOR_BLINK_PERIOD 3
128 #define CURSOR_BLINK_OFF (CURSOR_BLINK_PERIOD - 1)
129 
130 
131 // *************************
132 // LOCAL FUNCTION PROTOTYPES
133 // *************************
134 
135 static void __internal_destroy(TEXT_OBJECT *pThis);
136 static void __internal_init(TEXT_OBJECT *pThis);
137 static void __internal_highlight_colors(TEXT_OBJECT *pThis, XColor clrHFG, XColor clrHBG);
138 static char * __internal_get_text(TEXT_OBJECT *pThis);
139 static void __internal_set_text(TEXT_OBJECT *pThis, const char *szText, unsigned long cbLen);
140 static int __internal_get_rows(const TEXT_OBJECT *pThis);
141 static int __internal_get_cols(TEXT_OBJECT *pThis);
142 static int __internal_get_filetype(const TEXT_OBJECT *pThis);
143 static void __internal_set_filetype(TEXT_OBJECT *pThis, int iFileType);
144 static int __internal_get_linefeed(const TEXT_OBJECT *pThis);
145 static void __internal_set_linefeed(TEXT_OBJECT *pThis, int iLineFeed);
146 static int __internal_get_insmode(const TEXT_OBJECT *pThis);
147 static void __internal_set_insmode(TEXT_OBJECT *pThis, int iInsMode);
148 static int __internal_get_selmode(const TEXT_OBJECT *pThis);
149 static void __internal_set_selmode(TEXT_OBJECT *pThis, int iSelMode);
150 static int __internal_get_tab(const TEXT_OBJECT *pThis);
151 static void __internal_set_tab(TEXT_OBJECT *pThis, int iTab);
152 static int __internal_get_scrollmode(const TEXT_OBJECT *pThis);
153 static void __internal_set_scrollmode(TEXT_OBJECT *pThis, int iScrollMode);
154 static void __internal_get_select(const TEXT_OBJECT *pThis, WB_RECT *pRct);
155 static void __internal_set_select(TEXT_OBJECT *pThis, const WB_RECT *pRct);
156 static int __internal_has_select(const TEXT_OBJECT *pThis);
157 static char* __internal_get_sel_text(const TEXT_OBJECT *pThis, const WB_RECT *pRct);
158 static int __internal_get_row(const TEXT_OBJECT *pThis);
159 static void __internal_set_row(TEXT_OBJECT *pThis, int iRow);
160 static int __internal_get_col(const TEXT_OBJECT *pThis);
161 static void __internal_set_col(TEXT_OBJECT *pThis, int iCol);
162 static void __internal_del_select(TEXT_OBJECT *pThis);
163 static void __internal_replace_select(TEXT_OBJECT *pThis, const char *szText, unsigned long cbLen);
164 static void __internal_del_chars(TEXT_OBJECT *pThis, int nChar);
165 static void __internal_ins_chars(TEXT_OBJECT *pThis, const char *pChar, int nChar);
166 static void __internal_indent(TEXT_OBJECT *pThis, int nCol);
167 static int __internal_can_undo(TEXT_OBJECT *pThis);
168 static void __internal_undo(TEXT_OBJECT *pThis);
169 static int __internal_can_redo(TEXT_OBJECT *pThis);
170 static void __internal_redo(TEXT_OBJECT *pThis);
171 static void __internal_get_view(const TEXT_OBJECT *pThis, WB_RECT *pRct);
172 static void __internal_set_view_origin(TEXT_OBJECT *pThis, const WB_POINT *pOrig);
173 static void __internal_begin_highlight(TEXT_OBJECT *pThis);
174 static void __internal_end_highlight(TEXT_OBJECT *pThis);
175 
176 static void __internal_mouse_click(TEXT_OBJECT *pThis, int iMouseXDelta, int iMouseYDelta, int iType, int iACS);
177 static void __internal_begin_mouse_drag(TEXT_OBJECT *pThis);
178 static void __internal_end_mouse_drag(TEXT_OBJECT *pThis);
179 static void __internal_cursor_up(TEXT_OBJECT *pThis);
180 static void __internal_cursor_down(TEXT_OBJECT *pThis);
181 static void __internal_cursor_left(TEXT_OBJECT *pThis);
182 static void __internal_cursor_right(TEXT_OBJECT *pThis);
183 static void __internal_page_up(TEXT_OBJECT *pThis);
184 static void __internal_page_down(TEXT_OBJECT *pThis);
185 static void __internal_page_left(TEXT_OBJECT *pThis);
186 static void __internal_page_right(TEXT_OBJECT *pThis);
187 
188 static void __internal_cursor_home(TEXT_OBJECT *pThis);
189 static void __internal_cursor_end(TEXT_OBJECT *pThis);
190 static void __internal_cursor_top(TEXT_OBJECT *pThis);
191 static void __internal_cursor_bottom(TEXT_OBJECT *pThis);
192 
193 static void __internal_scroll_vertical(TEXT_OBJECT *pThis, int nRows);
194 static void __internal_scroll_horizontal(TEXT_OBJECT *pThis, int nCols);
195 
196 static void __internal_do_expose(TEXT_OBJECT *pThis, Display *pDisplay, Window wID,
197  WBGC gc, const WB_GEOM *pPaintGeom, const WB_GEOM *pViewGeom,
198  WB_FONTC pFont);
199 static void __internal_cursor_blink(TEXT_OBJECT *pThis, int bHasFocus);
200 static int __internal_cursor_show(int iBlinkState);
201 
202 static void __internal_set_save_point(TEXT_OBJECT *pThis);
203 static int __internal_get_modified(TEXT_OBJECT *pThis);
204 
205 
206 // *********************************
207 // LOCALLY DEFINED GLOBAL STRUCTURES
208 // *********************************
209 
210 const TEXT_OBJECT_VTABLE WBDefaultTextObjectVTable =
211 {
212  __internal_destroy,
213  __internal_init,
214  __internal_highlight_colors,
215  __internal_get_text,
216  __internal_set_text,
217  __internal_get_rows,
218  __internal_get_cols,
219  __internal_get_filetype,
220  __internal_set_filetype,
221  __internal_get_linefeed,
222  __internal_set_linefeed,
223  __internal_get_insmode,
224  __internal_set_insmode,
225  __internal_get_selmode,
226  __internal_set_selmode,
227  __internal_get_tab,
228  __internal_set_tab,
229  __internal_get_scrollmode,
230  __internal_set_scrollmode,
231  __internal_get_select,
232  __internal_set_select,
233  __internal_has_select,
234  __internal_get_sel_text,
235  __internal_get_row,
236  __internal_set_row,
237  __internal_get_col,
238  __internal_set_col,
239  __internal_del_select,
240  __internal_replace_select,
241  __internal_del_chars,
242  __internal_ins_chars,
243  __internal_indent,
244  __internal_can_undo,
245  __internal_undo,
246  __internal_can_redo,
247  __internal_redo,
248  __internal_get_view,
249  __internal_set_view_origin,
250  __internal_begin_highlight,
251  __internal_end_highlight,
252  __internal_mouse_click,
253  __internal_begin_mouse_drag,
254  __internal_end_mouse_drag,
255  __internal_cursor_up,
256  __internal_cursor_down,
257  __internal_cursor_left,
258  __internal_cursor_right,
259  __internal_page_up,
260  __internal_page_down,
261  __internal_page_left,
262  __internal_page_right,
263 
264  __internal_cursor_home,
265  __internal_cursor_end,
266  __internal_cursor_top,
267  __internal_cursor_bottom,
268 
269  __internal_scroll_vertical,
270  __internal_scroll_horizontal,
271 
273  __internal_cursor_blink,
274 
275  __internal_set_save_point,
276  __internal_get_modified
277 
278 };
279 
280 
281 
283 // GLOBAL (yet static) INLINE UTILITIES
285 
286 // line endings translated from 'enum' to 'const char *'
287 
288 static __inline__ const char * __internal_get_line_ending_text(enum e_LineFeed iIndex)
289 {
290  static const char * const szLineEndings[LineFeed_ENTRYCOUNT] =
291  {
292 #ifdef WIN32
293  "\r\n"
294 #else // POSIX
295  "\n",
296 #endif // WIN32 or POSIX - OS-dependent line endings
297  "\n","\r","\r\n","\n\r"
298  };
299 
300  if((int)iIndex < 0 || (int)iIndex >= LineFeed_ENTRYCOUNT)
301  {
302  return NULL; // single-line or invalid
303  }
304 
305  return szLineEndings[iIndex];
306 }
307 
308 
309 
310 
311 
312 // ***********
313 // TEXT BUFFER
314 // ***********
315 
316 // TODO: an API function for single-line text
317 
318 #define DEFAULT_TEXT_BUFFER_LINES 16384
319 
320 TEXT_BUFFER * WBAllocTextBuffer(const char *pBuf, unsigned int cbBufSize)
321 {
322 TEXT_BUFFER *pRval;
323 int nLines = 0;
324 int cbLen;
325 
326  if(pBuf && (cbBufSize || *pBuf))
327  {
328  nLines = WBStringLineCount(pBuf, cbBufSize);
329 
330  WB_DEBUG_PRINT(DebugLevel_Verbose, "%s line %d allocate for %d lines\n", __FUNCTION__, __LINE__, nLines);
331  }
332 
333  if(nLines < DEFAULT_TEXT_BUFFER_LINES)
334  {
335  nLines = DEFAULT_TEXT_BUFFER_LINES;
336  }
337 
338  cbLen = sizeof(*pRval) + nLines * sizeof(pRval->aLines[0]);
339  pRval = WBAlloc(cbLen);
340 
341  if(!pRval)
342  {
343  WB_ERROR_PRINT("ERROR - %s - not enough memory (%ld)\n", __FUNCTION__,
344  (unsigned long)(sizeof(*pRval) + nLines * sizeof(pRval->aLines[0])));
345  return NULL;
346  }
347 
348  memset(pRval, 0, cbLen); // zero out entire structure (always)
349 
350  pRval->nArraySize = nLines; // pre-assigned values
351 // pRval->nEntries = 0; already zero, comment left for reference
352 
353  if(pBuf && (cbBufSize || *pBuf))
354  {
355  int nL = 0;
356  const char *p1;
357 
358  if(!cbBufSize)
359  {
360  cbBufSize = strlen(pBuf);
361  }
362 
363  do
364  {
365  int cbLen;
366  char *p2, *p3;
367 
368  cbLen = cbBufSize; // size before I begin
369  p1 = WBStringNextLine(pBuf, &cbBufSize);
370 
371  if(p1) // another line remains
372  {
373  cbLen = p1 - pBuf; // re-calc length based on new pointer
374  }
375 
376  p2 = WBAlloc(cbLen + 2);
377 
378  if(!p2)
379  {
380  WBFreeTextBuffer(pRval);
381  pRval = NULL;
382  break;
383  }
384 
385  if(cbLen) // it's possible it may be zero
386  {
387  memcpy(p2, pBuf, cbLen); // copy the data
388  }
389 
390  p3 = p2 + cbLen; // the end of the string
391  *p3 = 0; // always zero-byte terminate it first
392 
393  while(p3 > p2 && (*(p3 - 1) <= ' ' ||
394  *(p3 - 1) == HARD_TAB_CHAR)) // trim ALL trailing white space including CR, LF, tab, space, FF, etc.
395  {
396  // TODO: handle <FF> or <VT> differently?
397  // TODO: leave white space to mark 'extent' of line? naaw, probably not
398 
399  *(--p3) = 0; // for now just trim it all (p3 always points past end of string)
400  }
401 
402  pRval->aLines[nL++] = p2;
403 
404 // WB_ERROR_PRINT("TEMPORARY - %s - %4d: %s\n", __FUNCTION__, nL, p2);
405 
406  pBuf = p1;
407 
408  } while(pBuf && cbBufSize && nL < nLines);
409 
410  pRval->nEntries = nL; // NOTE: on error, this will be needed for cleanup
411 
412 // WB_ERROR_PRINT("TEMPORARY - %s - %ld lines\n", __FUNCTION__, pRval->nEntries);
413 
415  }
416 
417 // if(pRval)
418 // {
419 // int nL;
420 //
421 // WB_ERROR_PRINT("\nTEMPORARY - %s - %ld lines\n", __FUNCTION__, pRval->nEntries);
422 //
423 // for(nL=0; nL < pRval->nEntries; nL++)
424 // {
425 // WB_ERROR_PRINT(" \"%s\"\n", pRval->aLines[nL]);
426 // }
427 //
428 // WB_ERROR_PRINT("END TEMPORARY - %s\n\n", __FUNCTION__);
429 // }
430 
431 // WB_ERROR_PRINT("TEMPORARY - %s returns %p\n", __FUNCTION__, pRval);
432 
433  return pRval;
434 }
435 
436 int WBCheckReAllocTextBuffer(TEXT_BUFFER **ppBuf, int nLinesToAdd)
437 {
438 TEXT_BUFFER *pBuf;
439 int nNew;
440 
441  if(!ppBuf || !*ppBuf)
442  {
443  return -1; // error
444  }
445 
446  // TODO: parameter validation
447 
448  pBuf = *ppBuf;
449 
450  nNew = pBuf->nEntries + nLinesToAdd;
451 
452  if(nNew > pBuf->nArraySize)
453  {
454  nNew += DEFAULT_TEXT_BUFFER_LINES;
455  nNew -= (nNew % (DEFAULT_TEXT_BUFFER_LINES / 2));
456 
457  pBuf = WBReAlloc(pBuf, sizeof(*pBuf) + nNew * sizeof(pBuf->aLines[0]));
458 
459  if(!pBuf)
460  {
461  return -1; // error
462  }
463 
464  pBuf->nArraySize = nNew;
465  *ppBuf = pBuf; // potentially new pointer - probably is, though
466  }
467 
468  return 0; // no problems
469 }
470 
472 {
473 int i1;
474 
475  if(!pBuf)
476  {
477  return;
478  }
479 
480  // TODO: parameter validation
481 
482  for(i1=0; i1 < pBuf->nEntries && i1 < pBuf->nArraySize; i1++)
483  {
484  if(pBuf->aLines[i1])
485  {
486  WBFree(pBuf->aLines[i1]);
487  pBuf->aLines[i1] = NULL; // by convention
488  }
489  }
490 
491  WBFree(pBuf);
492 }
493 
494 int WBTextBufferLineLength(TEXT_BUFFER *pBuf, unsigned long nLine)
495 {
496 int i1;
497 
498  if(!pBuf || pBuf->nEntries <= nLine) // not enough lines in buffer?
499  {
500  return 0;
501  }
502 
503  // TODO: hashing, sorted, ?
504 
505  for(i1=0; i1 < TEXT_BUFFER_LINE_CACHE_SIZE && pBuf->aLineCacheLen[i1]; i1++)
506  {
507  if(pBuf->aLineCache[i1] == nLine)
508  {
509  return pBuf->aLineCacheLen[i1];
510  }
511  }
512 
513  return 0;
514 }
515 
516 void WBTextBufferLineChange(TEXT_BUFFER *pBuf, unsigned long nLine, int nNewLen)
517 {
518 int i1, i2;
519 unsigned int nNewMax = 0, nNewMinMax = UINT_MAX;
520 
521 
522  if(pBuf->nEntries <= 1) // zero or one lines?
523  {
524  WB_DEBUG_PRINT(DebugLevel_Verbose, "%s line %d - (single line) nLine=%ld, nNewLen = %d\n",
525  __FUNCTION__, __LINE__, nLine, nNewLen);
526 
527  WBTextBufferRefreshCache(pBuf); // always do it THIS way
528 
529  return;
530  }
531 
532  if(nLine > pBuf->nEntries) // line number isn't sane?
533  {
534  return; // sanity check failed (do nothing)
535  }
536 
537  WB_DEBUG_PRINT(DebugLevel_Verbose, "%s line %d - nLine=%ld, nNewLen = %d\n",
538  __FUNCTION__, __LINE__, nLine, nNewLen);
539 
540  // update the line info in my cache. If this line appears, remove it (always).
541  // If I'm deleting the line, subtract one from every line higher than this one.
542 
543  i1 = 0;
544 
545  while(i1 < TEXT_BUFFER_LINE_CACHE_SIZE && pBuf->aLineCacheLen[i1])
546  {
547  if(pBuf->aLineCache[i1] == nLine)
548  {
549  // move everything up, remove the last entry
550  for(i2=i1 + 1; i2 < TEXT_BUFFER_LINE_CACHE_SIZE && pBuf->aLineCacheLen[i2]; i2++)
551  {
552  pBuf->aLineCache[i2 - 1] = pBuf->aLineCache[i2];
553  pBuf->aLineCacheLen[i2 - 1] = pBuf->aLineCacheLen[i2];
554  }
555 
556  // at this point 'i2 - 1' _was_ the last entry. make sure it has zeros in it.
557  i2--;
558  if(i2 < TEXT_BUFFER_LINE_CACHE_SIZE)
559  {
560  pBuf->aLineCache[i2] = 0;
561  pBuf->aLineCacheLen[i2] = 0;
562  }
563  }
564  else // if it's a line that follows this one, make sure I decrement the line number if I deleted it
565  {
566  if(pBuf->aLineCache[i1] > nLine)
567  {
568  if(nNewLen < 0) // a line deletion
569  {
570  pBuf->aLineCache[i1] --; // line was removed, so new line count must match
571  }
572  }
573 
574  if(nNewMax < pBuf->aLineCacheLen[i1])
575  {
576  nNewMax = pBuf->aLineCacheLen[i1]; // done this way as sanity check
577  }
578 
579  if(nNewMinMax > pBuf->aLineCacheLen[i1])
580  {
581  nNewMinMax = pBuf->aLineCacheLen[i1]; // done this way as sanity check
582  }
583 
584  i1++;
585  }
586  }
587 
588  // if the cache is NOW empty, re-evaluate it.
589  if(!pBuf->aLineCacheLen[0]) // empty
590  {
591  WB_DEBUG_PRINT(DebugLevel_Verbose, "%s exit (d) after WBTextBufferRefreshCache()\n", __FUNCTION__);
592 
594  return;
595  }
596 
597  // I've re-evaluated the max and min-max from the cache, so use that info
598 
599  pBuf->nMaxCol = nNewMax;
600 
601  if(nNewMinMax <= nNewMax) // NOTE: if cache is empty, nNewMinMax will be UINT_MAX
602  {
603  pBuf->nMinMaxCol = nNewMinMax; // sanity checked value
604  }
605  else
606  {
607  pBuf->nMinMaxCol = nNewMax; // don't put insane values in there (just in case)
608  // TODO: consider refactoring this code out once the algorithm is proved to work
609  }
610 
611  if(nNewLen < 0) // a line deletion
612  {
613  if(!nNewMax) // in case my cache has dwindled to nothing
614  {
615  WB_DEBUG_PRINT(DebugLevel_Verbose, "%s exit (c) after WBTextBufferRefreshCache()\n", __FUNCTION__);
616 
617  WBTextBufferRefreshCache(pBuf); // time to re-evaluate
618  }
619  else
620  {
621  WB_DEBUG_PRINT(DebugLevel_Verbose, "%s exit (c)\n", __FUNCTION__);
622  }
623 
624  return; // I'm done for delete. The rest is for updates
625  }
626 
627  // see if the length exceeds any of the maximums, and do an 'insertion sort'
628  // into my cache array if it does. Otherwise I can just leave.
629 
630  if(!nNewLen) // don't bother, it has a zero length
631  {
632  WB_DEBUG_PRINT(DebugLevel_Verbose, "%s exit (f)\n", __FUNCTION__);
633 
634  return; // I'm done - no need to re-insert this line into the cache (yet)
635  }
636 
637  if(nNewLen >= pBuf->nMaxCol) // bigger than maximum (or equal to it)
638  {
639  i2 = 0; // my insert point
640 
641  pBuf->nMaxCol = nNewLen; // new 'max'
642  if(nNewLen < nNewMinMax) // sanity test for empty cache
643  {
644  pBuf->nMinMaxCol = nNewLen; // may happen, if new cache is totally empty
645  }
646  }
647  else if(nNewLen >= pBuf->nMinMaxCol) // between min max and max
648  {
649  // find the place to insert this line into the cache
650  i2 = 0; // NOTE: platform-independent loop structure, probably not necessary, but...
651  while(i2 < TEXT_BUFFER_LINE_CACHE_SIZE && pBuf->aLineCacheLen[i2])
652  {
653  if(pBuf->aLineCacheLen[i2] < nNewLen) // the insert point
654  {
655  break;
656  }
657 
658  i2++; // don't forget this or infinite loop will result, heh
659  }
660  }
661  else
662  {
663  WB_DEBUG_PRINT(DebugLevel_Verbose, "%s exit (a)\n", __FUNCTION__);
664 
665  return; // I'm done - no need to re-insert this line into the cache (yet)
666  // NOTE: re-inserting the line into the cache would require re-doing the
667  // cache information from scratch. this is an optimization, so I'll
668  // avoid THAT operation until it's truly necessary.
669  }
670 
671  // at this point, 'i2' is the insertion point for the line length info
672 
673  if(i2 < TEXT_BUFFER_LINE_CACHE_SIZE) // sanity check, might happen
674  {
675  // insert at 'i2'
676  for(i1=TEXT_BUFFER_LINE_CACHE_SIZE - 1; i1 > i2; i1--)
677  {
678  pBuf->aLineCache[i1] = pBuf->aLineCache[i1 - 1];
679  pBuf->aLineCacheLen[i1] = pBuf->aLineCacheLen[i1 - 1];
680  }
681 
682  pBuf->aLineCache[i2] = nLine;
683  pBuf->aLineCacheLen[i2] = nNewLen;
684  }
685 
686  // everything should be ok now, unless I b0rked something
687 
688  WB_DEBUG_PRINT(DebugLevel_Verbose, "%s exit (b)\n", __FUNCTION__);
689 }
690 
692 {
693 int iLine, i1, i2, i3;
694 char *p1;
695 
696  if(!pBuf)
697  {
698  return;
699  }
700 
701  WB_DEBUG_PRINT(DebugLevel_Verbose, "%s\n", __FUNCTION__);
702 
703  // zero out the cache arrays
704  memset(pBuf->aLineCache, 0, sizeof(pBuf->aLineCache));
705  memset(pBuf->aLineCacheLen, 0, sizeof(pBuf->aLineCacheLen));
706 
707  pBuf->nMaxCol = pBuf->nMinMaxCol = 0; // initialize to zero
708 
709  if(!pBuf->nEntries)
710  {
711  return;
712  }
713  else if(pBuf->nEntries == 1)
714  {
715  if(pBuf->aLines[0])
716  {
717  pBuf->aLineCacheLen[0] = // refactored, do this assignment here instead
718  pBuf->nMaxCol = pBuf->nMinMaxCol = WBGetMBLength(pBuf->aLines[0]); // assign length to 'max' and 'minmax'
719 // if(pBuf->nMaxCol) 'refactored out' (left to document process, remove later)
720 // {
721 // pBuf->aLineCache[0] = 0; this value already assigned by 'memset'
722 // pBuf->aLineCacheLen[0] = pBuf->nMaxCol; refactored code does this
723 // }
724  }
725 
726  return;
727  }
728 
729  // the big loop
730 
731  for(iLine=0; iLine < pBuf->nEntries; iLine++)
732  {
733  // do not consider blank lines or lines with zero length
734  p1 = pBuf->aLines[iLine];
735 
736  if(!p1)
737  {
738  continue;
739  }
740 
741  i1 = WBGetMBLength(p1);
742 
743  if(!i1)
744  {
745  continue;
746  }
747 
748  if(i1 > pBuf->nMinMaxCol) // assume data is consistent
749  {
750  // perform an insertion sort into 'aLineCache'
751 
752  // step 1: find the insertion point
753 
754  i2 = 0; // NOTE: platform-independent loop structure, probably not necessary, but...
755  while(i2 < TEXT_BUFFER_LINE_CACHE_SIZE && pBuf->aLineCacheLen[i2])
756  {
757  if(pBuf->aLineCacheLen[i2] < i1) // the insert point
758  {
759  break;
760  }
761 
762  i2++; // don't forget this or infinite loop will result, heh
763  }
764 
765  if(i2 >= TEXT_BUFFER_LINE_CACHE_SIZE) // logic fail, clean it up
766  {
767  pBuf->nMaxCol = pBuf->aLineCacheLen[0]; // forced re-evaluation
768  pBuf->nMinMaxCol = pBuf->aLineCacheLen[TEXT_BUFFER_LINE_CACHE_SIZE - 1];
769  }
770  else if(i2 == TEXT_BUFFER_LINE_CACHE_SIZE - 1)
771  {
772  // special case, insert at the end
773  pBuf->aLineCache[i2] = iLine;
774  pBuf->aLineCacheLen[i2] = i1;
775 
776  pBuf->nMinMaxCol = i1; // the new 'min max'
777  }
778  else
779  {
780  // if this is the first line in the cache, update 'nMaxCol'
781  if(!i2) // NOTE: this test is done here 'cause of likely register optimization
782  {
783  pBuf->nMaxCol = i1;
784  }
785 
786  i3 = i2 + 1;
787  while(i3 < TEXT_BUFFER_LINE_CACHE_SIZE && pBuf->aLineCacheLen[i3])
788  {
789  i3++; // finding the end
790  }
791 
792  // move everything down using 'memmove'
793 
794  memmove(&(pBuf->aLineCache[i2 + 1]), &(pBuf->aLineCache[i2]),
795  (i3 - i2 - 1) * sizeof(pBuf->aLineCache[0]));
796  memmove(&(pBuf->aLineCacheLen[i2 + 1]), &(pBuf->aLineCacheLen[i2]),
797  (i3 - i2 - 1) * sizeof(pBuf->aLineCacheLen[0]));
798 
799  // use the final line's index and re-assign the 'max' and 'min max'
800  pBuf->nMinMaxCol = pBuf->aLineCacheLen[i3 - 1];
801 
802  // make sure the last element(s) have a zero length (TODO: optimize?)
803  while(i3 < TEXT_BUFFER_LINE_CACHE_SIZE)
804  {
805  pBuf->aLineCache[i3] = 0; // do this to, just because
806  pBuf->aLineCacheLen[i3] = 0;
807 
808  i3++;
809  }
810 
811  // and finally, insert this line's info at position 'i2'
812 
813  pBuf->aLineCache[i2] = iLine;
814  pBuf->aLineCacheLen[i2] = i1;
815  }
816  }
817  }
818 
819  WB_DEBUG_PRINT(DebugLevel_Verbose, "%s exit point\n", __FUNCTION__);
820 }
821 
822 // *********************************
823 // TEXT OBJECT CONSTRUCTION AND APIs
824 // *********************************
825 
826 TEXT_OBJECT *WBTextObjectConstructor(unsigned long cbStructSize, const char *szText, unsigned long cbLen, Window wIDOwner)
827 {
828 TEXT_OBJECT *pRval;
829 
830  pRval = (TEXT_OBJECT *)WBAlloc(sizeof(*pRval));
831 
832  if(pRval)
833  {
834 // pRval->vtable = &WBDefaultTextObjectVTable;
835 // pRval->ulTag = TEXT_OBJECT_TAG;
836 // pRval->vtable->init(pRval);
837 //
838 // pRval->wIDOwner = wIDOwner;
839 
840  WBInitializeInPlaceTextObject(pRval, wIDOwner);
841 
842  pRval->pText = WBAllocTextBuffer(szText, cbLen);
843  }
844 
845  return(pRval);
846 }
847 
849 {
850  if(WBIsValidTextObject(pObj))
851  {
852  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
853  "%s line %d: pObj iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
854  __FUNCTION__, __LINE__,
855  pObj->iRow, pObj->iCol,
856  pObj->rctSel.left, pObj->rctSel.top, pObj->rctSel.right, pObj->rctSel.bottom);
857 
858  pObj->vtable->destroy(pObj);
859 
860  WBFree(pObj);
861  }
862 }
863 
864 int WBTextObjectCalculateLineHeight(int iAscent, int iDescent) // consistently calculate line height from font ascent/descent
865 {
866 int iFontHeight;
867 
868  iFontHeight = iAscent + iDescent;
869 
870  // adjust font height to include line spacing (I'll use this to position the lines)
871  if(iDescent > MIN_LINE_SPACING / 2)
872  {
873  iFontHeight += iDescent / 2; // line spacing is 1/2 of descent, or MIN_LINE_SPACING
874  }
875  else
876  {
877  iFontHeight += MIN_LINE_SPACING;
878  }
879 
880  return iFontHeight;
881 }
882 
884  unsigned long (*callback)(TEXT_OBJECT *pThis, int nX, int nY),
885  void *pColorContextPointer)
886 {
887  if(!pThis)
888  {
889  return;
890  }
891 
892  pThis->pColorContext = pColorContextPointer;
893  pThis->pColorContextCallback = callback;
894 
895  if(pThis->pColorContextCallback)
896  {
897  pThis->pColorContextCallback(pThis, -1, -1); // to refresh it
898  }
899 }
900 
901 
902 
903 
904 // *****************************
905 // TEXT OBJECT UTILITY FUNCTIONS
906 // *****************************
907 
908 // INTERNAL-ONLY utilities that are NOT part of the vtable
909 
910 static void __internal_free_undo_redo_buffer(void *pBuffer)
911 {
912  if(pBuffer)
913  {
914  // TODO: validate that it's an undo/redo buffer
915 
916  struct s_internal_undo_redo_buffer *pU = (struct s_internal_undo_redo_buffer *)pBuffer;
917 
918  do
919  {
920  struct s_internal_undo_redo_buffer *pUsa = pU;
921 
922  pU = pU->pNext;
923  WBFree(pUsa); // for now just do this
924 
925  } while(pU);
926  }
927 }
928 
929 #define UNDO_LIMIT 256
930 
931 // NULL 'prctStartSel' or 'prctEndSel' implies 'NONE' selected, i.e. {0,0,0,0}
932 static void __internal_add_undo(TEXT_OBJECT *pThis, int iOperation, int iSelMode,
933  int iStartRow, int iStartCol, const WB_RECT *prctStartSel,
934  const char *pStartText, int cbStartText,
935  int iEndRow, int iEndCol, const WB_RECT *prctEndSel,
936  const char *pEndText, int cbEndText)
937 {
938 int cbLen, cbLen2, i1;
939 struct s_internal_undo_redo_buffer *pUndo, *pTU, *pTU2;
940 
941 
942  if(!WBIsValidTextObject(pThis))
943  {
944  return;
945  }
946 
947  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
948  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
949  __FUNCTION__, __LINE__,
950  pThis->iRow, pThis->iCol,
951  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
952 
953  pThis->iBlinkState = CURSOR_BLINK_RESET; // this affects the cursor blink, basically resetting it whenever I edit something
954 
955  cbLen = cbLen2 = 0;
956 
957  if(pStartText)
958  {
959  if(cbStartText < 0)
960  {
961  cbStartText = strlen(pStartText); // TODO: WBGetMBLength ?
962  }
963 
964  if(cbStartText > 0)
965  {
966  cbLen += cbStartText;
967  }
968  }
969 
970  if(pEndText)
971  {
972  if(cbEndText < 0)
973  {
974  cbEndText = strlen(pEndText); // TODO: WBGetMBLength?
975  }
976 
977  if(cbEndText > 0)
978  {
979  cbLen2 += cbEndText;
980  }
981  }
982 
983  pUndo = (struct s_internal_undo_redo_buffer *)WBAlloc(cbLen + cbLen2 + 4 + sizeof(*pUndo));
984  if(!pUndo)
985  {
986  // TODO: walk the 'undo' chain and start removing stuff, and retry the alloc
987 
988  WB_ERROR_PRINT("ERROR - %s - unable to create undo buffer, errno=%d\n", __FUNCTION__, errno);
989  return;
990  }
991 
992  pUndo->iOperation = iOperation;
993  pUndo->iSelMode = iSelMode;
994 
995  if(prctStartSel)
996  {
997  memcpy(&(pUndo->rctSelOld), prctStartSel, sizeof(WB_RECT));
998  }
999  else
1000  {
1001  memset(&(pUndo->rctSelOld), 0, sizeof(WB_RECT));
1002  }
1003 
1004  if(prctEndSel)
1005  {
1006  memcpy(&(pUndo->rctSelNew), prctEndSel, sizeof(WB_RECT));
1007  }
1008  else
1009  {
1010  memset(&(pUndo->rctSelNew), 0, sizeof(WB_RECT));
1011  }
1012 
1013  // this code should work on multi-byte characters as well...
1014 
1015  pUndo->nOld = cbLen;
1016  if(cbLen)
1017  {
1018  memcpy(pUndo->aData, pStartText, cbLen);
1019  pUndo->aData[cbLen++] = 0; // just because
1020  }
1021 
1022  pUndo->nOld = cbLen;
1023 
1024  if(cbLen2)
1025  {
1026  memcpy(pUndo->aData + cbLen, pEndText, cbLen2);
1027  pUndo->aData[cbLen + cbLen2++] = 0; // just because
1028  }
1029 
1030  pUndo->nNew = cbLen2;
1031 
1032  // insert undo buffer into the 'chain'
1033 
1034  pTU = pUndo->pNext = (struct s_internal_undo_redo_buffer *)(pThis->pUndo);
1035  pThis->pUndo = pUndo;
1036 
1037  // NOW walk the chain and remove things past a certain point. For now, UNDO_LIMIT
1038  // NOTE: this is a compromise - the alternative is a double-link list and an 'end' pointer
1039  // along with a running count of items. If the number of undo/redo items becomes
1040  // VERY large, that might be a better alternative. So for now I use a single-link list.
1041 
1042  if(pTU)
1043  {
1044  for(i1=1; i1 < UNDO_LIMIT && pTU->pNext; pTU = pTU->pNext, i1++) { }
1045  // on a modern CPU, this shouldn't take more than a few microseconds
1046 
1047  if(pTU->pNext) // will only be true if I have too many entries
1048  {
1049  // free and unhook the chain
1050  pTU2 = pTU->pNext;
1051  pTU->pNext = NULL; // generally safer THIS way
1052 
1053  __internal_free_undo_redo_buffer(pTU2); // NOW free what was once chained from here
1054  }
1055  }
1056 
1057  // whenever I add an 'undo' HERE, I screw up the 'redo' so blast it away if it exists
1058  if(pThis->pRedo)
1059  {
1060  pTU = pThis->pRedo;
1061  pThis->pRedo = NULL;
1062 
1063  __internal_free_undo_redo_buffer(pTU);
1064  }
1065 }
1066 
1067 
1068 #if 0 /* not currently in use - uncomment to implement its functionality */
1069 static void __internal_add_redo(TEXT_OBJECT *pThis, struct s_internal_undo_redo_buffer *pUndo)
1070 {
1071 struct s_internal_undo_redo_buffer *pRedo;
1072 int cbLen;
1073 
1074 
1075  if(!WBIsValidTextObject(pThis))
1076  {
1077  return;
1078  }
1079 
1080  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
1081  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
1082  __FUNCTION__, __LINE__,
1083  pThis->iRow, pThis->iCol,
1084  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
1085 
1086  // convert an 'undo' into a 'redo' and add it to the 'redo' chain
1087  // essentially 'just a copy'
1088 
1089  cbLen = pUndo->nOld + pUndo->nNew + sizeof(*pUndo);
1090 
1091  pRedo = (struct s_internal_undo_redo_buffer *)WBAlloc(cbLen + 2);
1092 
1093  if(!pRedo)
1094  {
1095  // TODO: walk the 'undo' and 'redo' chains and start removing stuff, and retry the alloc
1096 
1097  WB_ERROR_PRINT("ERROR - %s - unable to create redo buffer, errno=%d\n", __FUNCTION__, errno);
1098  return;
1099  }
1100 
1101  memcpy(pRedo, pUndo, cbLen);
1102 
1103  // now add it to the redo chain
1104 
1105  pRedo->pNext = (struct s_internal_undo_redo_buffer *)(pThis->pRedo);
1106  pThis->pRedo = pRedo;
1107 }
1108 #endif // 0
1109 
1110 #if 0 /* not currently in use - uncomment to implement its functionality */
1111 static void __internal_perform_undo(TEXT_OBJECT *pThis, struct s_internal_undo_redo_buffer *pUndo)
1112 {
1113 struct s_internal_undo_redo_buffer *pDo = NULL;
1114 int iOldSel;
1115 
1116 
1117  pDo = pDo; // TEMPORARY, warning avoidance
1118 
1119  if(!WBIsValidTextObject(pThis) || !pUndo)
1120  {
1121  return;
1122  }
1123 
1124  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
1125  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
1126  __FUNCTION__, __LINE__,
1127  pThis->iRow, pThis->iCol,
1128  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
1129 
1130  // first, make sure the selection mode is correct
1131 
1132  iOldSel = pThis->iSelMode;
1133  pThis->iSelMode = pUndo->iSelMode;
1134 
1135  if(pUndo->iOperation == UNDO_SELECT)
1136  {
1137  }
1138  else if(pUndo->iOperation == UNDO_DELETE)
1139  {
1140  // perform an insert of 'Old Text' data at the appropriate cursor spot
1141  }
1142  else if(pUndo->iOperation == UNDO_INSERT)
1143  {
1144  // perform a delete of 'new text' data at the appropriate cursor spot
1145  }
1146  else if(pUndo->iOperation == UNDO_REPLACE)
1147  {
1148  // replace 'new text' with 'old text' at the appropriate cursor spot
1149  }
1150  else if(pUndo->iOperation == UNDO_INDENT)
1151  {
1152  // indents a block of text
1153  // to indent, start col will be 0, end col will be the indent
1154  // to un-indent, start col will be > 0, end col will be 0
1155  // The start/end rows define the block of lines to be indented or un-indented
1156 
1157  }
1158 
1159  // re-select the old selection using the old select method
1160 
1161 
1162  // restore cursor to appropriate spot
1163 
1164 
1165 // // restore the current selection method if restored selection is empty
1166 // pThis->iSelMode = iOldSel;
1167 
1168 
1169  // add a redo buffer that's a copy of the undo operation
1170  __internal_add_redo(pThis, pUndo);
1171 }
1172 #endif // 0
1173 
1174 #if 0 /* not currently in use - uncomment to implement its functionality */
1175 static void __internal_perform_redo(TEXT_OBJECT *pThis, struct s_internal_undo_redo_buffer *pRedo)
1176 {
1177 struct s_internal_undo_redo_buffer *pNewUndo;
1178 int iOldSel, cbLen;
1179 
1180 
1181  if(!WBIsValidTextObject(pThis) || !pRedo)
1182  {
1183  return;
1184  }
1185 
1186  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
1187  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
1188  __FUNCTION__, __LINE__,
1189  pThis->iRow, pThis->iCol,
1190  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
1191 
1192  cbLen = pRedo->nOld + pRedo->nNew + sizeof(*pRedo);
1193 
1194  // first, make sure the selection mode is correct
1195 
1196  iOldSel = pThis->iSelMode;
1197  pThis->iSelMode = pRedo->iSelMode;
1198 
1199  if(pRedo->iOperation == UNDO_SELECT)
1200  {
1201  }
1202  else if(pRedo->iOperation == UNDO_DELETE)
1203  {
1204  // perform an insert of 'new Text' data at the appropriate cursor spot
1205  }
1206  else if(pRedo->iOperation == UNDO_INSERT)
1207  {
1208  // perform a delete of 'old text' data at the appropriate cursor spot
1209  }
1210  else if(pRedo->iOperation == UNDO_REPLACE)
1211  {
1212  // replace 'old text' with 'new text' at the appropriate cursor spot
1213  }
1214  else if(pRedo->iOperation == UNDO_INDENT)
1215  {
1216  // indents a block of text
1217  // to indent, start col will be 0, end col will be the indent
1218  // to un-indent, start col will be > 0, end col will be 0
1219  // The start/end rows define the block of lines to be indented or un-indented
1220 
1221  }
1222 
1223  // re-select the new selection using the applicable select method
1224 
1225 
1226  // restore cursor to appropriate spot
1227 
1228 
1229 // // restore the current selection method if restored selection is empty
1230 // pThis->iSelMode = iOldSel;
1231 
1232 
1233  // now I must add the 'redo' operation 'as-is' to the 'undo' chain, but NOT blast away the 'redo' chain
1234  // I'll make a copy of it first, since the caller will need to manage the undo/redo pointers
1235 
1236  pNewUndo = (struct s_internal_undo_redo_buffer *)WBAlloc(cbLen + 2);
1237 
1238  if(!pNewUndo)
1239  {
1240  // TODO: walk the 'undo' and 'redo' chains and start removing stuff, and retry the alloc
1241 
1242  WB_ERROR_PRINT("ERROR - %s - unable to create undo buffer from redo buffer, errno=%d\n", __FUNCTION__, errno);
1243  return;
1244  }
1245 
1246  memcpy(pNewUndo, pRedo, cbLen);
1247 
1248  // now add it to the undo chain so I can 'undo the re-do' if I want to
1249 
1250  pNewUndo->pNext = (struct s_internal_undo_redo_buffer *)(pThis->pUndo);
1251  pThis->pUndo = pNewUndo;
1252 }
1253 #endif // 0
1254 
1255 // ---------------------------------------------------------------------------
1256 // __internal_get_selected_text - arbitrary text retrieval (internal only)
1257 // returns WBAlloc'd string pointer
1258 //
1259 // iRow is the starting row position
1260 // iCol is the starting column position
1261 // iEndRow is the ending row position. In modes OTHER than 'box mode', this may equal 'iCol'
1262 // iEndCol is the ending column position. In modes OTHER than 'box mode' this may be LESS than 'iCol'
1263 static char * __internal_get_selected_text(const TEXT_OBJECT *pThis,
1264  int iRow, int iCol, int iEndRow, int iEndCol)
1265 {
1266 int i1, i2, i3, cb1, cbLF=0;
1267 char *p1, *pRval = NULL;
1268 const char *szLineFeed = NULL;
1269 TEXT_BUFFER *pTB;
1270 int iIsBoxMode, iIsLineMode;
1271 
1272 
1273  if(WBIsValidTextObject(pThis))
1274  {
1275  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
1276  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
1277  __FUNCTION__, __LINE__,
1278  pThis->iRow, pThis->iCol,
1279  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
1280 
1281  WB_DEBUG_PRINT(DebugLevel_Verbose, "calling %s(%d,%d,%d,%d)\n", __FUNCTION__, iRow, iCol, iEndRow, iEndCol);
1282 
1283  if(!pThis->pText)
1284  {
1285  return WBCopyString(""); // return a blank string
1286  }
1287 
1288  if(iRow < 0)
1289  {
1290  iRow = 0;
1291  }
1292  if(iCol < 0)
1293  {
1294  iCol = 0;
1295  }
1296  if(iEndRow < 0)
1297  {
1298  iEndRow = INT_MAX;
1299  }
1300  if(iEndCol < 0)
1301  {
1302  iEndCol = INT_MAX;
1303  }
1304 
1305  if(iRow == iEndRow)
1306  {
1307  iIsBoxMode = iIsLineMode = 0;
1308  }
1309  else
1310  {
1311  if(pThis->iSelMode == SelectMode_BOX)
1312  {
1313  iIsBoxMode = 1;
1314  iIsLineMode = 0;
1315  }
1316  else if(pThis->iSelMode == SelectMode_LINE)
1317  {
1318  iIsBoxMode = 0;
1319  iIsLineMode = 1;
1320  }
1321  else // char and default
1322  {
1323  iIsBoxMode = 0;
1324  iIsLineMode = 0;
1325  }
1326  }
1327 
1328  if(iRow > iEndRow // row exceeds ending row
1329  || (iRow == iEndRow && iCol >= iEndCol) // single row with no 'width'
1330  /* || (box mode && iCol >= iEndCol) */) // box mode requires iCol < iEndCol
1331  {
1332  return WBCopyString(""); // return a blank string
1333  }
1334 
1335  pTB = (TEXT_BUFFER *)(pThis->pText);
1336 
1337  // NOTE: when iRow == iEndRow, both box mode and line mode revert to 'stream mode'
1338  // and the 'iIsBoxMode' and 'iIsStreamMode' flags will both be zero
1339 
1340  if(iIsBoxMode)
1341  {
1342  cb1 = (iEndRow - iRow + 1) * (2 + iEndCol - iCol);
1343  }
1344  else
1345  {
1346  // FOR NOW just go through the TEXT_BUFFER array, determine the length (later cache it)
1347 
1348  for(i1=iRow, cb1=4; i1 < pTB->nEntries && i1 <= iEndRow; i1++)
1349  {
1350  i2 = strlen(pTB->aLines[i1]); // use 'strlen' here, as I want the REAL length
1351 
1352  // last line in 'normal' select mode limits width - 'iEndCol' may be 0
1353  if(i1 == iEndRow // i.e. "the last line" in the selected area
1354  && !iIsLineMode) // NOT 'line mode' (will be always false for single-row selects)
1355  {
1356  if(i2 > iEndCol) // trim the indicated length of the line accordingly
1357  {
1358  i2 = WBGetMBCharPtr(pTB->aLines[i1], iEndCol, NULL) - pTB->aLines[i1];
1359  }
1360  }
1361 
1362  cb1 += i2 + 2; // do this regardless (it's only to count up max string length)
1363  }
1364  }
1365 
1366  // now build the string using the specified line ending and selection mode
1367 
1368  szLineFeed = __internal_get_line_ending_text(pThis->iLineFeed);
1369 
1370  if(!szLineFeed) // single-line
1371  {
1372  if(pTB->nEntries > 1)
1373  {
1374  szLineFeed = __internal_get_line_ending_text(LineFeed_DEFAULT); // fallback with multi-line data
1375  }
1376  }
1377 
1378  if(szLineFeed)
1379  {
1380  cbLF = strlen(szLineFeed);
1381  }
1382 
1383  pRval = WBAlloc(cb1); // allocate the buffer
1384 
1385  if(pRval)
1386  {
1387  p1 = pRval;
1388  for(i1=iRow, p1 = pRval; i1 < pTB->nEntries && i1 <= iEndRow; i1++)
1389  {
1390  char *pTheLine = pTB->aLines[i1];
1391 
1392  // TODO: validate pRval + cb1 > p1 + length of "the stuff that follows"
1393  i2 = i3 = strlen(pTheLine); // the TRUE 'binary' length
1394 
1395  // TODO: for box mode limit the width on all lines and pad with white space as needed
1396 
1397  // TODO: handle hard tab translation? For now "leave it". Later, I should use a character
1398  // that prints as 'white space' but translates as a 'hard tab'.
1399  // Example might be 0xA0, i.e. a ' ' + 80H, filling up spaces to the tab point. Delete
1400  // and insert would have to compensate for this. This function ALSO would have to compensate,
1401  // translating tabs to spaces for box mode, and to hard tabs for everything else when 'select all'
1402  // is not in effect and tabs are preserved.
1403  // For 'select all', hard tabs (when preserved) would be translated into a minimum number of
1404  // tab characters; otherwise they would translate into spaces. Also a hard tab should be displayed
1405  // differently (when preserved), such as a greyed |--> or similar.
1406  // When 'do not preserve tabs' is set, inserted tabs should automatically become spaces. ONLY when
1407  // the tabs are being preserved should the translation take place.
1408  //
1409  // CASE FOR USING 'A0' FOR HARD TAB
1410  // a) UTF8 does not use it as a marker for multi-byte character data - http://en.wikipedia.org/wiki/UTF-8
1411  // b) it's basically a ' ' with the high bit set
1412  // c) it's not used for anything significant in 8-bit ASCII other than "non-breaking space" (i.e. &nbsp;)
1413  // d) if it's imported, it will translate to actual spaces
1414  // e) if it's exported by accident, it probably won't matter (other than formatting)
1415  // f) a definition NOW exists - see HARD_TAB_CHAR (text_object.h)
1416 
1417  // last line in 'normal' select mode limits width - 'iEndCol' may be 0
1418  if(i1 == iEndRow // i.e. "the last line" in the selected area
1419  || iIsBoxMode) // box mode limits length differently on all lines
1420  {
1421  if(iIsBoxMode)
1422  {
1423  if(i2 >= iEndCol)
1424  {
1425  i2 = WBGetMBCharPtr(pTheLine, iEndCol, NULL) - WBGetMBCharPtr(pTheLine, iCol, NULL);
1426  }
1427  else if(iCol > 0)
1428  {
1429  i2 -= WBGetMBCharPtr(pTheLine, iCol, NULL) - pTheLine;
1430  }
1431  }
1432  else if(!iIsLineMode)
1433  {
1434  if(i2 >= iEndCol)
1435  {
1436  i2 = WBGetMBCharPtr(pTheLine, iEndCol, NULL) - pTheLine;
1437  }
1438  }
1439  else if(iEndCol == 0)
1440  {
1441  i2 = 0; // typically 'last row'
1442  }
1443  }
1444 
1445  // check for stream mode on the first line, and start with 'iCol'
1446  if(!iIsBoxMode && !iIsLineMode && i1 == iRow) // on first line, start at 'iCol'
1447  {
1448  i2 -= WBGetMBCharPtr(pTheLine, iCol, NULL) - pTheLine; // subtract starting column offset for total length
1449  }
1450 
1451  if(i2 > 0) // might be < 0 depending
1452  {
1453  if(iIsLineMode || i1 > iCol) // multi-line select NOT on first line, or line mode
1454  {
1455  memcpy(p1, pTheLine, i2);
1456  }
1457  else
1458  {
1459  memcpy(p1, WBGetMBCharPtr(pTheLine, iCol, NULL), i2);
1460  }
1461  p1 += i2;
1462  }
1463  else
1464  {
1465  i2 = 0; // for box mode, mostly
1466  }
1467 
1468  if(iIsBoxMode) // iRow < iEndRow also
1469  {
1470  // for box mode, pad any 'remaining' length with space
1471  i2 = iEndCol - iCol // the ending width we're SUPPOSED to have
1472  - WBGetMBLength(WBGetMBCharPtr(pTheLine, iCol, NULL)); // the actual length in 'columns' starting at 'iCol'
1473 
1474  // I need THAT MANY white spaces for box mode
1475  if(i2 > 0)
1476  {
1477  memset(p1, ' ', i2);
1478  p1 += i2; // now the width should be exactly 'iEndCol - iCol'
1479  }
1480  }
1481 
1482  // don't add final line feed in 'stream' mode or in 'line' mode if it ends on column 0
1483 
1484  if(iIsBoxMode || // box mode ALWAYS
1485  (iIsLineMode && iEndCol > 0) || // I'm in line mode and NOT ending on column 0 on the last line
1486  i1 < iEndRow) // not the last line
1487  {
1488  if(szLineFeed)
1489  {
1490  memcpy(p1, szLineFeed, cbLF);
1491  p1 += cbLF;
1492  }
1493  }
1494  }
1495 
1496  *p1 = 0;
1497  }
1498  }
1499 
1500  return pRval;
1501 }
1502 
1503 static void __internal_invalidate_cursor(const TEXT_OBJECT *pThis, int bPaintFlag)
1504 {
1505 WB_RECT rctCursor;
1506 
1507  if(WBIsValidTextObject(pThis) && pThis->wIDOwner != None)
1508  {
1509  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
1510  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
1511  __FUNCTION__, __LINE__,
1512  pThis->iRow, pThis->iCol,
1513  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
1514 
1515  rctCursor.left = pThis->iCursorX - 1;
1516  rctCursor.top = pThis->iCursorY - 1;
1517  rctCursor.bottom = rctCursor.top + pThis->iCursorHeight + 2;
1518 
1519  if(pThis->iInsMode == InsertMode_OVERWRITE)
1520  {
1521  rctCursor.right = rctCursor.left + pThis->iFontWidth;
1522  }
1523  else
1524  {
1525  rctCursor.right = rctCursor.left + 3;
1526  }
1527 
1528  WBInvalidateRect(pThis->wIDOwner, &rctCursor, bPaintFlag);
1529  }
1530 }
1531 
1532 static void __internal_invalidate_rect(TEXT_OBJECT *pThis, WB_RECT *pRect, int bPaintFlag)
1533 {
1534 WB_RECT rctInvalid;
1535 
1536  if(WBIsValidTextObject(pThis) && pThis->wIDOwner != None)
1537  {
1538  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
1539  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
1540  __FUNCTION__, __LINE__,
1541  pThis->iRow, pThis->iCol,
1542  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
1543 
1544  if(pThis->rctView.right <= pThis->rctView.left || pThis->rctView.bottom <= pThis->rctView.top ||
1545  pThis->rctWinView.right <= pThis->rctWinView.left || pThis->rctWinView.bottom <= pThis->rctWinView.top ||
1546  memcmp(&pThis->rctView, &pThis->rctViewOld, sizeof(pThis->rctView)) ||
1547  memcmp(&pThis->rctWinView, &pThis->rctWinViewOld, sizeof(pThis->rctWinView)))
1548  {
1549  XClientMessageEvent evt;
1550 
1551  bzero(&evt, sizeof(evt));
1552  evt.type = ClientMessage;
1553  evt.display = WBGetWindowDisplay(pThis->wIDOwner);
1554  evt.window = pThis->wIDOwner;
1555  evt.message_type = aRECALC_LAYOUT;
1556  evt.format = 32;
1557 
1558  WBPostPriorityEvent(pThis->wIDOwner, (XEvent *)&evt); // async (for now)
1559 
1560  memcpy(&pThis->rctViewOld, &pThis->rctView, sizeof(pThis->rctViewOld));
1561  memcpy(&pThis->rctWinViewOld, &pThis->rctWinView, sizeof(pThis->rctWinViewOld));
1562 
1563  WB_DEBUG_PRINT(DebugLevel_Light | DebugSubSystem_EditWindow | DebugSubSystem_Expose,
1564  "%s,%d - force recalc layout, window %u (%08xH)\n",
1565  __FUNCTION__, __LINE__, (int)pThis->wIDOwner, (int)pThis->wIDOwner);
1566  }
1567 
1568  if(!pRect)
1569  {
1570  WB_DEBUG_PRINT(DebugLevel_Medium | DebugSubSystem_EditWindow | DebugSubSystem_Expose,
1571  "%s - invalidate entire window %u (%08xH)\n",
1572  __FUNCTION__, (int)pThis->wIDOwner, (int)pThis->wIDOwner);
1573 
1574  WBInvalidateRect(pThis->wIDOwner, NULL, bPaintFlag);
1575  return;
1576  }
1577 
1578  memcpy(&rctInvalid, pRect, sizeof(*pRect));
1579 
1580  if(pThis->rctWinView.left - MIN_BORDER_SPACING > rctInvalid.left)
1581  {
1582  rctInvalid.left = pThis->rctWinView.left - MIN_BORDER_SPACING;
1583  }
1584  if(pThis->rctWinView.right + MIN_BORDER_SPACING < rctInvalid.right)
1585  {
1586  rctInvalid.right = pThis->rctWinView.right + MIN_BORDER_SPACING;
1587  }
1588  if(pThis->rctWinView.top - MIN_BORDER_SPACING > rctInvalid.top)
1589  {
1590  rctInvalid.top = pThis->rctWinView.top - MIN_BORDER_SPACING;
1591  }
1592  if(pThis->rctWinView.bottom + MIN_BORDER_SPACING < rctInvalid.bottom)
1593  {
1594  rctInvalid.bottom = pThis->rctWinView.bottom + MIN_BORDER_SPACING;
1595  }
1596 
1597  WBInvalidateRect(pThis->wIDOwner, &rctInvalid, bPaintFlag);
1598 
1599  WB_DEBUG_PRINT(DebugLevel_Verbose, "%s line %d - invalidate %d,%d,%d,%d for %u (%08xH)\n",
1600  __FUNCTION__, __LINE__,
1601  rctInvalid.left, rctInvalid.top, rctInvalid.right, rctInvalid.bottom,
1602  (int)pThis->wIDOwner, (int)pThis->wIDOwner);
1603 
1604  }
1605 // else
1606 // {
1607 // if(!WBIsValidTextObject(pThis))
1608 // {
1609 // WB_DEBUG_PRINT(DebugLevel_Verbose, "%s line %d - invalid text object\n", __FUNCTION__, __LINE__);
1610 // }
1611 // else
1612 // {
1613 // WB_DEBUG_PRINT(DebugLevel_Verbose, "%s line %d - owner window ID is 'None'\n", __FUNCTION__, __LINE__);
1614 // }
1615 // }
1616 }
1617 
1618 // NOTE: iStartRow and iStartCol may be 0 but not negative
1619 // iEndRow and iEndCol can be negative to indicate "all"
1620 static void __internal_calc_rect(const TEXT_OBJECT *pThis, WB_RECT *pRect,
1621  int iStartRow, int iStartCol, int iEndRow, int iEndCol)
1622 {
1623 int iFontHeight;
1624 TEXT_BUFFER *pBuf;
1625 
1626  if(!pRect || !WBIsValidTextObject(pThis))
1627  {
1628  if(pRect)
1629  {
1630  memset(pRect, 0, sizeof(*pRect));
1631  }
1632 
1633  return;
1634  }
1635 
1636  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
1637  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
1638  __FUNCTION__, __LINE__,
1639  pThis->iRow, pThis->iCol,
1640  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
1641 
1642  pBuf = (TEXT_BUFFER *)(pThis->pText); // might be NULL, must check before use
1643 
1644  if(iEndRow < 0)
1645  {
1646  if(pBuf && pThis->iLineFeed != LineFeed_NONE)
1647  {
1648  iEndRow = pBuf->nEntries;
1649  }
1650  else
1651  {
1652  iEndRow = 0;
1653  }
1654  }
1655 
1656  // examine the viewpoint rect, font height, and other info
1657 
1658  iFontHeight = pThis->iAsc + pThis->iDesc;
1659 
1660  if(pThis->iLineFeed == LineFeed_NONE) // i.e. SINGLE LINE (ignore row)
1661  {
1662  // top of line will always be centered
1663 
1664  pRect->top = pThis->rctWinView.top
1665  + (pThis->rctWinView.bottom - pThis->rctWinView.top
1666  - pThis->iAsc - pThis->iDesc)
1667  / 2;
1668 
1669  pRect->bottom = pRect->top + iFontHeight; // always
1670 
1671  pRect->left = pThis->rctWinView.left
1672  + pThis->iFontWidth * (iStartCol - pThis->rctView.left); // can be negative
1673 
1674  if(iEndCol < 0)
1675  {
1676  pRect->right = INT_MAX;
1677  }
1678  else
1679  {
1680  pRect->right = pThis->iFontWidth * (iEndCol - iStartCol + 1);
1681  }
1682  }
1683  else
1684  {
1685 // pThis->rctWinView.top; what did I intend to do here???
1686 
1687  if(iStartRow == iEndRow) // single row
1688  {
1689  pRect->left = pThis->rctWinView.left
1690  + pThis->iFontWidth * (iStartCol - pThis->rctView.left); // can be negative
1691 
1692  if(iEndCol < 0)
1693  {
1694  pRect->right = INT_MAX;
1695  }
1696  else
1697  {
1698  pRect->right = pThis->iFontWidth * (iEndCol - iStartCol + 1);
1699  }
1700 
1701  pRect->top = pThis->rctWinView.top; // already calculated from earlier
1702  pRect->bottom = pRect->top + iFontHeight; // always
1703  }
1704  else // multiple rows implies including the entire line unless ending column is 0 [in which case last line is excluded]
1705  {
1706  // ignore columns, use the entire viewport width
1707  pRect->left = pThis->rctWinView.left;
1708  pRect->right = INT_MAX; //pThis->rctWinView.right;
1709 
1710  iFontHeight = WBTextObjectCalculateLineHeight(pThis->iAsc, pThis->iDesc); // height PLUS inter-line spacing
1711 
1712  pRect->top = pThis->rctWinView.top
1713  + iFontHeight * (iStartRow - pThis->rctView.top);
1714 
1715  if(iEndCol > 0)
1716  {
1717  pRect->bottom = pThis->rctWinView.top
1718  + iFontHeight * (iEndRow + 1 - pThis->rctView.top);
1719  }
1720  else // nothing selected on last row
1721  {
1722  pRect->bottom = pThis->rctWinView.top
1723  + iFontHeight * (iEndRow - pThis->rctView.top);
1724  }
1725 
1726  WB_DEBUG_PRINT(DebugLevel_Verbose, "%s line %d - merged rect %d,%d,%d,%d\n",
1727  __FUNCTION__, __LINE__,
1728  pRect->left, pRect->top, pRect->right, pRect->bottom);
1729  }
1730  }
1731 }
1732 
1733 static void __internal_merge_rect(const TEXT_OBJECT *pThis, WB_RECT *pRect,
1734  int iStartRow, int iStartCol, int iEndRow, int iEndCol)
1735 {
1736 WB_RECT rctMerge;
1737 
1738  if(!pRect || !WBIsValidTextObject(pThis))
1739  {
1740  if(pRect)
1741  {
1742  memset(pRect, 0, sizeof(*pRect));
1743  }
1744 
1745  return;
1746  }
1747 
1748  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
1749  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
1750  __FUNCTION__, __LINE__,
1751  pThis->iRow, pThis->iCol,
1752  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
1753 
1754  __internal_calc_rect(pThis, &rctMerge, iStartRow, iStartCol, iEndRow, iEndCol);
1755 
1756  if(rctMerge.left < pRect->left)
1757  {
1758  pRect->left = rctMerge.left;
1759  }
1760  if(rctMerge.top < pRect->top)
1761  {
1762  pRect->top = rctMerge.top;
1763  }
1764  if(rctMerge.right > pRect->right)
1765  {
1766  pRect->right = rctMerge.right;
1767  }
1768  if(rctMerge.bottom > pRect->bottom)
1769  {
1770  rctMerge.bottom = pRect->bottom;
1771  }
1772 }
1773 
1774 
1775 // ****************************
1776 // TEXT OBJECT VTABLE FUNCTIONS
1777 // ****************************
1778 
1779 static void __internal_destroy(TEXT_OBJECT *pThis)
1780 {
1781  if(WBIsValidTextObject(pThis))
1782  {
1783  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
1784  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
1785  __FUNCTION__, __LINE__,
1786  pThis->iRow, pThis->iCol,
1787  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
1788 
1789  // TODO: additional validation
1790 
1791  if(pThis->pText)
1792  {
1793  // TODO: validate pointer
1794  WBFreeTextBuffer((TEXT_BUFFER *)pThis->pText);
1795  pThis->pText = NULL;
1796  }
1797 
1798  // now for the undo/redo buffers
1799 
1800  __internal_free_undo_redo_buffer(pThis->pUndo);
1801  pThis->pUndo = NULL;
1802  __internal_free_undo_redo_buffer(pThis->pRedo);
1803  pThis->pRedo = NULL;
1804  }
1805 }
1806 
1807 static void __internal_init(TEXT_OBJECT *pThis)
1808 {
1809  pThis->ulTag = TEXT_OBJECT_TAG;
1810  pThis->wIDOwner = None;
1811 
1812  bzero(&pThis->rctSel, sizeof(pThis->rctSel));
1813  bzero(&pThis->rctHighLight, sizeof(pThis->rctHighLight));
1814 
1815  pThis->iFileType = 0; // plain text
1816  pThis->iLineFeed = LineFeed_DEFAULT;
1817  pThis->iInsMode = 1; // insert
1818  pThis->iSelMode = 0; // normal
1819  pThis->iScrollMode = 0; // normal
1820  pThis->iRow = 0;
1821  pThis->iCol = 0;
1822  pThis->iPos = 0; // reserved
1823  bzero(&pThis->rctView, sizeof(pThis->rctView));
1824  bzero(&pThis->rctWinView, sizeof(pThis->rctWinView));
1825  bzero(&pThis->rctViewOld, sizeof(pThis->rctViewOld));
1826  bzero(&pThis->rctWinViewOld, sizeof(pThis->rctWinViewOld));
1827  pThis->pText = NULL;
1828  pThis->pUndo = NULL;
1829  pThis->pRedo = NULL;
1830 
1831  // get initial default highlight colors
1832 
1833  {
1834  char szHFG[16], szHBG[16];
1835  Colormap colormap = DefaultColormap(WBGetDefaultDisplay(), DefaultScreen(WBGetDefaultDisplay()));
1836 
1837 //#define LOAD_COLOR(X,Y,Z) \
1838 // if(CHGetResourceString(WBGetDefaultDisplay(), X, Y, sizeof(Y)) <= 0) \
1839 // { WB_WARN_PRINT("%s - WARNING: can't find color %s, using default value %s\n", \
1840 // __FUNCTION__, X, Z); strcpy(Y,Z); }
1841 //
1842 // LOAD_COLOR("selected_bg_color", szHBG, "#0040FF"); // a slightly greenish blue for the 'selected' BG color
1843 // LOAD_COLOR("selected_fg_color", szHFG, "white"); // white FG when selected
1844 //
1845 //#undef LOAD_COLOR
1846 
1847 #define COPY_COLOR_NAME(X,Y,Z) {const char *pX = X(WBGetDefaultDisplay()); if(pX) strncpy(Y,pX,sizeof(Y)); else strncpy(Y,Z,sizeof(Y));}
1850 
1851  XParseColor(WBGetDefaultDisplay(), colormap, szHFG, &(pThis->clrHFG));
1852  XAllocColor(WBGetDefaultDisplay(), colormap, &(pThis->clrHFG)); // TODO: calculate missing pieces myself?
1853  XParseColor(WBGetDefaultDisplay(), colormap, szHBG, &(pThis->clrHBG));
1854  XAllocColor(WBGetDefaultDisplay(), colormap, &(pThis->clrHBG)); // TODO: calculate missing pieces myself?
1855  }
1856 
1857  // TODO: do I re-initialize the owner-maintained values? for now, NO!
1858 
1859  if(pThis->pColorContextCallback)
1860  {
1861  pThis->pColorContextCallback(pThis, -1, -1); // to refresh it (or in this case, erase it)
1862  }
1863 }
1864 
1865 static void __internal_highlight_colors(TEXT_OBJECT *pThis, XColor clrHFG, XColor clrHBG)
1866 {
1867  if(WBIsValidTextObject(pThis))
1868  {
1869  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
1870  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
1871  __FUNCTION__, __LINE__,
1872  pThis->iRow, pThis->iCol,
1873  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
1874 
1875  pThis->clrHFG = clrHFG;
1876  pThis->clrHBG = clrHBG;
1877  }
1878 }
1879 
1880 static char * __internal_get_text(TEXT_OBJECT *pThis)
1881 {
1882  return __internal_get_selected_text(pThis, -1, -1, -1, -1);
1883 }
1884 
1885 static void __internal_set_text(TEXT_OBJECT *pThis, const char *szText, unsigned long cbLen)
1886 {
1887 TEXT_BUFFER *pTemp;
1888 
1889  if(WBIsValidTextObject(pThis))
1890  {
1891  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
1892  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
1893  __FUNCTION__, __LINE__,
1894  pThis->iRow, pThis->iCol,
1895  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
1896 
1897  // for now, allocate a NEW text buffer and replace the old one with it
1898 
1899  pTemp = WBAllocTextBuffer(szText, cbLen);
1900 
1901  if(pTemp)
1902  {
1903  if(pThis->pText)
1904  {
1905  WBFreeTextBuffer(pThis->pText);
1906  }
1907 
1908  pThis->pText = pTemp; // and I'm spent
1909 
1910  if(pThis->pColorContextCallback)
1911  {
1912  pThis->pColorContextCallback(pThis, -1, -1); // to refresh it
1913  }
1914  }
1915  else
1916  {
1917  WB_ERROR_PRINT("TEXT OBJECT: %s - not enough memory for TEXT BUFFER errno=%d (no change)\n", __FUNCTION__, errno);
1918  }
1919  }
1920  else
1921  {
1922  WB_ERROR_PRINT("ERROR - %s - NOT a valid TEXT_OBJECT - %p\n", __FUNCTION__, pThis);
1923  }
1924 
1925  if(pThis->pText)
1926  {
1928  }
1929 }
1930 
1931 static int __internal_get_rows(const TEXT_OBJECT *pThis)
1932 {
1933 TEXT_BUFFER *pBuf;
1934 
1935  if(WBIsValidTextObject(pThis))
1936  {
1937  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
1938  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
1939  __FUNCTION__, __LINE__,
1940  pThis->iRow, pThis->iCol,
1941  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
1942 
1943  pBuf = (TEXT_BUFFER *)(pThis->pText);
1944 
1945  if(pBuf && pBuf->nEntries > 0) // only if NOT empty
1946  {
1947  if(pThis->iLineFeed == LineFeed_NONE ||
1948  !pBuf->aLines[0] || !*(pBuf->aLines[0])) // strlen(pBuf->aLines[0])) // "blank line"
1949  {
1950  return 1; // for multiline, even a blank line counts as '1'
1951  }
1952 
1953  return pBuf->nEntries + 1; // include the last line (a blank one) in the line count
1954  }
1955  }
1956 
1957  return 0; // assume blank
1958 }
1959 static int __internal_get_cols(TEXT_OBJECT *pThis)
1960 {
1961 TEXT_BUFFER *pBuf;
1962 int i1;
1963 
1964  if(WBIsValidTextObject(pThis))
1965  {
1966  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
1967  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
1968  __FUNCTION__, __LINE__,
1969  pThis->iRow, pThis->iCol,
1970  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
1971 
1972  pBuf = (TEXT_BUFFER *)(pThis->pText);
1973 
1974  if(pBuf)
1975  {
1976  if(pBuf->nMinMaxCol > pBuf->nMaxCol || !pBuf->nMaxCol || !pBuf->nMinMaxCol)
1977  {
1978  // cache data not valid, so re-evaluate
1979  WBTextBufferRefreshCache(pBuf); // re-calculate
1980  }
1981 
1982  if(pBuf->nMaxCol)
1983  {
1984  if(pBuf->nMaxCol < pThis->iCol) // is my current column past the 'max' column?
1985  {
1986  i1 = pThis->iCol + DEFAULT_TAB_WIDTH; // use my current column position
1987  }
1988  else
1989  {
1990  i1 = pBuf->nMaxCol + DEFAULT_TAB_WIDTH; // use the 'max' column position
1991  }
1992 
1993  return i1 - (i1 % DEFAULT_TAB_WIDTH); // rounded off to 'DEFAULT_TAB_WIDTH'
1994  // so if DEFAULT_TAB_WIDTH is 8, then 1 becomes 8, 8 becomes 16, etc.
1995  }
1996  }
1997  else if(pThis->iCol > 0) // allow for 'scrolling without any actual data yet'
1998  {
1999  i1 = pThis->iCol + DEFAULT_TAB_WIDTH; // use my current column position
2000  return i1 - (i1 % DEFAULT_TAB_WIDTH); // rounded off to 'DEFAULT_TAB_WIDTH'
2001  }
2002 
2003  // all other conditions fall through and return 0
2004  }
2005 
2006  return 0; // assume blank
2007 }
2008 
2009 static int __internal_get_filetype(const TEXT_OBJECT *pThis)
2010 {
2011  if(WBIsValidTextObject(pThis))
2012  {
2013  return pThis->iFileType;
2014  }
2015  return 0; // for now
2016 }
2017 static void __internal_set_filetype(TEXT_OBJECT *pThis, int iFileType)
2018 {
2019  if(WBIsValidTextObject(pThis))
2020  {
2021  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
2022  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
2023  __FUNCTION__, __LINE__,
2024  pThis->iRow, pThis->iCol,
2025  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
2026 
2027  pThis->iFileType = iFileType;
2028 
2029  if(pThis->pColorContextCallback)
2030  {
2031  pThis->pColorContextCallback(pThis, -1, -1); // to refresh it
2032  }
2033  }
2034 }
2035 static int __internal_get_linefeed(const TEXT_OBJECT *pThis)
2036 {
2037  if(WBIsValidTextObject(pThis))
2038  {
2039  return (int) pThis->iLineFeed;
2040  }
2041  return 0; // for now
2042 }
2043 static void __internal_set_linefeed(TEXT_OBJECT *pThis, int iLineFeed)
2044 {
2045  if(WBIsValidTextObject(pThis))
2046  {
2047  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
2048  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
2049  __FUNCTION__, __LINE__,
2050  pThis->iRow, pThis->iCol,
2051  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
2052 
2053  pThis->iLineFeed = (enum e_LineFeed)iLineFeed;
2054  }
2055 }
2056 static int __internal_get_insmode(const TEXT_OBJECT *pThis)
2057 {
2058  if(WBIsValidTextObject(pThis))
2059  {
2060  return pThis->iInsMode;
2061  }
2062  return 0; // for now
2063 }
2064 static void __internal_set_insmode(TEXT_OBJECT *pThis, int iInsMode)
2065 {
2066  if(WBIsValidTextObject(pThis))
2067  {
2068  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
2069  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
2070  __FUNCTION__, __LINE__,
2071  pThis->iRow, pThis->iCol,
2072  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
2073 
2074  pThis->iInsMode = iInsMode;
2075 
2076  pThis->iBlinkState = CURSOR_BLINK_RESET; // this affects the cursor blink, basically resetting it whenever I edit something
2077 
2078  __internal_invalidate_cursor(pThis, 1); // invalidate the cursor immediately
2079  }
2080 }
2081 static int __internal_get_selmode(const TEXT_OBJECT *pThis)
2082 {
2083  if(WBIsValidTextObject(pThis))
2084  {
2085  return pThis->iSelMode;
2086  }
2087  return 0; // for now
2088 }
2089 static void __internal_set_selmode(TEXT_OBJECT *pThis, int iSelMode)
2090 {
2091  if(WBIsValidTextObject(pThis))
2092  {
2093  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
2094  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
2095  __FUNCTION__, __LINE__,
2096  pThis->iRow, pThis->iCol,
2097  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
2098 
2099  pThis->iSelMode = iSelMode;
2100  }
2101 }
2102 static int __internal_get_tab(const TEXT_OBJECT *pThis)
2103 {
2104  if(WBIsValidTextObject(pThis))
2105  {
2106  return pThis->iTab;
2107  }
2108 
2109  return 0; // for now
2110 }
2111 static void __internal_set_tab(TEXT_OBJECT *pThis, int iTab)
2112 {
2113  if(WBIsValidTextObject(pThis))
2114  {
2115  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
2116  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
2117  __FUNCTION__, __LINE__,
2118  pThis->iRow, pThis->iCol,
2119  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
2120 
2121  pThis->iTab = iTab;
2122  }
2123 }
2124 static int __internal_get_scrollmode(const TEXT_OBJECT *pThis)
2125 {
2126  if(WBIsValidTextObject(pThis))
2127  {
2128  return pThis->iScrollMode;
2129  }
2130  return 0; // for now
2131 }
2132 static void __internal_set_scrollmode(TEXT_OBJECT *pThis, int iScrollMode)
2133 {
2134  if(WBIsValidTextObject(pThis))
2135  {
2136  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
2137  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
2138  __FUNCTION__, __LINE__,
2139  pThis->iRow, pThis->iCol,
2140  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
2141 
2142  pThis->iScrollMode = iScrollMode;
2143  }
2144 }
2145 static void __internal_get_select(const TEXT_OBJECT *pThis, WB_RECT *pRct)
2146 {
2147  if(WBIsValidTextObject(pThis))
2148  {
2149  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
2150  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
2151  __FUNCTION__, __LINE__,
2152  pThis->iRow, pThis->iCol,
2153  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
2154 
2155  if(pRct)
2156  {
2157  if(SEL_RECT_ALL(pThis))
2158  {
2159  pRct->left = -1; // 'normalize' 'select all' to return {-1,-1,-1,-1}
2160  pRct->right = -1;
2161  pRct->top = -1;
2162  pRct->bottom = -1;
2163  }
2164  else
2165  {
2166  memcpy(pRct, &(pThis->rctSel), sizeof(*pRct));
2167 
2168  if(!SEL_RECT_EMPTY(pThis)) // if not empty
2169  {
2170  NORMALIZE_SEL_RECT(*pRct); // normalize the results
2171  }
2172  }
2173  }
2174  }
2175 }
2176 static void __internal_set_select(TEXT_OBJECT *pThis, const WB_RECT *pRct)
2177 {
2178  // setting NULL for the selection rectangle selects 'none'
2179 
2180  if(WBIsValidTextObject(pThis))
2181  {
2182  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
2183  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
2184  __FUNCTION__, __LINE__,
2185  pThis->iRow, pThis->iCol,
2186  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
2187 
2188  if(!pRct)
2189  {
2190  pThis->rctSel.left = pThis->rctSel.top = pThis->rctSel.right = pThis->rctSel.bottom
2191  = 0; // select NONE
2192  }
2193  else
2194  {
2195  memcpy(&(pThis->rctSel), pRct, sizeof(*pRct));
2196  }
2197  }
2198 }
2199 static int __internal_has_select(const TEXT_OBJECT *pThis)
2200 {
2201  if(WBIsValidTextObject(pThis))
2202  {
2203  return !SEL_RECT_EMPTY(pThis);
2204  }
2205  return 0;
2206 }
2207 static char* __internal_get_sel_text(const TEXT_OBJECT *pThis, const WB_RECT *pRct)
2208 {
2209 WB_RECT rctSel;
2210 
2211  if(WBIsValidTextObject(pThis))
2212  {
2213  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
2214  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
2215  __FUNCTION__, __LINE__,
2216  pThis->iRow, pThis->iCol,
2217  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
2218 
2219  if(pRct)
2220  {
2221  if(pRct->left == pRct->right && pRct->top == pRct->bottom)
2222  {
2223  return NULL; // empty
2224  }
2225  else if(pRct->left < 0) // a flag for 'select all'
2226  {
2227  return __internal_get_selected_text(pThis, -1, -1, -1, -1);
2228  }
2229 
2230  memcpy(&rctSel, pRct, sizeof(rctSel));
2231  }
2232  else
2233  {
2234  if(SEL_RECT_EMPTY(pThis))
2235  {
2236  return NULL;
2237  }
2238  else if(SEL_RECT_ALL(pThis))
2239  {
2240  return __internal_get_selected_text(pThis, -1, -1, -1, -1);
2241  }
2242 
2243  memcpy(&rctSel, &(pThis->rctSel), sizeof(rctSel));
2244  }
2245 
2246  NORMALIZE_SEL_RECT(rctSel);
2247 
2248  return __internal_get_selected_text(pThis, rctSel.top, rctSel.left,
2249  rctSel.bottom, rctSel.right);
2250  }
2251  return NULL; // for now
2252 }
2253 static int __internal_get_row(const TEXT_OBJECT *pThis)
2254 {
2255  if(WBIsValidTextObject(pThis))
2256  {
2257  return pThis->iRow;
2258  }
2259  return 0; // for now
2260 }
2261 static void __internal_set_row(TEXT_OBJECT *pThis, int iRow)
2262 {
2263  if(WBIsValidTextObject(pThis))
2264  {
2265  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
2266  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
2267  __FUNCTION__, __LINE__,
2268  pThis->iRow, pThis->iCol,
2269  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
2270 
2271  if(iRow < 0 || !pThis->pText)
2272  {
2273  iRow = 0;
2274  }
2275  else if(iRow > ((TEXT_BUFFER *)pThis->pText)->nEntries)
2276  {
2277  iRow = ((TEXT_BUFFER *)pThis->pText)->nEntries;
2278  }
2279 
2280  pThis->iRow = iRow;
2281  }
2282 }
2283 static int __internal_get_col(const TEXT_OBJECT *pThis)
2284 {
2285  if(WBIsValidTextObject(pThis))
2286  {
2287  return pThis->iCol;
2288  }
2289  return 0; // for now
2290 }
2291 static void __internal_set_col(TEXT_OBJECT *pThis, int iCol)
2292 {
2293 int iAutoScrollWidth = AUTO_HSCROLL_SIZE;
2294 
2295 
2296  if(WBIsValidTextObject(pThis))
2297  {
2298  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
2299  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
2300  __FUNCTION__, __LINE__,
2301  pThis->iRow, pThis->iCol,
2302  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
2303 
2304  pThis->iCol = iCol; // for now, assign "as-is"
2305 
2306  // auto-scroll viewport for single-line only
2307 
2308  if(pThis->iLineFeed == LineFeed_NONE && // single-line, do auto-scrolling
2309  pThis->rctView.right > pThis->rctView.left) // in case the view area is empty
2310  {
2311  while(iAutoScrollWidth > 1 && iAutoScrollWidth >= pThis->rctView.right - pThis->rctView.left)
2312  {
2313  iAutoScrollWidth >>= 1;
2314  }
2315 
2316  // scroll left/right to expose the cursor
2317 
2318  while(pThis->rctView.left > pThis->iCol)
2319  {
2320  pThis->rctView.left -= iAutoScrollWidth;
2321  pThis->rctView.right -= iAutoScrollWidth;
2322  }
2323  while(pThis->rctView.right <= pThis->iCol)
2324  {
2325  pThis->rctView.left += iAutoScrollWidth;
2326  pThis->rctView.right += iAutoScrollWidth;
2327  }
2328  }
2329 
2330  if(pThis->pText &&
2331  WBTextBufferLineLength(pThis->pText, pThis->iRow) < iCol)
2332  {
2333  WBTextBufferLineChange(pThis->pText, pThis->iRow, iCol);
2334 // WBTextBufferRefreshCache(pThis->pText);
2335  }
2336  }
2337 }
2338 static void __internal_del_select(TEXT_OBJECT *pThis)
2339 {
2340 char *pTemp, *pL;
2341 int iSelAll, iLen, i1, i2;
2342 TEXT_BUFFER *pBuf;
2343 WB_RECT rctSel;
2344 
2345 
2346  if(WBIsValidTextObject(pThis))
2347  {
2348  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
2349  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
2350  __FUNCTION__, __LINE__,
2351  pThis->iRow, pThis->iCol,
2352  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
2353 
2354  __internal_invalidate_cursor(pThis, 0);
2355  pThis->iBlinkState = CURSOR_BLINK_RESET; // this affects the cursor blink, basically resetting it whenever I edit something
2356 
2357  iSelAll = SEL_RECT_ALL(pThis); // identifies "select all"
2358 
2359  if(!iSelAll && SEL_RECT_EMPTY(pThis))
2360  {
2361  WB_DEBUG_PRINT(DebugLevel_Medium | DebugSubSystem_TextObject, "%s line %d - empty selection\n", __FUNCTION__, __LINE__);
2362 
2363  // no selection, do nothing
2364  return;
2365  }
2366 
2367  pBuf = (TEXT_BUFFER *)(pThis->pText);
2368 
2369  if(!pBuf || (pThis->rctSel.top >= pBuf->nEntries && pThis->rctSel.bottom >= pBuf->nEntries))
2370  {
2371  WB_DEBUG_PRINT(DebugLevel_Medium | DebugSubSystem_TextObject,
2372  "%s line %d: no selection to delete\n", __FUNCTION__, __LINE__);
2373 
2374  return; // NO buffer, or select area is outside of buffer area
2375  }
2376 
2377  // for the undo buffer I will need a copy of the original text.
2378  // this function only returns NULL on error
2379 
2380  memcpy(&rctSel, &(pThis->rctSel), sizeof(rctSel));
2381 
2382  if(iSelAll)
2383  {
2384  pTemp = __internal_get_selected_text(pThis, -1, -1, -1, -1);
2385  }
2386  else
2387  {
2388  NORMALIZE_SEL_RECT(rctSel);
2389 
2390  pTemp = __internal_get_selected_text(pThis, rctSel.top, rctSel.left,
2391  rctSel.bottom, rctSel.right);
2392  }
2393 
2394  if(iSelAll)
2395  {
2396  WB_DEBUG_PRINT(DebugLevel_Medium | DebugSubSystem_TextObject,
2397  "%s line %d: delete 'select all'\n", __FUNCTION__, __LINE__);
2398  // delete all
2399  if(pThis->pText)
2400  {
2401  WBFreeTextBuffer(pThis->pText);
2402  pThis->pText = NULL;
2403  }
2404 
2405  if(pTemp)
2406  {
2407  __internal_add_undo(pThis, UNDO_DELETE, pThis->iSelMode,
2408  0, 0, &(pThis->rctSel), pTemp, -1,
2409  pBuf->nEntries, 0, NULL, NULL, 0);
2410  }
2411  }
2412  else if(pThis->iLineFeed == LineFeed_NONE ||
2413  rctSel.top == rctSel.bottom) // single-line selection (all methods behave the same)
2414  {
2415  // delete from rctSel.left to rctSel.right (exclusive)
2416  WB_DEBUG_PRINT(DebugLevel_Medium | DebugSubSystem_TextObject,
2417  "%s line %d: delete single line, %d to %d (top=%d, bottom=%d)\n",
2418  __FUNCTION__, __LINE__,
2419  rctSel.left, rctSel.right, rctSel.top, rctSel.bottom);
2420 
2421  pL = pBuf->aLines[rctSel.top];
2422 
2423  if(pL)
2424  {
2425  iLen = WBGetMBLength(pL);
2426  if(iLen >= rctSel.left)
2427  {
2428  if(iLen <= rctSel.right)
2429  {
2430  char *pTempL = WBGetMBCharPtr(pL, rctSel.left, NULL);
2431  if(pTempL)
2432  {
2433  *pTempL = 0; // just truncate the string
2434  }
2435  }
2436  else
2437  {
2438  char *pTempL = WBGetMBCharPtr(pL, rctSel.left, NULL);
2439  char *pTempR = WBGetMBCharPtr(pL, rctSel.right, NULL);
2440 
2441  if(pTempL && pTempR)
2442  {
2443  strcpy(pTempL, pTempR); // thereby squeezing the string together and deleting the selection
2444  }
2445  }
2446  }
2447  }
2448 
2449  if(pTemp)
2450  {
2451  __internal_add_undo(pThis, UNDO_DELETE, pThis->iSelMode,
2452  rctSel.top, rctSel.left, &(pThis->rctSel),
2453  pTemp, -1,
2454  rctSel.bottom, rctSel.right, NULL, NULL, 0);
2455  }
2456 
2457  if(pThis->iLineFeed == LineFeed_NONE && // special case, auto-hscroll
2458  pThis->rctView.right > pThis->rctView.left)
2459  {
2460  int iAutoScrollWidth = AUTO_HSCROLL_SIZE;
2461 
2462  while(iAutoScrollWidth > 1 && iAutoScrollWidth >= pThis->rctView.right - pThis->rctView.left)
2463  {
2464  iAutoScrollWidth >>= 1;
2465  }
2466 
2467  // scroll left/right to expose the cursor
2468 
2469  while(pThis->rctView.left > pThis->iCol)
2470  {
2471  pThis->rctView.left -= iAutoScrollWidth;
2472  pThis->rctView.right -= iAutoScrollWidth;
2473  }
2474  while(pThis->rctView.right <= pThis->iCol)
2475  {
2476  pThis->rctView.left += iAutoScrollWidth;
2477  pThis->rctView.right += iAutoScrollWidth;
2478  }
2479  }
2480 
2482  }
2483  else if(pThis->iSelMode == SelectMode_BOX) // multiline delete BOX mode
2484  {
2485  WB_DEBUG_PRINT(DebugLevel_Medium | DebugSubSystem_TextObject,
2486  "%s line %d: BOX MODE delete multiline, %d,%d to %d,%d\n",
2487  __FUNCTION__, __LINE__,
2488  rctSel.left, rctSel.top,
2489  rctSel.right, rctSel.right);
2490  }
2491  else if(pThis->iSelMode == SelectMode_LINE) // multiline delete LINE mode
2492  {
2493  WB_DEBUG_PRINT(DebugLevel_Medium | DebugSubSystem_TextObject,
2494  "%s line %d: LINE MODE delete multiline, %d,%d to %d,%d\n",
2495  __FUNCTION__, __LINE__,
2496  rctSel.left, rctSel.top,
2497  rctSel.right, rctSel.right);
2498  }
2499  else // default 'char'
2500  {
2501  WB_DEBUG_PRINT(DebugLevel_Medium | DebugSubSystem_TextObject,
2502  "%s line %d: delete multiline, %d,%d to %d,%d\n",
2503  __FUNCTION__, __LINE__,
2504  rctSel.left, rctSel.top,
2505  rctSel.right, rctSel.right);
2506 
2507  if(pBuf->aLines[rctSel.top] && rctSel.left > 0)
2508  {
2509  pL = pBuf->aLines[rctSel.top];
2510 
2511  if(rctSel.right > 0)
2512  {
2513  char *pJoin, *pNew;
2514 
2515  pJoin = WBGetMBCharPtr(pBuf->aLines[rctSel.bottom], rctSel.right, NULL);
2516  pNew = WBJoinMBLine(pL, rctSel.left, pJoin);
2517 
2518  if(!pNew) // error
2519  {
2520  WB_ERROR_PRINT("ERROR - %s - memory allocation error, errno=%d\n", __FUNCTION__, errno);
2521  return; // no undo buffer when there's a memory error
2522  }
2523  pBuf->aLines[rctSel.top] = pNew;
2524  if(pL) // just in case
2525  {
2526  WBFree(pL);
2527  }
2528 
2529  i2 = rctSel.bottom + 1; // start copying the NEXT line
2530  }
2531  else
2532  {
2533  char *pTemp = WBGetMBCharPtr(pL, rctSel.left, NULL);
2534  if(pTemp) // just in case
2535  {
2536  *pTemp = 0; // truncate line at this position
2537  }
2538 
2539  i2 = rctSel.bottom; // start copying THIS line (since I'm not joining them)
2540  }
2541 
2542  for(i1=pThis->rctSel.top + 1; i2 < pBuf->nEntries; i1++, i2++)
2543  {
2544  if(pBuf->aLines[i1])
2545  {
2546  WBFree(pBuf->aLines[i1]);
2547  }
2548  pBuf->aLines[i1] = pBuf->aLines[i2];
2549  pBuf->aLines[i2] = NULL; // for now, to prevent accidental pointer re-use later
2550  }
2551 
2552  pBuf->nEntries = i1;
2553  }
2554  else
2555  {
2556  if(rctSel.right == 0) // an even number of lines is being deleted
2557  {
2558  for(i1=rctSel.top, i2=rctSel.bottom; i2 < pBuf->nEntries; i1++, i2++)
2559  {
2560  if(pBuf->aLines[i1])
2561  {
2562  WBFree(pBuf->aLines[i1]);
2563  }
2564  pBuf->aLines[i1] = pBuf->aLines[i2];
2565  pBuf->aLines[i2] = NULL; // for now, to prevent accidental pointer re-use later
2566  }
2567 
2568  pBuf->nEntries = i1;
2569  }
2570  else // uneven lines
2571  {
2572  // top line will become partial bottom line
2573  pL = pBuf->aLines[rctSel.bottom];
2574 
2575  if(pL)
2576  {
2577  char *pTempR = WBGetMBCharPtr(pL, rctSel.right, NULL);
2578  if(pTempR)
2579  {
2580  strcpy(pL, pTempR);
2581  }
2582  else
2583  {
2584  *pL = 0; // for now, truncate the line if this happens
2585  }
2586  }
2587 
2588  if(pBuf->aLines[rctSel.top])
2589  {
2590  WBFree(pBuf->aLines[rctSel.top]);
2591  pBuf->aLines[rctSel.top] = NULL; // by convention (it's re-assigned in the next line though)
2592  }
2593 
2594  pBuf->aLines[rctSel.top] = pL;
2595  pBuf->aLines[rctSel.bottom] = NULL; // by convention, to prevent re-use of pointer
2596 
2597  for(i1=rctSel.top + 1, i2=rctSel.bottom + 1; i2 < pBuf->nEntries; i1++, i2++)
2598  {
2599  if(pBuf->aLines[i1])
2600  {
2601  WBFree(pBuf->aLines[i1]);
2602  pBuf->aLines[i1] = NULL; // by convention (re-assigned in next line)
2603  }
2604 
2605  pBuf->aLines[i1] = pBuf->aLines[i2];
2606  pBuf->aLines[i2] = NULL; // by convention, to prevent accidental pointer re-use later
2607  }
2608 
2609  pBuf->nEntries = i1;
2610  }
2611  }
2612 
2613  // update cached line length info
2614 
2615  if(rctSel.bottom == rctSel.top) // same line delete
2616  {
2617  int iNewLen = 0;
2618 
2619  if(pBuf->aLines[rctSel.top])
2620  {
2621  iNewLen = WBGetMBLength(pBuf->aLines[rctSel.top]);
2622  }
2623 
2624  WBTextBufferLineChange(pThis->pText, rctSel.top, iNewLen);
2625  }
2626  else if((rctSel.bottom - rctSel.top) == 1) // single-line delete
2627  {
2628  WBTextBufferLineChange(pThis->pText, rctSel.top, -1); // deleted line
2629  }
2630  else // multi-line
2631  {
2633  }
2634 
2635  if(pTemp)
2636  {
2637  __internal_add_undo(pThis, UNDO_DELETE, pThis->iSelMode,
2638  rctSel.top, rctSel.left, &(pThis->rctSel),
2639  pTemp, -1,
2640  rctSel.bottom, rctSel.right, NULL, NULL, 0);
2641  }
2642  }
2643 
2644  if(pTemp)
2645  {
2646  WBFree(pTemp);
2647  }
2648 
2649  // once the selection has been deleted, select 'nothing' and re-paint all
2650  // and of course, iRow and iCol will be at the left/top of the selection
2651 
2652  // NOTE: this assumes that the data is consistent
2653 
2654  pThis->rctSel.left = 0;
2655  pThis->rctSel.top = 0;
2656  pThis->rctSel.right = 0;
2657  pThis->rctSel.bottom = 0;
2658 
2659  if(iSelAll) // everything was deleted
2660  {
2661  pThis->iRow = 0;
2662  pThis->iCol = 0;
2663  }
2664  else
2665  {
2666  if(rctSel.top >= 0)
2667  pThis->iRow = rctSel.top;
2668  else
2669  {
2670  WB_WARN_PRINT("WARNING - %s line %d - rctSel.top == %d\n", __FUNCTION__, __LINE__, rctSel.top);
2671  pThis->iRow = 0;
2672  }
2673 
2674  if(rctSel.left >= 0)
2675  pThis->iCol = rctSel.left;
2676  else
2677  {
2678  WB_WARN_PRINT("WARNING - %s line %d - rctSel.left == %d\n", __FUNCTION__, __LINE__, rctSel.left);
2679  pThis->iCol = 0;
2680  }
2681  }
2682 
2683  if(pThis->pColorContextCallback)
2684  {
2685  pThis->pColorContextCallback(pThis, -1, -1); // to refresh it
2686  }
2687 
2688  WB_DEBUG_PRINT(DebugLevel_Verbose, "%s line %d - need to optimize invalidate rect\n", __FUNCTION__, __LINE__);
2689 
2690  __internal_invalidate_rect(pThis, NULL, 1); // TODO: optimize this
2691  }
2692 }
2693 static void __internal_replace_select(TEXT_OBJECT *pThis, const char *szText, unsigned long cbLen)
2694 {
2695 WB_RECT rctSel;
2696 int iSelAll;
2697 
2698 
2699  if(WBIsValidTextObject(pThis))
2700  {
2701  int iOldIns; //, iOldRow, iOldCol;
2702 
2703  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
2704  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
2705  __FUNCTION__, __LINE__,
2706  pThis->iRow, pThis->iCol,
2707  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
2708 
2709  if(pThis->iRow < 0 || pThis->iCol< 0)
2710  {
2711  pThis->iRow = 0;
2712  pThis->iCol = 0;
2713  }
2714 
2715 // NOTE: iOldRow and iOldCol not being used; commented out because of linux gcc warnings
2716 // iOldRow = pThis->iRow;
2717 // iOldCol = pThis->iCol;
2718  iOldIns = pThis->iInsMode;
2719 
2720  iSelAll = SEL_RECT_ALL(pThis);
2721 
2722  memcpy(&rctSel, &(pThis->rctSel), sizeof(rctSel));
2723 
2724  if(iSelAll)
2725  {
2726  pThis->iRow = 0;
2727  pThis->iCol = 0;
2728  }
2729  else if(!SEL_RECT_EMPTY(pThis))
2730  {
2731  NORMALIZE_SEL_RECT(rctSel);
2732 
2733  pThis->iRow = rctSel.top;
2734  pThis->iCol = rctSel.left;
2735  }
2736 
2737  pThis->iInsMode = InsertMode_INSERT;
2738 
2739  // TODO: if box mode, height/width may need to match. for now, don't care
2740 
2741  if(!iSelAll && SEL_RECT_EMPTY(pThis)) // not 'ALL'
2742  {
2743  WB_DEBUG_PRINT(DebugLevel_Medium | DebugSubSystem_TextObject, "%s line %d - replace an empty select\n", __FUNCTION__, __LINE__);
2744 
2745  __internal_ins_chars(pThis, szText, cbLen); // just insert the text (no selection)
2746 
2747  // NOTE: select rectangle remains empty
2748  }
2749  else
2750  {
2751  // for now delete the selection, then insert the new characters, and make new selection match inserted text
2752  // this will take into consideration the select mode
2753  __internal_del_select(pThis); // this also clears the selection
2754 
2755  // new selection starts at rctSel.top, rctSel.left
2756  __internal_ins_chars(pThis, szText, cbLen); // just insert the text (no selection)
2757 
2758  // new selection ends at iRow, iCol
2759  rctSel.bottom = pThis->iRow;
2760  rctSel.right = pThis->iCol;
2761 
2762  // assign the new select rectangle to become the text I just added.
2763  memcpy(&(pThis->rctSel), &rctSel, sizeof(pThis->rctSel));
2764  }
2765 
2766 // pThis->iRow = iOldRow;
2767 // pThis->iCol = iOldCol;
2768  pThis->iInsMode = iOldIns;
2769 
2770  // todo: mark a NEW selection using the inserted text? this might mean replicating code for
2771  // __internal_del_select and __internal_ins_chars and processing undo here
2772 
2773 
2774  if(pThis->pColorContextCallback)
2775  {
2776  pThis->pColorContextCallback(pThis, -1, -1); // to refresh it
2777  }
2778 
2779  WB_DEBUG_PRINT(DebugLevel_Verbose, "%s line %d - need to optimize invalidate rect\n", __FUNCTION__, __LINE__);
2780  __internal_invalidate_rect(pThis, NULL, 1); // TODO: optimize this
2781 
2782  // NOTE: no need to call WBTextBufferRefreshCache(), the 'del_select' and 'ins_chars' would've done it
2783 
2784  }
2785 }
2786 static void __internal_del_chars(TEXT_OBJECT *pThis, int nChar)
2787 {
2788 TEXT_BUFFER *pBuf;
2789 int i1, i2, iLen;
2790 char *pL, *pL2, *pL3;
2791 WB_RECT rctInvalid;
2792 
2793 
2794  if(!nChar)
2795  {
2796  return; // does nothing
2797  }
2798 
2799  // NOTE: nChar < 0 deletes to the left (like a backspace), >0 deletes to the right like 'Del' normally would
2800  // If the cursor is at the end of the line or the start of the line, an appropriate delete will
2801  // merge the previous (or next) line. Values of |nChar| greater than the number of characters remaining
2802  // in the line (from the cursor) _STOP_ at the beginning/end of the line. Only a delete with cursor
2803  // AT the beginning or end of the line will merge. Merge also considers the virtual cursor position.
2804 
2805  if(!WBIsValidTextObject(pThis))
2806  {
2807  return; // error
2808  }
2809 
2810  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
2811  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
2812  __FUNCTION__, __LINE__,
2813  pThis->iRow, pThis->iCol,
2814  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
2815 
2816  __internal_invalidate_cursor(pThis, 0);
2817  pThis->iBlinkState = CURSOR_BLINK_RESET; // this affects the cursor blink, basically resetting it whenever I edit something
2818 
2819  if(nChar > 0 || (pThis->iCol == 0 && pThis->iLineFeed == LineFeed_NONE))
2820  {
2821  __internal_calc_rect(pThis, &rctInvalid, pThis->iRow, pThis->iCol, pThis->iRow, pThis->iCol + 1);
2822  }
2823  else if(pThis->iCol > 0) // backspace, don't merge lines
2824  {
2825  __internal_calc_rect(pThis, &rctInvalid, pThis->iRow, pThis->iCol - 1, pThis->iRow, pThis->iCol);
2826  }
2827  else if(pThis->iLineFeed != LineFeed_NONE)
2828  {
2829  __internal_calc_rect(pThis, &rctInvalid, pThis->iRow - 1, 0, -1, -1);
2830  }
2831 
2832 
2833  pBuf = (TEXT_BUFFER *)(pThis->pText);
2834 
2835  if(!pBuf || !pBuf->nEntries || pThis->iRow > pBuf->nEntries ||
2836  (pThis->iRow == pBuf->nEntries && nChar > 0)) // ending row only allows a backspace from the 1st column
2837  {
2838  return; // do nothing
2839  }
2840 
2841  while(nChar) // while I have characters to delete
2842  {
2843  // if I hit a limit and 'nChar' is still non-zero, break out and return anyway
2844 
2845  if(pThis->iRow > pBuf->nEntries || pThis->iRow < 0) // allow row == nEntries
2846  {
2847  return; // empty
2848  }
2849 
2850  pL = pBuf->aLines[pThis->iRow];
2851 
2852  iLen = WBGetMBLength(pL);
2853 
2854  if(nChar < 0 && pThis->iCol == 0 && // backspace prior to beginning of line
2855  (pThis->iRow <= 0 || pThis->iLineFeed == LineFeed_NONE)) // single line and backspace past begin of column
2856  {
2857  WB_DEBUG_PRINT(DebugLevel_Verbose, "%s - backspace while I'm at start of file\n", __FUNCTION__);
2858  return; // exit
2859  }
2860 
2861  if(nChar > 0 && pThis->iCol >= iLen && // delete past end of line
2862  (pThis->iRow >= pBuf->nEntries ||
2863  pThis->iLineFeed == LineFeed_NONE)) // single line and backspace past begin of column
2864  {
2865  WB_DEBUG_PRINT(DebugLevel_Verbose, "%s - delete while I'm past end of file\n", __FUNCTION__);
2866 
2867  return; // exit
2868  }
2869 
2870  // if it's at an edge, merge lines
2871 
2872  if(nChar < 0 && // backspacing
2873  pThis->iRow > 0 && pThis->iCol == 0) // backspace while at the start of a line NOT the first line
2874  {
2875  // this backspace will merge the previous line with this one
2876 
2877  pL2 = pBuf->aLines[pThis->iRow - 1];
2878  if(!pL2)
2879  {
2880  pThis->iCol = 0; // a kind of 'fallback' - put column cursor at zero
2881 
2882  pBuf->aLines[pThis->iRow - 1] = pL; // move line to THIS position
2883  pBuf->aLines[pThis->iRow] = NULL; // pre-emptive, avoid sharing pointers
2884 
2885  pL = NULL;
2886  }
2887  else
2888  {
2889  if(pL) // can be NULL, which is a blank line
2890  {
2891  i2 = WBGetMBLength(pL2); // now THIS is the new column position
2892 
2893  pL2 = WBJoinMBLine(pL2, i2, pL);
2894 
2895  if(!pL2)
2896  {
2897  WB_ERROR_PRINT("ERROR: %s - not enough memory to join lines\n", __FUNCTION__);
2898 
2899  return; // do nothing (TODO: error message?)
2900  }
2901 
2902  pBuf->aLines[pThis->iRow - 1] = pL2; // the new pointer
2903  }
2904  else
2905  {
2906  i2 = 0;
2907  }
2908 
2909  pBuf->aLines[pThis->iRow] = NULL; // pre-emptive, avoid sharing pointers
2910 
2911  pThis->iCol = i2; // the new position (end of previous line)
2912 
2913  WBFree(pL);
2914  pL = NULL; // pre-emptive, avoid shared pointers
2915  }
2916 
2917  // if I hit backspace while on row==nEntries, just move the cursor to the end
2918  // of the previous line. I'm not really deleting anything.
2919  if(pThis->iRow >= pBuf->nEntries) // beyond the last line?
2920  {
2921  pThis->iRow--;
2922  if(pBuf->aLines[pThis->iRow])
2923  {
2924  pThis->iCol = WBGetMBLength(pBuf->aLines[pThis->iRow]);
2925  }
2926 
2927  // NOTE: no need to update the line's length in the buffer cache, I didn't change anything
2928  }
2929  else
2930  {
2931  // at this point, 'iRow - 1' is the 'replaced' row, so I need to move rows such that
2932  // I start with 'iRow + 1' and assign to 'i1 - 1'. this works.
2933  for(i1=pThis->iRow + 1; i1 < pBuf->nEntries; i1++)
2934  {
2935  pBuf->aLines[i1 - 1] = pBuf->aLines[i1]; // pack them up by one line
2936  }
2937 
2938  pBuf->nEntries--;
2939  pBuf->aLines[pBuf->nEntries] = NULL; // so that pointers aren't accidentally re-used
2940 
2941  WBTextBufferLineChange(pThis->pText, pThis->iRow, -1); // deleted line (current 'iRow')
2942 
2943  pThis->iRow--; // since I moved up
2944 
2945  WBTextBufferLineChange(pThis->pText, pThis->iRow,
2946  WBGetMBLength(pBuf->aLines[pThis->iRow])); // 'replaced' line (new length)
2947  }
2948 
2949  nChar++; // backspacing, so increment
2950 
2951  __internal_add_undo(pThis, UNDO_DELETE, pThis->iSelMode,
2952  pThis->iRow, pThis->iCol, &(pThis->rctSel),
2953  "\n", 1, // this is what I'm deleting - the newline
2954  pThis->iRow + 1, 0, NULL, NULL, 0);
2955 
2956  __internal_merge_rect(pThis, &rctInvalid, pThis->iRow, 0, -1, -1);
2957  }
2958  else if(nChar > 0 && // deleting
2959  pThis->iCol >= iLen && pThis->iRow < (pBuf->nEntries - 1)) // not at end of 'last row'
2960  {
2961  // delete at end of line which merges with the next line
2962 
2963  pL2 = pBuf->aLines[pThis->iRow + 1];
2964 
2965  if(pL2)
2966  {
2967  if(*pL2)
2968  {
2969  WB_DEBUG_PRINT(DebugLevel_Verbose, "%s - joining \"%s\" at %d with \"%s\"\n", __FUNCTION__,
2970  pL, pThis->iCol, pL2);
2971 
2972  pL = WBJoinMBLine(pL, pThis->iCol, pL2);
2973 
2974  if(!pL)
2975  {
2976  WB_ERROR_PRINT("ERROR: %s - not enough memory to join\n", __FUNCTION__);
2977 
2978  return; // do nothing ELSE
2979  }
2980 
2981  pBuf->aLines[pThis->iRow] = pL; // the new pointer
2982  }
2983 
2984  WBFree(pL2);
2985  }
2986 
2987  pBuf->nEntries--;
2988 
2989  // merge UP the lines, starting with 'iRow + 1' getting 'iRow + 2'
2990  for(i1=pThis->iRow + 1; i1 < pBuf->nEntries; i1++)
2991  {
2992  pBuf->aLines[i1] = pBuf->aLines[i1 + 1];
2993  }
2994 
2995  pBuf->aLines[pBuf->nEntries] = NULL; // so that pointers aren't accidentally re-used
2996 
2997  WBTextBufferLineChange(pThis->pText, pThis->iRow + 1, -1); // deleted line (the one that follows 'iRow')
2998 
2999  WBTextBufferLineChange(pThis->pText, pThis->iRow,
3000  WBGetMBLength(pBuf->aLines[pThis->iRow])); // 'replaced' line (new length)
3001 
3002  nChar--; // deleting, so decrement
3003 
3004  __internal_add_undo(pThis, UNDO_DELETE, pThis->iSelMode,
3005  pThis->iRow, iLen, &(pThis->rctSel),
3006  "\n", 1,
3007  pThis->iRow + 1, 0, NULL, NULL, 0);
3008 
3009 
3010  WB_DEBUG_PRINT(DebugLevel_Verbose, "%s %d - fix the optimization for __internal_merge_rect\n", __FUNCTION__, __LINE__);
3011 // __internal_merge_rect(pThis, &rctInvalid, pThis->iRow, 0, -1, -1); // invalidate entire row and those that follow
3012 
3013  // bug workaround, mark everything 'invalid'
3014  __internal_merge_rect(pThis, &rctInvalid, 0, 0, -1, -1);
3015  }
3016  else
3017  {
3018  // OK now that _THAT_ is done, delete "up to the end of the string if needed"
3019  // on either end, depending upon which direction we must travel
3020 
3021  pL = pBuf->aLines[pThis->iRow];
3022 
3023 // if(!pL)
3024 // {
3025 // WB_ERROR_PRINT("UNEXPECTED: %s - NULL pointer at row %d\n", __FUNCTION__, pThis->iRow);
3026 //
3027 // return; // do nothing (TODO: error message?)
3028 // }
3029 
3030  // TODO: use WBDelMBChars
3031 
3032  if(nChar < 0 && pThis->iCol > 0)
3033  {
3034  // backspacing
3035 
3036  // TODO: handle hard tab translation? For now "leave it"
3037 
3038  i2 = -nChar;
3039  if(i2 > pThis->iCol)
3040  {
3041  i2 = pThis->iCol;
3042  }
3043 
3044  if(pL) // could be NULL
3045  {
3046  // position to which I delete
3047  pL2 = WBGetMBCharPtr(pL, pThis->iCol - i2, NULL); // column to which I delete (backspace)
3048  pL3 = WBGetMBCharPtr(pL, pThis->iCol, NULL); // position FROM where I delete/backspace
3049 
3050 // TEMPORARY DEBUG CODE - remove when I fix this, it's here to remind me
3051  {
3052  const char *pL2a;
3053  const char *pL3a;
3054  int i2a;
3055 
3056  for(i2a=0; i2a < i2; i2a++)
3057  {
3058  pL2a = WBGetMBCharPtr(pL, pThis->iCol - i2 + i2a, NULL);
3059  pL3a = WBGetMBCharPtr(pL, pThis->iCol - i2 + i2a + 1, NULL);
3060 
3061  if(pL2a && (pL3a - pL2a) == 1 &&
3062  ((unsigned char)*pL2a == (unsigned char)HARD_TAB_CHAR || *pL2a == '\t'))
3063  {
3064  WB_DEBUG_PRINT(DebugLevel_Verbose, "%s %d - hard tab (%02xH) at row %d column %d\n",
3065  __FUNCTION__, __LINE__, (unsigned char)*pL2a,
3066  pThis->iRow, pThis->iCol - i2 + i2a);
3067  }
3068  }
3069  }
3070 
3071 
3072  // create undo record first, before I actually delete things
3073  __internal_add_undo(pThis, UNDO_DELETE, pThis->iSelMode,
3074  pThis->iRow, pThis->iCol + nChar, &(pThis->rctSel),
3075  pL2, pL3 - pL2, // text pointer AND 'true length' in bytes
3076  pThis->iRow, pThis->iCol, NULL, NULL, 0);
3077 
3078  strcpy(pL2, pL3); // delete things down
3079  }
3080 
3081  pThis->iCol -= i2;
3082  nChar += i2; // # of characters actually deleted (added 'cause nChar is negative)
3083 
3084  WBTextBufferLineChange(pThis->pText, pThis->iRow,
3085  WBGetMBLength(pBuf->aLines[pThis->iRow])); // new length
3086 
3087  __internal_merge_rect(pThis, &rctInvalid, pThis->iRow, pThis->iCol, pThis->iRow, -1); // invalidate remainder of row
3088  }
3089  else if(nChar > 0 && iLen > pThis->iCol)
3090  {
3091  // TODO: handle hard tab translation? For now "leave it"
3092 
3093  i2 = nChar;
3094 
3095  if(i2 > iLen - pThis->iCol)
3096  {
3097  i2 = iLen - pThis->iCol;
3098  }
3099 
3100  if(pL)
3101  {
3102  // position to which I delete
3103  pL2 = WBGetMBCharPtr(pL, pThis->iCol + i2, NULL); // column to which I delete
3104  pL3 = WBGetMBCharPtr(pL, pThis->iCol, NULL); // position FROM where I delete/backspace
3105 
3106 // TEMPORARY DEBUG CODE - remove when I fix this, it's here to remind me
3107  {
3108  const char *pL2a;
3109  const char *pL3a;
3110  int i2a;
3111 
3112  for(i2a=0; i2a < i2; i2a++)
3113  {
3114  pL2a = WBGetMBCharPtr(pL, pThis->iCol + i2a, NULL);
3115  pL3a = WBGetMBCharPtr(pL, pThis->iCol + i2a + 1, NULL);
3116 
3117  if(pL3a && (pL2a - pL3a) == 1 &&
3118  ((unsigned char)*pL3a == (unsigned char)HARD_TAB_CHAR || *pL3a == '\t'))
3119  {
3120  WB_DEBUG_PRINT(DebugLevel_Verbose, "%s %d - hard tab (%02xH) at row %d column %d\n",
3121  __FUNCTION__, __LINE__, (unsigned char)*pL3a,
3122  pThis->iRow, pThis->iCol - i2 + i2a);
3123  }
3124  }
3125  }
3126 
3127  // create undo record first, before I actually delete things
3128  __internal_add_undo(pThis, UNDO_DELETE, pThis->iSelMode,
3129  pThis->iRow, pThis->iCol, &(pThis->rctSel),
3130  pL3, pL2 - pL3, // text pointer AND 'true length' in bytes
3131  pThis->iRow, pThis->iCol + nChar, NULL, NULL, 0);
3132 
3133  strcpy(pL3, pL2); // delete things down
3134  }
3135 
3136  nChar -= i2;
3137 
3138  WBTextBufferLineChange(pThis->pText, pThis->iRow,
3139  WBGetMBLength(pBuf->aLines[pThis->iRow])); // new length
3140 
3141  // NOTE: column does not change
3142  __internal_merge_rect(pThis, &rctInvalid, pThis->iRow, pThis->iCol, pThis->iRow, -1); // invalidate remainder of row
3143  }
3144  else
3145  {
3146  // this is a condition that might infinite loop since it doesn't inc/dec nChar
3147  // so it's a condition that was SUPPOSED to be tested for, but wasn't.
3148 
3149  WB_ERROR_PRINT("UNEXPECTED: %s - nChar=%d, iCol=%d, iRow=%d nEntries=%d\n",
3150  __FUNCTION__, nChar, pThis->iCol, pThis->iRow, (int)pBuf->nEntries);
3151 
3152  break;
3153  }
3154  }
3155 
3156  // implement auto-hscroll while deleting
3157 
3158  if(pThis->rctView.right > pThis->rctView.left)
3159  {
3160  if(pThis->rctView.right <= pThis->iCol ||
3161  pThis->rctView.left > pThis->iCol)
3162  {
3163  int iAutoScrollWidth = AUTO_HSCROLL_SIZE;
3164 
3165  while(iAutoScrollWidth > 1 && iAutoScrollWidth >= pThis->rctView.right - pThis->rctView.left)
3166  {
3167  iAutoScrollWidth >>= 1;
3168  }
3169 
3170  // scroll left/right to expose the cursor
3171 
3172  while(pThis->rctView.left > pThis->iCol)
3173  {
3174  pThis->rctView.left -= iAutoScrollWidth;
3175  pThis->rctView.right -= iAutoScrollWidth;
3176  }
3177  while(pThis->rctView.right <= pThis->iCol)
3178  {
3179  pThis->rctView.left += iAutoScrollWidth;
3180  pThis->rctView.right += iAutoScrollWidth;
3181  }
3182 
3183  if(pThis->pColorContextCallback) // TODO: if I only need to invalidate one line, should I just do that?
3184  {
3185  pThis->pColorContextCallback(pThis, -1, -1); // to refresh it
3186  }
3187 
3188  WB_DEBUG_PRINT(DebugLevel_Verbose, "%s line %d - need to optimize invalidate rect\n", __FUNCTION__, __LINE__);
3189  __internal_invalidate_rect(pThis, NULL, 1); // scrolling, so invalidate everything
3190  }
3191  else
3192  {
3193  if(pThis->pColorContextCallback) // TODO: if I only need to invalidate one line, should I just do that?
3194  {
3195  pThis->pColorContextCallback(pThis, -1, -1); // to refresh it
3196  }
3197 
3198  WB_DEBUG_PRINT(DebugLevel_Verbose, "%s line %d - need to optimize invalidate rect\n", __FUNCTION__, __LINE__);
3199  // for now, always do this
3200  __internal_invalidate_rect(pThis, NULL, 1); // scrolling, so invalidate everything
3201 // __internal_invalidate_rect(pThis, &rctInvalid, 1); // invalidate bounding rectangle
3202  }
3203  }
3204  else
3205  {
3206  if(pThis->pColorContextCallback) // assume multi-line
3207  {
3208  pThis->pColorContextCallback(pThis, -1, -1); // to refresh it
3209  }
3210 
3211  // for now, always do this
3212  WB_DEBUG_PRINT(DebugLevel_Verbose, "%s line %d - need to optimize invalidate rect\n", __FUNCTION__, __LINE__);
3213 
3214  __internal_invalidate_rect(pThis, NULL, 1); // scrolling, so invalidate everything
3215 
3216 // __internal_invalidate_rect(pThis, &rctInvalid, 1); // invalidate bounding rectangle
3217  }
3218  }
3219 }
3220 static void __internal_ins_chars(TEXT_OBJECT *pThis, const char *pChar, int nChar)
3221 {
3222 TEXT_BUFFER *pBuf;
3223 const char *p1, *p2;
3224 char *pL, *pTemp;
3225 int i1, iLen=0, iMultiLine = 0;
3226 WB_RECT rctInvalid;
3227 
3228 
3229  if(!pChar || !nChar)
3230  {
3231  return;
3232  }
3233 
3234  if(nChar < 0)
3235  {
3236  nChar = strlen(pChar);
3237  }
3238 
3239  p1 = pChar;
3240  p2 = pChar + nChar;
3241 
3242 
3243  // FIRST, see if the insert is single-line or multi-line and act accordingly
3244 
3245  while(p1 < p2)
3246  {
3247  if(*p1 == '\n' || *p1 == '\r')
3248  {
3249  iMultiLine = 1;
3250  break; // this will be used in single-line mode to mark "stop here"
3251  }
3252 
3253  p1++;
3254  }
3255 
3256  __internal_invalidate_cursor(pThis, 0); // always do this
3257 
3258  if(WBIsValidTextObject(pThis))
3259  {
3260  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
3261  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
3262  __FUNCTION__, __LINE__,
3263  pThis->iRow, pThis->iCol,
3264  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
3265 
3266  if(pThis->iRow < 0 || pThis->iCol< 0)
3267  {
3268  pThis->iRow = 0;
3269  pThis->iCol = 0;
3270  }
3271 
3272  __internal_invalidate_cursor(pThis, 0);
3273  pThis->iBlinkState = CURSOR_BLINK_RESET;
3274  // this affects the cursor blink, basically resetting it whenever I edit something
3275 
3276  __internal_calc_rect(pThis, &rctInvalid, pThis->iRow, pThis->iCol, pThis->iRow, -1);
3277  // always refresh entire line from 'iCol' forward
3278 
3279  pBuf = (TEXT_BUFFER *)(pThis->pText);
3280 
3281  if(pThis->iLineFeed == LineFeed_NONE) // single line
3282  {
3283  if(iMultiLine)
3284  {
3285  iMultiLine = 0;
3286  p2 = p1; // either end of string, or the position of the newline
3287  }
3288 
3289  p1 = pChar;
3290 
3291  // insert at 'iCol'
3292 
3293  if(!pBuf) // no buffer, so add text now
3294  {
3295  pBuf = pThis->pText = WBAllocTextBuffer(p1, p2 - p1); // this copies the data, too
3296 
3297  pThis->iRow = 0; // always
3298  pThis->iCol = WBGetMBColIndex(p1, p2); // the col index of 'p2' within 'p1' ('MB' length of 'p1 through p2')
3299  // new col will be 'end of string'. by using this value, it's effectively, like pressing 'end' after inserting
3300 
3301  __internal_add_undo(pThis, UNDO_INSERT, pThis->iSelMode,
3302  pThis->iRow, 0, &(pThis->rctSel), NULL, 0, // old col was 0, always
3303  pThis->iRow, pThis->iCol, NULL, // new col already assigned
3304  p1, p2 - p1); // the new text
3305  }
3306  else if(p2 > p1) // adding text to existing buffer
3307  {
3308  if(pThis->iRow != 0)
3309  {
3310  pThis->iRow = 0; // force it
3311  }
3312 
3313  iLen = 0; // pre-assign for later (so it won't be "unassigned" by accident on error)
3314 
3315  //------------------------------------------------
3316  // calculating buffer size, allocating line buffer
3317  //------------------------------------------------
3318 
3319  if(pBuf->nEntries <= 0 || !pBuf->aLines[0])
3320  {
3321  if(pBuf->nEntries <= 0)
3322  {
3323  pBuf->nEntries = 1; // single-line, always this, but not verifying for now
3324  }
3325 
3326  pL = pBuf->aLines[0] = WBAlloc(pThis->iCol + p2 - p1 + 2);
3327 
3328  if(pL)
3329  {
3330  if(pThis->iCol > 0) // this works because a space is always 1 byte long
3331  {
3332  memset(pL, ' ', pThis->iCol);
3333  pL[pThis->iCol] = 0;
3334  }
3335 
3336  iLen = pThis->iCol;
3337  }
3338  }
3339  else
3340  {
3341  pTemp = pBuf->aLines[0];
3342 
3343  i1 = strlen(pTemp); // the actual length
3344 
3345  iLen = WBGetMBLength(pTemp); // the # of columns
3346 
3347  if(iLen < pThis->iCol)
3348  {
3349  i1 += pThis->iCol - iLen; // add the alloc length for white space following 'iCol'
3350  }
3351 
3352  if(pThis->iInsMode == InsertMode_OVERWRITE &&
3353  iLen >= pThis->iCol) // only if I actually overwrite something
3354  {
3355  if(iLen < pThis->iCol + WBGetMBColIndex(p1, p2)) // will overwrite extend the string??
3356  {
3357  char *pTempC = WBGetMBCharPtr(pTemp, pThis->iCol, NULL);
3358 
3359  if(pTempC)
3360  {
3361  i1 = (pTempC - pTemp) + (p2 - p1); // extent of teh text I'm adding when I overwrite
3362  }
3363  else
3364  {
3365  i1 += p2 - p1; // fallback in case of problems
3366  }
3367  }
3368  }
3369  else
3370  {
3371  i1 += p2 - p1; // I'm extending the string by 'that much'
3372  }
3373 
3374  pL = WBReAlloc(pTemp, i1 + 2);
3375 
3376  if(pL)
3377  {
3378  pBuf->aLines[0] = pL;
3379  }
3380  else
3381  {
3382  WB_ERROR_PRINT("ERROR: %s - not enough memory\n", __FUNCTION__);
3383  }
3384  }
3385 
3386  //-------------------------------------------
3387  // copying data into buffer at column 'iCol'
3388  //-------------------------------------------
3389 
3390  if(pL)
3391  {
3392  char *pTempL;
3393  if(iLen < pThis->iCol)
3394  {
3395  pTempL = pL + strlen(pL); // will always be this
3396 
3397  memset(pTempL, ' ', pThis->iCol - iLen); // pad with spaces
3398 
3399  pTempL += pThis->iCol - iLen; // advance the pointer to where 'iCol' is
3400  *pTempL = 0; // make sure it ends in a zero byte
3401  }
3402  else if(iLen > pThis->iCol && // insert 'in the middle'
3403  pThis->iInsMode != InsertMode_OVERWRITE) // NOT overwriting
3404  {
3405  pTempL = WBGetMBCharPtr(pL, pThis->iCol, NULL);
3406 
3407  // make room for text
3408  if(WB_LIKELY((iLen - pThis->iCol + 1) > 0)) // probably always true
3409  {
3410  memmove(pTempL + (p2 - p1), pTempL, strlen(pTempL) + 1); // note that this includes the zero byte at the end
3411  }
3412  }
3413  else if((p2 - p1) + pThis->iCol >= iLen) // overwriting AND it extends the buffer?
3414  {
3415  pTempL = WBGetMBCharPtr(pL, pThis->iCol, NULL);
3416 
3417  pTempL[p2 - p1] = 0; // I need a terminating zero byte immediately after where the text will go
3418  }
3419 
3420  __internal_add_undo(pThis, UNDO_INSERT, pThis->iSelMode,
3421  pThis->iRow, pThis->iCol, &(pThis->rctSel),
3422  (pThis->iInsMode == InsertMode_OVERWRITE && pThis->iCol < iLen ?
3423  pL + pThis->iCol : NULL), // for overwrite, it's the original text
3424  (pThis->iInsMode == InsertMode_OVERWRITE && pThis->iCol < iLen ?
3425  p2 - p1 : 0), // length of old text for overwrite
3426  pThis->iRow, pThis->iCol + (p2 - p1), NULL,
3427  p1, p2 - p1); // the new text
3428 
3429  memcpy(pL + pThis->iCol, p1, p2 - p1); // insert the data (but not the terminating zero byte)
3430 
3431  pThis->iCol += WBGetMBColIndex(p1, p2); // always advance the cursor to this point (overwrite OR insert)
3432  }
3433  }
3434 
3435  if(pThis->pColorContextCallback)
3436  {
3437  pThis->pColorContextCallback(pThis, -1, -1); // to refresh it
3438  }
3439  }
3440  else // multi-line text
3441  {
3442 
3443  // TODO: auto-vscroll while inserting text, make sure cursor position is visible
3444 
3445  // insert at 'iCol'
3446 
3447  if(!pBuf) // no buffer, so add text now
3448  {
3449  p1 = pChar;
3450  p2 = pChar + nChar;
3451 
3452  pBuf = pThis->pText = WBAllocTextBuffer(pChar, nChar);
3453 
3454  pThis->iRow = 0; // always
3455  pThis->iCol = nChar; // effectively, like pressing 'end' after inserting
3456 
3457  __internal_add_undo(pThis, UNDO_INSERT, pThis->iSelMode,
3458  pThis->iRow, 0, &(pThis->rctSel), NULL, 0, // old col was 0, always
3459  pThis->iRow, pThis->iCol, NULL, // new col already assigned
3460  pChar, nChar); // the new text
3461  }
3462  else // add to existing buffer
3463  {
3464  // NOW, parse into 'lines' and add text as needed
3465  const char *p3 = pChar + nChar; // p3 is 'end of text' marker now
3466  p2 = pChar; // also marks 'end of line' for insertion
3467 
3468 
3469  while(p2 < p3)
3470  {
3471  int nTabs = 0;
3472  p1 = p2; // new 'start of line' pointer (for insertion)
3473 
3474  while(p2 < p3 && *p2 != '\n' && *p2 != '\r')
3475  {
3476  if(*p2 == '\t') // hard tab insertion?
3477  {
3478  nTabs++; // count them (for now); this is for allocation purposes
3479 
3480  // NOTE: if hard tabs are supported, I will be storing the white space
3481  // as a special character
3482  }
3483 
3484  p2++;
3485  }
3486 
3487  // p2 now points just past the end of the 'line' I am inserting, and points to
3488  // either a '\r' or a '\n' (which will be inserted)
3489 
3490  // there are 3 scenarious:
3491  // a) inserting a line feed (no text)
3492  // b) inserting text without a line feed
3493  // c) inserting text WITH a line feed (or more than one line feed)
3494  //
3495  // and the insert condition: insert OR overwrite
3496  // a) if overwrite exceeds the length of the line, it appends to the same line.
3497  // b) if overwrite 'overwrites' with a line feed, it acts like 'insert' at that point (gedit does this too)
3498  // c) if insert or overwrite adds a line feed, a new line is created with the remaining text from the line
3499  // and subsequent lines are moved down by 1 to make space for it
3500 
3501  // I'll deal with 'inserting text' first, then the line feed separately
3502 
3503  // TODO: use WBSplitMBLine() if there's a linefeed
3504  // otherwise, WBInsertMBChars()
3505 
3506  if(pBuf->nEntries <= 0 || !pBuf->aLines[0])
3507  {
3508  if(pBuf->nEntries <= 0)
3509  {
3510  pBuf->nEntries = 1; // single-line, always this, but not verifying for now
3511  }
3512 
3513  pL = pBuf->aLines[0] = WBAlloc(pThis->iCol + (p2 - p1)
3514  + nTabs * pThis->iTab // extra space for tabs
3515  + 2); // 2 additional spaces
3516 
3517  if(pL)
3518  {
3519  if(pThis->iCol > 0) // prepend with spaces
3520  {
3521  memset(pL, ' ', pThis->iCol);
3522  }
3523 
3524  pL[pThis->iCol] = 0; // mark end of string
3525 
3526  iLen = pThis->iCol; // in this case it will be
3527  }
3528  }
3529  else
3530  {
3531  pL = NULL; // a flag for success-testing
3532 
3533  if(WBCheckReAllocTextBuffer(&pBuf, 1) || !pBuf)
3534  {
3535  WB_ERROR_PRINT("ERROR: %s - not enough memory to add line\n", __FUNCTION__);
3536  }
3537  else
3538  {
3539  pThis->pText = pBuf; // re-assign in case there's a new ptr
3540 
3541  pTemp = pBuf->aLines[pThis->iRow];
3542 
3543  // pTemp may be NULL if I'm at the end...
3544  if(pTemp)
3545  {
3546  i1 = strlen(pTemp); // the actual length
3547  iLen = WBGetMBLength(pTemp); // # of columns
3548  }
3549  else
3550  {
3551  i1 = iLen = 0;
3552  }
3553 
3554  if(iLen < pThis->iCol)
3555  {
3556  i1 += pThis->iCol - iLen; // the necessary alloc length
3557  }
3558 
3559  if(pThis->iInsMode == InsertMode_OVERWRITE
3560  && iLen > pThis->iCol)
3561  {
3562  if(iLen < pThis->iCol + (p2 - p1))
3563  {
3564  i1 += (p2 - p1) - (iLen - pThis->iCol); // estimated extent of the text I'm adding
3565  }
3566  }
3567  else
3568  {
3569  i1 += p2 - p1; // extending string length
3570  }
3571 
3572  if(pTemp)
3573  {
3574  pL = WBReAlloc(pTemp, i1
3575  + nTabs * pThis->iTab // extra space for tabs
3576  + 2); // re-allocate to fit (plus 2 extra chars)
3577  }
3578  else
3579  {
3580  pL = WBAlloc(i1
3581  + nTabs * pThis->iTab // extra space for tabs
3582  + 2); // allocate to fit (plus 2 extra chars)
3583  }
3584 
3585  if(pL)
3586  {
3587  pBuf->aLines[pThis->iRow] = pL;
3588  }
3589  else
3590  {
3591  WB_ERROR_PRINT("ERROR: %s - not enough memory\n", __FUNCTION__);
3592  }
3593  }
3594  }
3595 
3596  if(pL) // only if above section worked properly
3597  {
3598  if(iLen < pThis->iCol)
3599  {
3600  pTemp = WBGetMBCharPtr(pL, iLen, NULL);
3601 
3602  memset(pTemp, ' ', pThis->iCol - iLen); // pad with spaces
3603  pTemp[pThis->iCol - iLen] = 0; // I need a terminating zero byte
3604 
3605  iLen = pThis->iCol;
3606  }
3607  else if(iLen > pThis->iCol && // insert 'in the middle'
3608  pThis->iInsMode != InsertMode_OVERWRITE) // NOT overwriting
3609  {
3610  // make room for text
3611  if(WB_LIKELY((iLen - pThis->iCol + 1) > 0)) // probably always true
3612  {
3613  memmove(pL + pThis->iCol + (p2 - p1), pL + pThis->iCol, iLen - pThis->iCol + 1);
3614  // note that this includes teh zero byte.
3615  }
3616  }
3617  else if((p2 - p1) + pThis->iCol >= iLen)
3618  {
3619  pL[pThis->iCol + p2 - p1] = 0; // I need a terminating zero byte
3620  }
3621 
3622  __internal_add_undo(pThis, UNDO_INSERT, pThis->iSelMode,
3623  pThis->iRow, pThis->iCol, &(pThis->rctSel),
3624  (pThis->iInsMode == InsertMode_OVERWRITE && pThis->iCol < iLen ?
3625  pL + pThis->iCol : NULL), // for overwrite, it's the original text
3626  (pThis->iInsMode == InsertMode_OVERWRITE && pThis->iCol < iLen ?
3627  p2 - p1 : 0), // length of old text for overwrite
3628  pThis->iRow, pThis->iCol + (p2 - p1), NULL,
3629  p1, p2 - p1); // the new text
3630 
3631  memcpy(pL + pThis->iCol, p1, p2 - p1); // insert the data
3632 
3633  // the end of the loop
3634 
3635  if(p2 > p1)
3636  {
3637  pThis->iCol += p2 - p1; // always advance the cursor to this point (overwrite OR insert)
3638  }
3639 
3640  // if last char is a newline, split the line at 'iCol'
3641 
3642  if(p2 < p3 && (*p2 == '\r' || *p2 == '\n')) // a newline
3643  {
3644  char c1 = *p2;
3645 
3646  p2++; // advance past the newline
3647 
3648  if(p2 < p3 && *p2 != c1 &&
3649  (*p2 == '\r' || *p2 == '\n')) // CRLF or LFCR
3650  {
3651  p2++; // skip this one too
3652  }
3653 
3654  // insert a new row after this one. But first, does pBuf have enough space?
3655 
3656  if(WBCheckReAllocTextBuffer(&pBuf, 1) || !pBuf)
3657  {
3658  WB_ERROR_PRINT("ERROR: %s - not enough memory to add line\n", __FUNCTION__);
3659 
3660  break; // bust out of loop - cannot insert any more
3661  }
3662  else
3663  {
3664  pThis->pText = pBuf; // re-assign in case there's a new ptr
3665 
3666  WB_DEBUG_PRINT(DebugLevel_Verbose, "%s - split line, %d, %d, %d\n", __FUNCTION__,
3667  pThis->iRow, pThis->iCol, (int)pBuf->nEntries);
3668 
3669  for(i1=pBuf->nEntries; i1 > (pThis->iRow + 1); i1--)
3670  {
3671  pBuf->aLines[i1] = pBuf->aLines[i1 - 1]; // make room for new line
3672  }
3673 
3674  pBuf->nEntries++;
3675 
3676  // TODO: if insert blank line, do I just leave it 'NULL' and not make a copy?
3677 
3678  pTemp = WBGetMBCharPtr(pL, pThis->iCol, NULL); // point at which I insert the newline
3679 
3680  pBuf->aLines[pThis->iRow + 1] = WBCopyString(pTemp); // make a copy of previous at "that point"
3681  *pTemp = 0; // terminate line at "that point" (TODO: trim right?)
3682 
3683  WB_DEBUG_PRINT(DebugLevel_Verbose, "%s - split line into \"%s\", \"%s\"\n", __FUNCTION__,
3684  pBuf->aLines[pThis->iRow],
3685  pBuf->aLines[pThis->iRow + 1]);
3686 
3687 
3688  pThis->iRow++; // advance row position
3689  pThis->iCol = 0; // newline forces column 0, always
3690 
3691  }
3692  }
3693  }
3694  }
3695  }
3696 
3697  if(pThis->pColorContextCallback) // TODO: if I only need to invalidate one line, should I just do that?
3698  {
3699  pThis->pColorContextCallback(pThis, -1, -1); // to refresh it
3700  }
3701 
3702  WB_DEBUG_PRINT(DebugLevel_Verbose, "%s line %d - need to optimize invalidate rect\n", __FUNCTION__, __LINE__);
3703 
3704  __internal_invalidate_rect(pThis, NULL, 1); // so invalidate everything (for NOW)
3705  }
3706 
3707  // auto-hscroll while inserting text, make sure cursor position is visible
3708 
3709  if(pThis->rctView.right > pThis->rctView.left)
3710  {
3711  if(iMultiLine ||
3712  pThis->rctView.right <= pThis->iCol ||
3713  pThis->rctView.left > pThis->iCol)
3714  {
3715  int iAutoScrollWidth = AUTO_HSCROLL_SIZE;
3716 
3717  while(iAutoScrollWidth > 1 && iAutoScrollWidth >= pThis->rctView.right - pThis->rctView.left)
3718  {
3719  iAutoScrollWidth >>= 1;
3720  }
3721 
3722  // scroll left/right to expose the cursor
3723 
3724  while(pThis->rctView.left > pThis->iCol)
3725  {
3726  pThis->rctView.left -= iAutoScrollWidth;
3727  pThis->rctView.right -= iAutoScrollWidth;
3728  }
3729  while(pThis->rctView.right <= pThis->iCol)
3730  {
3731  pThis->rctView.left += iAutoScrollWidth;
3732  pThis->rctView.right += iAutoScrollWidth;
3733  }
3734 
3735  WB_DEBUG_PRINT(DebugLevel_Verbose, "%s line %d - need to optimize invalidate rect\n", __FUNCTION__, __LINE__);
3736  __internal_invalidate_rect(pThis, NULL, 1); // scrolling, so invalidate everything
3737  }
3738  else
3739  {
3740  __internal_invalidate_rect(pThis, &rctInvalid, 1); // invalidate bounding rectangle
3741  }
3742  }
3743  else
3744  {
3745  WB_WARN_PRINT("WARNING: %s line %d - invalidate entire window rect\n", __FUNCTION__, __LINE__);
3746 
3747  __internal_invalidate_rect(pThis, NULL, 1); // invalidate everything (confusion handler)
3748  }
3749 
3750  if(iMultiLine || !pBuf)
3751  {
3753  }
3754  else if(pThis->iRow < pBuf->nEntries)
3755  {
3756  WBTextBufferLineChange(pThis->pText, pThis->iRow,
3757  WBGetMBLength(pBuf->aLines[pThis->iRow])); // new length
3758  }
3759  }
3760 
3761  // TODO: anything else??
3762 }
3763 static void __internal_indent(TEXT_OBJECT *pThis, int nCol)
3764 {
3765  if(WBIsValidTextObject(pThis))
3766  {
3767  WB_ERROR_PRINT("TODO: %s - implement 'indent'\n", __FUNCTION__);
3768  }
3769 }
3770 static int __internal_can_undo(TEXT_OBJECT *pThis)
3771 {
3772  if(WBIsValidTextObject(pThis))
3773  {
3774  }
3775 
3776  return 0; // for now
3777 }
3778 static void __internal_undo(TEXT_OBJECT *pThis)
3779 {
3780  if(WBIsValidTextObject(pThis))
3781  {
3782  WB_ERROR_PRINT("TODO: %s - implement 'undo'\n", __FUNCTION__);
3783  }
3784 }
3785 static int __internal_can_redo(TEXT_OBJECT *pThis)
3786 {
3787  if(WBIsValidTextObject(pThis))
3788  {
3789  }
3790 
3791  return 0; // for now
3792 }
3793 static void __internal_redo(TEXT_OBJECT *pThis)
3794 {
3795  if(WBIsValidTextObject(pThis))
3796  {
3797  WB_ERROR_PRINT("TODO: %s - implement 'redo'\n", __FUNCTION__);
3798  }
3799 }
3800 static void __internal_get_view(const TEXT_OBJECT *pThis, WB_RECT *pRct)
3801 {
3802  if(!pRct)
3803  {
3804  return;
3805  }
3806 
3807  if(WBIsValidTextObject(pThis))
3808  {
3809  memcpy(pRct, &(pThis->rctView), sizeof(*pRct));
3810  }
3811 }
3812 static void __internal_set_view_origin(TEXT_OBJECT *pThis, const WB_POINT *pOrig)
3813 {
3814  if(!pOrig)
3815  {
3816  return;
3817  }
3818 
3819  if(WBIsValidTextObject(pThis))
3820  {
3821  int nRows = __internal_get_rows(pThis);
3822  int nCols = __internal_get_cols(pThis);
3823 
3824  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
3825  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
3826  __FUNCTION__, __LINE__,
3827  pThis->iRow, pThis->iCol,
3828  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
3829 
3830  nRows -= (pThis->rctView.bottom - pThis->rctView.top); // note that 'nRows' includes the blank line at the end
3831  if(nRows < 0)
3832  {
3833  nRows = 0;
3834  }
3835 
3836  nCols -= (pThis->rctView.right - pThis->rctView.left - 4); // TODO: what constant do I subtract?
3837  if(nCols < 0)
3838  {
3839  nCols = 0;
3840  }
3841 
3842  if(pOrig->x < 0)
3843  {
3844  nCols = 0;
3845  }
3846  else if(pOrig->x < nCols)
3847  {
3848  nCols = pOrig->x;
3849  }
3850 
3851  if(pOrig->y < 0)
3852  {
3853  nRows = 0;
3854  }
3855  else if(pOrig->y < nRows)
3856  {
3857  nRows = pOrig->y;
3858  }
3859 
3860  if(nRows != pThis->rctView.top || nCols != pThis->rctView.left)
3861  {
3862  pThis->rctView.bottom = (pThis->rctView.bottom - pThis->rctView.top)
3863  + nRows;
3864  pThis->rctView.top = nRows;
3865 
3866  pThis->rctView.right = (pThis->rctView.right - pThis->rctView.left)
3867  + nCols;
3868  pThis->rctView.left = nCols;
3869 
3870  __internal_invalidate_rect(pThis, NULL, 1); // invalidate the display and re-draw
3871  }
3872  }
3873 }
3874 static void __internal_begin_highlight(TEXT_OBJECT *pThis)
3875 {
3876  if(WBIsValidTextObject(pThis))
3877  {
3878  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
3879  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
3880  __FUNCTION__, __LINE__,
3881  pThis->iRow, pThis->iCol,
3882  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
3883 
3884  pThis->iDragState = DragState_CURSOR;
3885  }
3886 }
3887 static void __internal_end_highlight(TEXT_OBJECT *pThis)
3888 {
3889  if(WBIsValidTextObject(pThis))
3890  {
3891  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
3892  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
3893  __FUNCTION__, __LINE__,
3894  pThis->iRow, pThis->iCol,
3895  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
3896 
3897  pThis->iDragState = DragState_NONE;
3898  }
3899 }
3900 static void __internal_mouse_click(TEXT_OBJECT *pThis, int iMouseXDelta, int iMouseYDelta, int iType, int iACS)
3901 {
3902 TEXT_BUFFER *pBuf;
3903 int iRow = -1, iCol = -1; // pre-assign error returns
3904 int iFontHeight;
3905 WB_RECT rctSel;
3906 
3907  // NOTE: if this has never been painted, I can't respond to this request
3908 
3909  if(WBIsValidTextObject(pThis))
3910  {
3911  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
3912  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
3913  __FUNCTION__, __LINE__,
3914  pThis->iRow, pThis->iCol,
3915  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
3916 
3917  pBuf = (TEXT_BUFFER *)(pThis->pText);
3918 
3919  memset(&rctSel, 0, sizeof(rctSel));
3920 
3921  if(!(pThis->iDragState & DragState_MOUSE)) // not mouse-dragging at the moment
3922  {
3923  // clear the selection if there is one
3924  if(!SEL_RECT_EMPTY(pThis))
3925  {
3926  __internal_invalidate_rect(pThis, &(pThis->rctHighLight), 0); // invalidate the highlight rectangle
3927 
3928  // clearing the selection (TODO: do this with a utility function?)
3929  memset(&(pThis->rctSel), 0, sizeof(pThis->rctSel));
3930  memset(&(pThis->rctHighLight), 0, sizeof(pThis->rctHighLight));
3931  }
3932  }
3933 
3934  iFontHeight = pThis->iAsc + pThis->iDesc;
3935 
3936  if(iFontHeight && pThis->iFontWidth &&
3937  pThis->rctWinView.left < pThis->rctWinView.right &&
3938  pThis->rctWinView.top < pThis->rctWinView.bottom)
3939  {
3940  if(pThis->iLineFeed == LineFeed_NONE) // i.e. "single line"
3941  {
3942  iRow = 0; // always
3943  }
3944  else
3945  {
3946  iFontHeight = WBTextObjectCalculateLineHeight(pThis->iAsc, pThis->iDesc);
3947 
3948  iRow = pThis->rctView.top
3949  + (iMouseYDelta + iFontHeight / 4 - pThis->rctWinView.top) / iFontHeight;
3950 
3951  if(iRow < 0 || !pBuf)
3952  {
3953  iRow = 0;
3954  }
3955  else if(iRow > pBuf->nEntries)
3956  {
3957  iRow = pBuf->nEntries;
3958  }
3959  }
3960 
3961  // column calculation is no different between single-line and multi-line
3962  iCol = pThis->rctView.left
3963  + (iMouseXDelta + pThis->iFontWidth / 4 - pThis->rctWinView.left) / pThis->iFontWidth;
3964 
3965  if(iCol < 0 || !pBuf || iRow >= pBuf->nEntries || !pBuf->aLines[iRow])
3966  {
3967  iCol = 0; // for now also force column 0 if there's no buffer or a NULL entry for it or at end of document
3968  }
3969  else if(pThis->iLineFeed == LineFeed_NONE &&
3970  iCol > WBGetMBLength(pBuf->aLines[iRow]))
3971  {
3972  iCol = WBGetMBLength(pBuf->aLines[iRow]); // don't select pos past end of line for single-line edit
3973  }
3974  }
3975 
3976  // if I am doing a mouse-drag, and the selection is empty, start a new selection.
3977  // if I am doing a cursor-drag, disable it.
3978 
3979  if(pThis->iDragState & DragState_CURSOR)
3980  {
3981  pThis->iDragState = DragState_NONE; // turn it off now.
3982 
3983  if(iType == WB_POINTER_BUTTON1 && iACS == 0) // only if left-click for now
3984  {
3985  pThis->iCol = iCol;
3986  pThis->iRow = iRow;
3987  }
3988  }
3989  else if(!(pThis->iDragState & DragState_MOUSE))
3990  {
3991  if(iType == WB_POINTER_BUTTON1 && iACS == 0) // only if left-click for now
3992  {
3993  pThis->iCol = iCol;
3994  pThis->iRow = iRow;
3995  }
3996  else
3997  {
3998  pThis->iDragState = DragState_NONE; // turn dragging off
3999 
4000  // TODO: check for wheel buttons WB_POINTER_BUTTON4 and WB_POINTER_BUTTON5 ?
4001  }
4002  }
4003  else if(iType == 0 && iACS == 0) // a mouse motion message while dragging
4004  {
4005  // see if the select rectangle is empty. If it is, then I
4006  // am just starting the drag and I need to anchor the cursor
4007 
4008  // NOTE: cursor anchor is always (left,top) even if it's a negative distance
4009 
4010  if(SEL_RECT_EMPTY(pThis))
4011  {
4012  WB_DEBUG_PRINT(DebugLevel_Medium | DebugSubSystem_TextObject, "%s line %d - empty selection, starting the drag\n", __FUNCTION__, __LINE__);
4013 
4014  pThis->rctSel.top = pThis->iRow; // old row
4015  pThis->rctSel.left = pThis->iCol; // previous column
4016  }
4017 
4018  pThis->rctSel.bottom = iRow; // new row
4019  pThis->rctSel.right = iCol; // new column
4020 
4021  pThis->iCol = iCol;
4022  pThis->iRow = iRow;
4023  }
4024  else
4025  {
4026  // TODO: cancel dragging? or not... [maybe check for right-click or scroll]
4027 
4028  // TODO: check for wheel buttons WB_POINTER_BUTTON4 and WB_POINTER_BUTTON5 ?
4029  // for a mouse drag I may enable this, but probably should just shut off the drag
4030  // maybe shift+wheel would select...? testing it shows that it scrolls the viewport
4031 
4032  pThis->iDragState = DragState_NONE; // turn dragging off, for now
4033  }
4034 
4035  // for now mouse-click invalidates the entire rectangle. Later I fix this.
4036 
4037  WB_DEBUG_PRINT(DebugLevel_Verbose, "%s line %d - need to optimize invalidate rect\n", __FUNCTION__, __LINE__);
4038 
4039  __internal_invalidate_rect(pThis, NULL, 1);
4040  }
4041 }
4042 static void __internal_begin_mouse_drag(TEXT_OBJECT *pThis)
4043 {
4044  if(WBIsValidTextObject(pThis))
4045  {
4046  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
4047  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
4048  __FUNCTION__, __LINE__,
4049  pThis->iRow, pThis->iCol,
4050  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
4051 
4052 // WB_ERROR_PRINT("TEMPORARY - %s\n", __FUNCTION__);
4053  pThis->iDragState = DragState_MOUSE;
4054  }
4055 }
4056 static void __internal_end_mouse_drag(TEXT_OBJECT *pThis)
4057 {
4058  if(WBIsValidTextObject(pThis))
4059  {
4060  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
4061  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
4062  __FUNCTION__, __LINE__,
4063  pThis->iRow, pThis->iCol,
4064  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
4065 
4066 // WB_ERROR_PRINT("TEMPORARY - %s\n", __FUNCTION__);
4067  pThis->iDragState = DragState_NONE;
4068  }
4069 }
4070 static void __internal_cursor_up(TEXT_OBJECT *pThis)
4071 {
4072 TEXT_BUFFER *pBuf;
4073 
4074 
4075  if(!WBIsValidTextObject(pThis))
4076  {
4077  WB_ERROR_PRINT("ERROR: %p - invalid text object %p\n", __FUNCTION__, pThis);
4078  }
4079  else
4080  {
4081  int iOldRow = pThis->iRow;
4082 
4083  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
4084  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
4085  __FUNCTION__, __LINE__,
4086  pThis->iRow, pThis->iCol,
4087  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
4088 
4089  pThis->iBlinkState = CURSOR_BLINK_RESET; // this affects the cursor blink, basically resetting it whenever I edit something
4090 
4091  if(pThis->iLineFeed == LineFeed_NONE) // single line
4092  {
4093  return; // do nothing
4094  }
4095 
4096  __internal_invalidate_cursor(pThis, 0); // invalidate current cursor rectangle
4097 
4098  pBuf = (TEXT_BUFFER *)(pThis->pText);
4099 
4100  if(!pBuf)
4101  {
4102  pThis->iRow = 0;
4103  pThis->iCol = 0;
4104  }
4105  else if(pBuf->nEntries < pThis->iRow)
4106  {
4107  pThis->iRow = pBuf->nEntries;
4108  pThis->iCol = 0;
4109  }
4110  else if(pThis->iRow > 0)
4111  {
4112  pThis->iRow--;
4113  }
4114  else
4115  {
4116  pThis->iRow = 0;
4117  }
4118 
4119  if(pThis->iDragState & DragState_CURSOR)
4120  {
4121  if(SEL_RECT_EMPTY(pThis))
4122  {
4123  WB_DEBUG_PRINT(DebugLevel_Medium | DebugSubSystem_TextObject, "%s line %d - empty selection, starting select\n", __FUNCTION__, __LINE__);
4124 
4125  pThis->rctSel.left = pThis->iCol;
4126  pThis->rctSel.top = iOldRow;
4127  }
4128 
4129  pThis->rctSel.right = pThis->iCol;
4130  pThis->rctSel.bottom = pThis->iRow;
4131  }
4132  else
4133  {
4134  // clear the selection if there is one
4135  if(!SEL_RECT_EMPTY(pThis))
4136  {
4137  __internal_invalidate_rect(pThis, &(pThis->rctHighLight), 0); // invalidate the highlight rectangle
4138  memset(&(pThis->rctSel), 0, sizeof(pThis->rctSel));
4139  memset(&(pThis->rctHighLight), 0, sizeof(pThis->rctHighLight));
4140  }
4141  }
4142 
4143  if(pThis->rctView.top > pThis->iRow ||
4144  pThis->rctView.bottom <= pThis->iRow)
4145  {
4146  WB_WARN_PRINT("WARNING: %s line %d - invalidate entire window rect\n", __FUNCTION__, __LINE__);
4147 
4148  __internal_invalidate_rect(pThis, NULL, 1); // scrolling invalidates all
4149 
4150  // scroll right/left so that I have one additional white space visible
4151  if(pThis->rctView.top > pThis->iRow)
4152  {
4153  pThis->rctView.bottom -= (pThis->rctView.top - pThis->iRow);
4154  pThis->rctView.top = pThis->iRow; // new top row
4155  }
4156  else if(pThis->rctView.bottom <= pThis->iRow)
4157  {
4158  pThis->rctView.top += pThis->iRow + 1 - pThis->rctView.bottom;
4159  pThis->rctView.bottom = pThis->iRow + 1;
4160  }
4161  }
4162  else // re-calculate cursor metrics
4163  {
4164  pThis->iCursorY += WBTextObjectCalculateLineHeight(pThis->iAsc, pThis->iDesc)
4165  * (pThis->iRow - iOldRow); // will effectively subtract
4166 
4167  __internal_invalidate_cursor(pThis, 1); // invalidate the NEW cursor rectangle
4168  }
4169  }
4170 }
4171 static void __internal_cursor_down(TEXT_OBJECT *pThis)
4172 {
4173 TEXT_BUFFER *pBuf;
4174 
4175 
4176  if(!WBIsValidTextObject(pThis))
4177  {
4178  WB_ERROR_PRINT("ERROR: %p - invalid text object %p\n", __FUNCTION__, pThis);
4179  }
4180  else
4181  {
4182  int iOldRow = pThis->iRow;
4183 
4184  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
4185  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
4186  __FUNCTION__, __LINE__,
4187  pThis->iRow, pThis->iCol,
4188  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
4189 
4190  pThis->iBlinkState = CURSOR_BLINK_RESET; // this affects the cursor blink, basically resetting it whenever I edit something
4191 
4192  if(pThis->iLineFeed == LineFeed_NONE) // single line
4193  {
4194  return; // do nothing
4195  }
4196 
4197  __internal_invalidate_cursor(pThis, 0); // invalidate current cursor rectangle
4198 
4199  pBuf = (TEXT_BUFFER *)(pThis->pText);
4200 
4201  if(!pBuf)
4202  {
4203  pThis->iRow = 0;
4204  pThis->iCol = 0;
4205  }
4206  else if(pThis->iRow >= (pBuf->nEntries - 1))
4207  {
4208  pThis->iRow = pBuf->nEntries;
4209  pThis->iCol = 0;
4210  }
4211  else
4212  {
4213  pThis->iRow++;
4214  }
4215 
4216  if(pThis->iDragState & DragState_CURSOR)
4217  {
4218  if(SEL_RECT_EMPTY(pThis))
4219  {
4220  WB_DEBUG_PRINT(DebugLevel_Medium | DebugSubSystem_TextObject, "%s line %d - empty selection, starting select\n", __FUNCTION__, __LINE__);
4221 
4222  pThis->rctSel.left = pThis->iCol;
4223  pThis->rctSel.top = iOldRow;
4224  }
4225 
4226  pThis->rctSel.right = pThis->iCol;
4227  pThis->rctSel.bottom = pThis->iRow;
4228  }
4229  else
4230  {
4231  // clear the selection if there is one
4232  if(!SEL_RECT_EMPTY(pThis))
4233  {
4234  __internal_invalidate_rect(pThis, &(pThis->rctHighLight), 0); // invalidate the highlight rectangle
4235  memset(&(pThis->rctSel), 0, sizeof(pThis->rctSel));
4236  memset(&(pThis->rctHighLight), 0, sizeof(pThis->rctHighLight));
4237  }
4238  }
4239 
4240  if(pThis->rctView.top > pThis->iRow ||
4241  pThis->rctView.bottom <= pThis->iRow)
4242  {
4243  WB_WARN_PRINT("WARNING: %s line %d - invalidate entire window rect\n", __FUNCTION__, __LINE__);
4244 
4245  __internal_invalidate_rect(pThis, NULL, 1); // scrolling invalidates all
4246 
4247  // scroll down so that I have one additional white space visible
4248  if(pThis->rctView.top > pThis->iRow)
4249  {
4250  pThis->rctView.bottom -= (pThis->rctView.top - pThis->iRow);
4251  pThis->rctView.top = pThis->iRow; // new top row
4252  }
4253  else if(pThis->rctView.bottom <= pThis->iRow)
4254  {
4255  pThis->rctView.top += pThis->iRow + 1 - pThis->rctView.bottom;
4256  pThis->rctView.bottom = pThis->iRow + 1;
4257  }
4258  }
4259  else // re-calculate cursor metrics
4260  {
4261  pThis->iCursorY += WBTextObjectCalculateLineHeight(pThis->iAsc, pThis->iDesc)
4262  * (pThis->iRow - iOldRow);
4263 
4264  __internal_invalidate_cursor(pThis, 1); // invalidate the NEW cursor rectangle
4265  }
4266  }
4267 }
4268 static void __internal_cursor_left(TEXT_OBJECT *pThis)
4269 {
4270 TEXT_BUFFER *pBuf;
4271 
4272 
4273  if(!WBIsValidTextObject(pThis))
4274  {
4275  WB_ERROR_PRINT("ERROR: %p - invalid text object %p\n", __FUNCTION__, pThis);
4276  }
4277  else
4278  {
4279  int iOldCol = pThis->iCol;
4280 
4281  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
4282  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
4283  __FUNCTION__, __LINE__,
4284  pThis->iRow, pThis->iCol,
4285  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
4286 
4287  pThis->iBlinkState = CURSOR_BLINK_RESET; // this affects the cursor blink, basically resetting it whenever I edit something
4288 
4289  __internal_invalidate_cursor(pThis, 0); // invalidate current cursor rectangle
4290 
4291  pBuf = (TEXT_BUFFER *)(pThis->pText);
4292 
4293  if(!pBuf)
4294  {
4295  pThis->iRow = 0;
4296  pThis->iCol = 0;
4297  }
4298  else if(pThis->iCol > 0)
4299  {
4300  pThis->iCol --;
4301  }
4302  else
4303  {
4304  pThis->iCol = 0;
4305  }
4306 
4307  if(pThis->iDragState & DragState_CURSOR)
4308  {
4309  if(SEL_RECT_EMPTY(pThis))
4310  {
4311  WB_DEBUG_PRINT(DebugLevel_Medium | DebugSubSystem_TextObject, "%s line %d - empty selection, starting select\n", __FUNCTION__, __LINE__);
4312 
4313  pThis->rctSel.left = iOldCol;
4314  pThis->rctSel.top = pThis->iRow;
4315  }
4316 
4317  pThis->rctSel.right = pThis->iCol;
4318  pThis->rctSel.bottom = pThis->iRow;
4319  }
4320  else
4321  {
4322  // clear the selection if there is one
4323  if(!SEL_RECT_EMPTY(pThis))
4324  {
4325  __internal_invalidate_rect(pThis, &(pThis->rctHighLight), 0); // invalidate the highlight rectangle
4326  memset(&(pThis->rctSel), 0, sizeof(pThis->rctSel));
4327  memset(&(pThis->rctHighLight), 0, sizeof(pThis->rctHighLight));
4328  }
4329  }
4330 
4331  if(pThis->rctView.right > pThis->rctView.left)
4332  {
4333  if(pThis->rctView.right <= pThis->iCol ||
4334  pThis->rctView.left > pThis->iCol)
4335  {
4336  // scroll right/left so that I have one additional white space visible
4337  if(pThis->rctView.left > pThis->iCol)
4338  {
4339  pThis->rctView.right -= (pThis->rctView.left - pThis->iCol);
4340  pThis->rctView.left = pThis->iCol;
4341  }
4342  else if(pThis->rctView.right <= pThis->iCol)
4343  {
4344  pThis->rctView.left += pThis->iCol + 1 - pThis->rctView.right;
4345  pThis->rctView.right = pThis->iCol + 1;
4346  }
4347 
4348  WB_WARN_PRINT("WARNING: %s line %d - invalidate entire window rect\n", __FUNCTION__, __LINE__);
4349 
4350  __internal_invalidate_rect(pThis, NULL, 1); // invalidate entire screen if I'm here
4351  }
4352  else // re-calculate cursor metrics
4353  {
4354  pThis->iCursorX += (pThis->iCol - iOldCol) * pThis->iFontWidth; // will effectively subtract
4355 
4356  __internal_invalidate_cursor(pThis, 1); // invalidate the NEW cursor rectangle
4357  }
4358  }
4359  else
4360  {
4361  WB_WARN_PRINT("WARNING: %s line %d - invalidate entire window rect\n", __FUNCTION__, __LINE__);
4362 
4363  __internal_invalidate_rect(pThis, NULL, 1); // invalidate entire screen if I'm here (confusion point)
4364  }
4365  }
4366 }
4367 static void __internal_cursor_right(TEXT_OBJECT *pThis)
4368 {
4369 TEXT_BUFFER *pBuf;
4370 
4371 
4372  if(!WBIsValidTextObject(pThis))
4373  {
4374  WB_ERROR_PRINT("ERROR: %p - invalid text object %p\n", __FUNCTION__, pThis);
4375  }
4376  else
4377  {
4378  int iOldCol = pThis->iCol;
4379 
4380  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
4381  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
4382  __FUNCTION__, __LINE__,
4383  pThis->iRow, pThis->iCol,
4384  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
4385 
4386  pThis->iBlinkState = CURSOR_BLINK_RESET; // this affects the cursor blink, basically resetting it whenever I edit something
4387 
4388  __internal_invalidate_cursor(pThis, 0); // invalidate current cursor rectangle
4389 
4390  pBuf = (TEXT_BUFFER *)(pThis->pText);
4391 
4392  if(!pBuf)
4393  {
4394  pThis->iRow = 0;
4395  pThis->iCol = 0;
4396  }
4397  else if(pThis->iCol < 0)
4398  {
4399  pThis->iCol = 0;
4400  }
4401  else if(pThis->iCol < INT_MAX)
4402  {
4403  if(pThis->iLineFeed == LineFeed_NONE) // single line
4404  {
4405  if(pBuf->nEntries <= 0 || !pBuf->aLines[0])
4406  {
4407  pThis->iCol = 0;
4408  }
4409  else
4410  {
4411  int iLen = WBGetMBLength(pBuf->aLines[0]);
4412 
4413  if(pThis->iRow != 0)
4414  {
4415  pThis->iRow = 0; // force it
4416  }
4417 
4418  // no 'virtual space' for single-line but trailing white space OK
4419 
4420  if(pThis->iCol >= iLen)
4421  {
4422  pThis->iCol = iLen;
4423  }
4424  else
4425  {
4426  pThis->iCol++;
4427  }
4428  }
4429  }
4430  else
4431  {
4432  pThis->iCol ++;
4433  }
4434  }
4435 
4436  if(pThis->iDragState & DragState_CURSOR)
4437  {
4438  if(SEL_RECT_EMPTY(pThis))
4439  {
4440  WB_DEBUG_PRINT(DebugLevel_Medium | DebugSubSystem_TextObject, "%s line %d - empty selection, starting select\n", __FUNCTION__, __LINE__);
4441 
4442  pThis->rctSel.left = iOldCol;
4443  pThis->rctSel.top = pThis->iRow;
4444  }
4445 
4446  pThis->rctSel.right = pThis->iCol;
4447  pThis->rctSel.bottom = pThis->iRow;
4448  }
4449  else
4450  {
4451  // clear the selection if there is one
4452  if(!SEL_RECT_EMPTY(pThis))
4453  {
4454  __internal_invalidate_rect(pThis, &(pThis->rctHighLight), 0); // invalidate the highlight rectangle
4455  memset(&(pThis->rctSel), 0, sizeof(pThis->rctSel));
4456  memset(&(pThis->rctHighLight), 0, sizeof(pThis->rctHighLight));
4457  }
4458  }
4459 
4460  if(pThis->rctView.right > pThis->rctView.left)
4461  {
4462  if(pThis->rctView.right <= pThis->iCol ||
4463  pThis->rctView.left > pThis->iCol)
4464  {
4465  // scroll right/left so that I have one additional white space visible
4466  if(pThis->rctView.left > pThis->iCol)
4467  {
4468  pThis->rctView.right -= (pThis->rctView.left - pThis->iCol);
4469  pThis->rctView.left = pThis->iCol;
4470  }
4471  else if(pThis->rctView.right <= pThis->iCol)
4472  {
4473  pThis->rctView.left += pThis->iCol + 1 - pThis->rctView.right;
4474  pThis->rctView.right = pThis->iCol + 1;
4475  }
4476 
4477  WB_WARN_PRINT("WARNING: %s line %d - invalidate entire window rect\n", __FUNCTION__, __LINE__);
4478 
4479  __internal_invalidate_rect(pThis, NULL, 1); // invalidate entire screen if I'm here
4480  }
4481  else // re-calculate cursor metrics
4482  {
4483  pThis->iCursorX += (pThis->iCol - iOldCol) * pThis->iFontWidth;
4484 
4485  __internal_invalidate_cursor(pThis, 1); // invalidate the NEW cursor rectangle
4486  }
4487  }
4488  else
4489  {
4490  WB_WARN_PRINT("WARNING: %s line %d - invalidate entire window rect\n", __FUNCTION__, __LINE__);
4491 
4492  __internal_invalidate_rect(pThis, NULL, 1); // invalidate entire screen if I'm here (confusion point)
4493  }
4494  }
4495 }
4496 static void __internal_page_up(TEXT_OBJECT *pThis)
4497 {
4498 TEXT_BUFFER *pBuf;
4499 
4500  if(!WBIsValidTextObject(pThis))
4501  {
4502  WB_ERROR_PRINT("ERROR: %p - invalid text object %p\n", __FUNCTION__, pThis);
4503  }
4504  else
4505  {
4506  int iOldRow = pThis->iRow;
4507  int iPageHeight = pThis->rctView.bottom - pThis->rctView.top;
4508 
4509  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
4510  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
4511  __FUNCTION__, __LINE__,
4512  pThis->iRow, pThis->iCol,
4513  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
4514 
4515  pThis->iBlinkState = CURSOR_BLINK_RESET; // this affects the cursor blink, basically resetting it whenever I edit something
4516 
4517  if(pThis->iLineFeed == LineFeed_NONE) // single line
4518  {
4519  return; // do nothing
4520  }
4521 
4522  __internal_invalidate_cursor(pThis, 0); // invalidate current cursor rectangle
4523 
4524  pBuf = (TEXT_BUFFER *)(pThis->pText);
4525 
4526  if(!pBuf)
4527  {
4528  pThis->iRow = 0;
4529  pThis->iCol = 0;
4530  }
4531  else if(pThis->iRow <= iPageHeight)
4532  {
4533  if(pThis->iRow <= 0) // check for negatives anyway
4534  {
4535  pThis->iCol = 0; // page-up on top line homes the cursor
4536  }
4537 
4538  pThis->iRow = 0;
4539  }
4540  else
4541  {
4542  pThis->iRow -= iPageHeight;
4543  }
4544 
4545  if(pThis->iDragState & DragState_CURSOR)
4546  {
4547  if(SEL_RECT_EMPTY(pThis))
4548  {
4549  WB_DEBUG_PRINT(DebugLevel_Medium | DebugSubSystem_TextObject, "%s line %d - empty selection, starting select\n", __FUNCTION__, __LINE__);
4550 
4551  pThis->rctSel.left = pThis->iCol;
4552  pThis->rctSel.top = iOldRow;
4553  }
4554 
4555  pThis->rctSel.right = pThis->iCol;
4556  pThis->rctSel.bottom = pThis->iRow;
4557  }
4558  else
4559  {
4560  // clear the selection if there is one
4561  if(!SEL_RECT_EMPTY(pThis))
4562  {
4563  __internal_invalidate_rect(pThis, &(pThis->rctHighLight), 0); // invalidate the highlight rectangle
4564  memset(&(pThis->rctSel), 0, sizeof(pThis->rctSel));
4565  memset(&(pThis->rctHighLight), 0, sizeof(pThis->rctHighLight));
4566  }
4567  }
4568 
4569  if(WB_LIKELY(pThis->rctView.top > pThis->iRow ||
4570  pThis->rctView.bottom <= pThis->iRow))
4571  {
4572  int iDelta = iOldRow - pThis->iRow;
4573 
4574  if((pThis->rctView.top - iDelta) > 0)
4575  {
4576  pThis->rctView.top -= iDelta;
4577  pThis->rctView.bottom -= iDelta;
4578  }
4579  else
4580  {
4581  pThis->rctView.bottom -= pThis->rctView.top;
4582  pThis->rctView.top = 0;
4583  }
4584 
4585  if(pThis->rctView.top > pThis->iRow) // still?
4586  {
4587  iDelta = pThis->rctView.top - pThis->iRow;
4588 
4589  pThis->rctView.bottom -= iDelta;
4590  pThis->rctView.top = pThis->iRow;
4591  }
4592  else if(pThis->rctView.bottom <= pThis->iRow) // still?
4593  {
4594  iDelta = (pThis->iRow + 1) - pThis->rctView.bottom;
4595 
4596  pThis->rctView.top += iDelta;
4597  pThis->rctView.bottom = pThis->iRow + 1;
4598  }
4599  }
4600 
4601  // in this case, since I updated the position by an entire page, the entire window is probably invalid
4602 
4603  __internal_invalidate_rect(pThis, NULL, 1); // invalidate entire screen if I'm here
4604  }
4605 
4606 }
4607 static void __internal_page_down(TEXT_OBJECT *pThis)
4608 {
4609 TEXT_BUFFER *pBuf;
4610 
4611  if(!WBIsValidTextObject(pThis))
4612  {
4613  WB_ERROR_PRINT("ERROR: %p - invalid text object %p\n", __FUNCTION__, pThis);
4614  }
4615  else
4616  {
4617  int iOldRow = pThis->iRow;
4618  int iPageHeight = pThis->rctView.bottom - pThis->rctView.top;
4619 
4620  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
4621  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
4622  __FUNCTION__, __LINE__,
4623  pThis->iRow, pThis->iCol,
4624  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
4625 
4626  pThis->iBlinkState = CURSOR_BLINK_RESET; // this affects the cursor blink, basically resetting it whenever I edit something
4627 
4628  if(pThis->iLineFeed == LineFeed_NONE) // single line
4629  {
4630  return; // do nothing
4631  }
4632 
4633  __internal_invalidate_cursor(pThis, 0); // invalidate current cursor rectangle
4634 
4635  pBuf = (TEXT_BUFFER *)(pThis->pText);
4636 
4637  if(!pBuf)
4638  {
4639  pThis->iRow = 0;
4640  pThis->iCol = 0;
4641  }
4642  else if((int)pThis->iRow >= ((int)pBuf->nEntries - iPageHeight))
4643  {
4644  if(pThis->iRow >= 0) // check for negatives anyway
4645  {
4646  pThis->iCol = 0; // page-up on top line homes the cursor
4647  }
4648 
4649  pThis->iRow = pBuf->nEntries;
4650  }
4651  else
4652  {
4653  pThis->iRow += iPageHeight;
4654  }
4655 
4656  if(pThis->iDragState & DragState_CURSOR)
4657  {
4658  if(SEL_RECT_EMPTY(pThis))
4659  {
4660  WB_DEBUG_PRINT(DebugLevel_Medium | DebugSubSystem_TextObject, "%s line %d - empty selection, starting select\n", __FUNCTION__, __LINE__);
4661 
4662  pThis->rctSel.left = pThis->iCol;
4663  pThis->rctSel.top = iOldRow;
4664  }
4665 
4666  pThis->rctSel.right = pThis->iCol;
4667  pThis->rctSel.bottom = pThis->iRow;
4668  }
4669  else
4670  {
4671  // clear the selection if there is one
4672  if(!SEL_RECT_EMPTY(pThis))
4673  {
4674  __internal_invalidate_rect(pThis, &(pThis->rctHighLight), 0); // invalidate the highlight rectangle
4675  memset(&(pThis->rctSel), 0, sizeof(pThis->rctSel));
4676  memset(&(pThis->rctHighLight), 0, sizeof(pThis->rctHighLight));
4677  }
4678  }
4679 
4680  if(WB_LIKELY(pThis->rctView.top > pThis->iRow ||
4681  pThis->rctView.bottom <= pThis->iRow))
4682  {
4683  int iDelta = pThis->iRow - iOldRow;
4684 
4685  if(!pBuf || (pThis->rctView.bottom + iDelta) <= pBuf->nEntries)
4686  {
4687  pThis->rctView.top += iDelta;
4688  pThis->rctView.bottom += iDelta;
4689  }
4690  else
4691  {
4692  pThis->rctView.top = pBuf->nEntries + 1
4693  - (pThis->rctView.bottom - pThis->rctView.top);
4694  pThis->rctView.bottom = pBuf->nEntries + 1;
4695  }
4696 
4697  if(pThis->rctView.top > pThis->iRow) // still?
4698  {
4699  iDelta = pThis->rctView.top - pThis->iRow;
4700 
4701  pThis->rctView.bottom -= iDelta;
4702  pThis->rctView.top = pThis->iRow;
4703  }
4704  else if(pThis->rctView.bottom <= pThis->iRow) // still?
4705  {
4706  iDelta = (pThis->iRow + 1) - pThis->rctView.bottom;
4707 
4708  pThis->rctView.top += iDelta;
4709  pThis->rctView.bottom = pThis->iRow + 1;
4710  }
4711  }
4712 
4713  // in this case, since I updated the position by an entire page, the entire window is probably invalid
4714 
4715  __internal_invalidate_rect(pThis, NULL, 1); // invalidate entire screen if I'm here
4716  }
4717 }
4718 static void __internal_page_left(TEXT_OBJECT *pThis)
4719 {
4720 //int iAutoScrollWidth = AUTO_HSCROLL_SIZE;
4721 TEXT_BUFFER *pBuf;
4722 
4723 
4724  if(!WBIsValidTextObject(pThis))
4725  {
4726  WB_ERROR_PRINT("ERROR: %p - invalid text object %p\n", __FUNCTION__, pThis);
4727  }
4728  else
4729  {
4730  int iOldCol = pThis->iCol;
4731  int iPageWidth = pThis->rctView.right - pThis->rctView.left;
4732 
4733  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
4734  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
4735  __FUNCTION__, __LINE__,
4736  pThis->iRow, pThis->iCol,
4737  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
4738 
4739  WB_DEBUG_PRINT(DebugLevel_Heavy | DebugSubSystem_ScrollBar,
4740  "%s %d - page left by %d, iCol=%d\n",
4741  __FUNCTION__, __LINE__, iPageWidth, pThis->iCol);
4742 
4743  pThis->iBlinkState = CURSOR_BLINK_RESET; // this affects the cursor blink, basically resetting it whenever I edit something
4744 
4745  __internal_invalidate_cursor(pThis, 0); // invalidate current cursor rectangle
4746 
4747  pBuf = (TEXT_BUFFER *)(pThis->pText);
4748 
4749  if(!pBuf)
4750  {
4751  pThis->iRow = 0;
4752  pThis->iCol = 0;
4753  }
4754  else if(pThis->iCol > iPageWidth)
4755  {
4756  pThis->iCol -= iPageWidth;
4757  }
4758  else
4759  {
4760  pThis->iCol = 0;
4761  }
4762 
4763  if(pThis->iDragState & DragState_CURSOR)
4764  {
4765  if(SEL_RECT_EMPTY(pThis))
4766  {
4767  WB_DEBUG_PRINT(DebugLevel_Medium | DebugSubSystem_TextObject, "%s line %d - empty selection, starting select\n", __FUNCTION__, __LINE__);
4768 
4769  pThis->rctSel.left = iOldCol;
4770  pThis->rctSel.top = pThis->iRow;
4771  }
4772 
4773  pThis->rctSel.right = pThis->iCol;
4774  pThis->rctSel.bottom = pThis->iRow;
4775  }
4776  else
4777  {
4778  // clear the selection if there is one
4779  if(!SEL_RECT_EMPTY(pThis))
4780  {
4781  __internal_invalidate_rect(pThis, &(pThis->rctHighLight), 0); // invalidate the highlight rectangle
4782  memset(&(pThis->rctSel), 0, sizeof(pThis->rctSel));
4783  memset(&(pThis->rctHighLight), 0, sizeof(pThis->rctHighLight));
4784  }
4785  }
4786 
4787  if(WB_LIKELY(pThis->rctView.left > pThis->iCol ||
4788  pThis->rctView.right <= pThis->iCol))
4789  {
4790  int iDelta = pThis->iCol - iOldCol;
4791 
4792  if(pThis->rctView.left > iDelta)
4793  {
4794  pThis->rctView.left -= iDelta;
4795  pThis->rctView.right -= iDelta;
4796  }
4797  else
4798  {
4799  pThis->rctView.right = iPageWidth;
4800  pThis->rctView.left = 0;
4801  }
4802 
4803  if(pThis->rctView.left > pThis->iCol) // still?
4804  {
4805  iDelta = pThis->rctView.left - pThis->iCol;
4806 
4807  pThis->rctView.right -= iDelta;
4808  pThis->rctView.left = pThis->iCol;
4809  }
4810  else if(pThis->rctView.right <= pThis->iCol) // still?
4811  {
4812  iDelta = (pThis->iCol + 1) - pThis->rctView.right;
4813 
4814  pThis->rctView.left += iDelta;
4815  pThis->rctView.right = pThis->iCol + 1;
4816  }
4817  }
4818 
4819  // in this case, since I updated the position by an entire page, the entire window is probably invalid
4820 
4821  __internal_invalidate_rect(pThis, NULL, 1); // invalidate entire screen if I'm here
4822  }
4823 }
4824 static void __internal_page_right(TEXT_OBJECT *pThis)
4825 {
4826 TEXT_BUFFER *pBuf;
4827 
4828 
4829  if(!WBIsValidTextObject(pThis))
4830  {
4831  WB_ERROR_PRINT("ERROR: %p - invalid text object %p\n", __FUNCTION__, pThis);
4832  }
4833  else
4834  {
4835  int iOldCol = pThis->iCol;
4836  int iPageWidth = pThis->rctView.right - pThis->rctView.left;
4837 
4838  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
4839  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
4840  __FUNCTION__, __LINE__,
4841  pThis->iRow, pThis->iCol,
4842  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
4843 
4844  WB_DEBUG_PRINT(DebugLevel_Heavy | DebugSubSystem_ScrollBar,
4845  "%s %d - page right by %d, iCol=%d\n",
4846  __FUNCTION__, __LINE__, iPageWidth, pThis->iCol);
4847 
4848  pThis->iBlinkState = CURSOR_BLINK_RESET; // this affects the cursor blink, basically resetting it whenever I edit something
4849 
4850  __internal_invalidate_cursor(pThis, 0); // invalidate current cursor rectangle
4851 
4852  pBuf = (TEXT_BUFFER *)(pThis->pText);
4853 
4854  if(!pBuf)
4855  {
4856  pThis->iRow = 0;
4857  pThis->iCol = 0;
4858  }
4859  else if(pThis->iCol < (INT_MAX - iPageWidth))
4860  {
4861  pThis->iCol += iPageWidth;
4862 
4863  if(pThis->iCol < 0) // in case of wrap/overflow?
4864  {
4865  pThis->iCol = 0;
4866  }
4867  }
4868  else
4869  {
4870  pThis->iCol = INT_MAX; // can't scroll any more
4871  }
4872 
4873  if(pThis->iDragState & DragState_CURSOR)
4874  {
4875  if(SEL_RECT_EMPTY(pThis))
4876  {
4877  WB_DEBUG_PRINT(DebugLevel_Medium | DebugSubSystem_TextObject, "%s line %d - empty selection, starting select\n", __FUNCTION__, __LINE__);
4878 
4879  pThis->rctSel.left = iOldCol;
4880  pThis->rctSel.top = pThis->iRow;
4881  }
4882 
4883  pThis->rctSel.right = pThis->iCol;
4884  pThis->rctSel.bottom = pThis->iRow;
4885  }
4886  else
4887  {
4888  // clear the selection if there is one
4889  if(!SEL_RECT_EMPTY(pThis))
4890  {
4891  __internal_invalidate_rect(pThis, &(pThis->rctHighLight), 0); // invalidate the highlight rectangle
4892  memset(&(pThis->rctSel), 0, sizeof(pThis->rctSel));
4893  memset(&(pThis->rctHighLight), 0, sizeof(pThis->rctHighLight));
4894  }
4895  }
4896 
4897  if(WB_LIKELY(pThis->rctView.left > pThis->iCol ||
4898  pThis->rctView.right <= pThis->iCol))
4899  {
4900  int iDelta = pThis->iCol - iOldCol;
4901 
4902  pThis->rctView.left += iDelta; // TODO: scroll forever? (maybe MAX_INT as a limit)
4903  pThis->rctView.right += iDelta;
4904 
4905  if(pThis->rctView.left > pThis->iCol) // still?
4906  {
4907  iDelta = pThis->rctView.left - pThis->iCol;
4908 
4909  pThis->rctView.right -= iDelta;
4910  pThis->rctView.left = pThis->iCol;
4911  }
4912  else if(pThis->rctView.right <= pThis->iCol) // still?
4913  {
4914  iDelta = (pThis->iCol + 1) - pThis->rctView.right;
4915 
4916  pThis->rctView.left += iDelta;
4917  pThis->rctView.right = pThis->iCol + 1;
4918  }
4919  }
4920 
4921  // in this case, since I updated the position by an entire page, the entire window is probably invalid
4922 
4923  __internal_invalidate_rect(pThis, NULL, 1); // invalidate entire screen if I'm here
4924  }
4925 }
4926 
4927 static void __internal_cursor_home(TEXT_OBJECT *pThis)
4928 {
4929 const char *pL, *p2;
4930 TEXT_BUFFER *pBuf;
4931 
4932 
4933  if(!WBIsValidTextObject(pThis))
4934  {
4935  WB_ERROR_PRINT("ERROR: %p - invalid text object %p\n", __FUNCTION__, pThis);
4936  }
4937  else
4938  {
4939  int iOldCol = pThis->iCol;
4940 
4941  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
4942  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
4943  __FUNCTION__, __LINE__,
4944  pThis->iRow, pThis->iCol,
4945  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
4946 
4947  pThis->iBlinkState = CURSOR_BLINK_RESET; // this affects the cursor blink, basically resetting it whenever I edit something
4948 
4949  __internal_invalidate_cursor(pThis, 0); // invalidate current cursor rectangle
4950 
4951  pBuf = (TEXT_BUFFER *)(pThis->pText);
4952 
4953  if(!pBuf || pThis->iRow >= pBuf->nEntries || pThis->iCol > 0)
4954  {
4955  pThis->iCol = 0;
4956  }
4957  else
4958  {
4959  // find first non-white-space character and assign to THAT
4960  // TODO: handle hard tab translation? For now "leave it"
4961 
4962  pL = pBuf->aLines[pThis->iRow];
4963 
4964  if(pL)
4965  {
4966  p2 = pL;
4967  while(*p2 && *p2 <= ' ') // TODO: adjust for 'hard tab' char which might be 'U+00A0'...
4968  {
4969  p2++;
4970  }
4971 
4972  if(*p2)
4973  {
4974  pThis->iCol = (int)(p2 - pL); // toggling 'beginning of line' and column 0
4975  }
4976  }
4977  }
4978 
4979  if(pThis->iDragState & DragState_CURSOR)
4980  {
4981  if(SEL_RECT_EMPTY(pThis))
4982  {
4983  WB_DEBUG_PRINT(DebugLevel_Medium | DebugSubSystem_TextObject, "%s line %d - empty selection, starting select\n", __FUNCTION__, __LINE__);
4984 
4985  pThis->rctSel.left = iOldCol;
4986  pThis->rctSel.top = pThis->iRow;
4987  }
4988 
4989  pThis->rctSel.right = pThis->iCol;
4990  pThis->rctSel.bottom = pThis->iRow;
4991  }
4992  else
4993  {
4994  // clear the selection if there is one
4995  if(!SEL_RECT_EMPTY(pThis))
4996  {
4997  __internal_invalidate_rect(pThis, &(pThis->rctHighLight), 0); // invalidate the highlight rectangle
4998  memset(&(pThis->rctSel), 0, sizeof(pThis->rctSel));
4999  memset(&(pThis->rctHighLight), 0, sizeof(pThis->rctHighLight));
5000  }
5001  }
5002 
5003  if(pThis->rctView.right > pThis->rctView.left)
5004  {
5005  if(pThis->rctView.right <= pThis->iCol ||
5006  pThis->rctView.left > pThis->iCol)
5007  {
5008  if(pThis->rctView.left > pThis->iCol)
5009  {
5010  pThis->rctView.right -= (pThis->rctView.left - pThis->iCol);
5011  pThis->rctView.left = pThis->iCol;
5012  }
5013  else if(pThis->rctView.right <= pThis->iCol)
5014  {
5015  pThis->rctView.left += pThis->iCol + 1 - pThis->rctView.right;
5016  pThis->rctView.right = pThis->iCol + 1;
5017  }
5018 
5019  WB_DEBUG_PRINT(DebugLevel_Verbose, "%s line %d - need to optimize invalidate rect\n", __FUNCTION__, __LINE__);
5020 
5021  __internal_invalidate_rect(pThis, NULL, 1); // invalidate entire screen if I'm here
5022  }
5023  else // re-calculate cursor metrics
5024  {
5025  pThis->iCursorX += (pThis->iCol - iOldCol) * pThis->iFontWidth;
5026 
5027  __internal_invalidate_cursor(pThis, 1); // invalidate the NEW cursor rectangle
5028  }
5029  }
5030  else
5031  {
5032  WB_WARN_PRINT("WARNING: %s line %d - invalidate entire window rect\n", __FUNCTION__, __LINE__);
5033 
5034  __internal_invalidate_rect(pThis, NULL, 1); // invalidate entire screen if I'm here (confusion point)
5035  }
5036  }
5037 }
5038 
5039 static void __internal_cursor_end(TEXT_OBJECT *pThis)
5040 {
5041 const char *pL;
5042 TEXT_BUFFER *pBuf;
5043 
5044 
5045  if(!WBIsValidTextObject(pThis))
5046  {
5047  WB_ERROR_PRINT("ERROR: %p - invalid text object %p\n", __FUNCTION__, pThis);
5048  }
5049  else
5050  {
5051  int iOldCol = pThis->iCol;
5052 
5053  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
5054  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
5055  __FUNCTION__, __LINE__,
5056  pThis->iRow, pThis->iCol,
5057  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
5058 
5059  pThis->iBlinkState = CURSOR_BLINK_RESET; // this affects the cursor blink, basically resetting it whenever I edit something
5060 
5061  __internal_invalidate_cursor(pThis, 0); // invalidate current cursor rectangle
5062 
5063  pBuf = (TEXT_BUFFER *)(pThis->pText);
5064 
5065  if(!pBuf || pThis->iRow >= pBuf->nEntries)
5066  {
5067  pThis->iCol = 0;
5068  }
5069  else
5070  {
5071  pL = pBuf->aLines[pThis->iRow];
5072 
5073  if(!pL)
5074  {
5075  pThis->iCol = 0;
5076  }
5077  else
5078  {
5079  pThis->iCol = WBGetMBLength(pL);
5080  }
5081  }
5082 
5083  if(pThis->iDragState & DragState_CURSOR)
5084  {
5085  if(SEL_RECT_EMPTY(pThis))
5086  {
5087  WB_DEBUG_PRINT(DebugLevel_Medium | DebugSubSystem_TextObject, "%s line %d - empty selection, starting select\n", __FUNCTION__, __LINE__);
5088 
5089  pThis->rctSel.left = iOldCol;
5090  pThis->rctSel.top = pThis->iRow;
5091  }
5092 
5093  pThis->rctSel.right = pThis->iCol;
5094  pThis->rctSel.bottom = pThis->iRow;
5095  }
5096  else
5097  {
5098  // clear the selection if there is one
5099  if(!SEL_RECT_EMPTY(pThis))
5100  {
5101  __internal_invalidate_rect(pThis, &(pThis->rctHighLight), 0); // invalidate the highlight rectangle
5102  memset(&(pThis->rctSel), 0, sizeof(pThis->rctSel));
5103  memset(&(pThis->rctHighLight), 0, sizeof(pThis->rctHighLight));
5104  }
5105  }
5106 
5107  if(pThis->rctView.right > pThis->rctView.left)
5108  {
5109  if(pThis->rctView.right <= pThis->iCol ||
5110  pThis->rctView.left > pThis->iCol)
5111  {
5112  if(pThis->rctView.left > pThis->iCol)
5113  {
5114  pThis->rctView.right -= (pThis->rctView.left - pThis->iCol);
5115  pThis->rctView.left = pThis->iCol;
5116  }
5117  else if(pThis->rctView.right <= pThis->iCol)
5118  {
5119  pThis->rctView.left += pThis->iCol + 1 - pThis->rctView.right;
5120  pThis->rctView.right = pThis->iCol + 1;
5121  }
5122 
5123  WB_DEBUG_PRINT(DebugLevel_Verbose, "%s line %d - need to optimize invalidate rect\n", __FUNCTION__, __LINE__);
5124 
5125  __internal_invalidate_rect(pThis, NULL, 1); // invalidate entire screen if I'm here
5126  }
5127  else // re-calculate cursor metrics
5128  {
5129  pThis->iCursorX += (pThis->iCol - iOldCol) * pThis->iFontWidth;
5130 
5131  __internal_invalidate_cursor(pThis, 1); // invalidate the NEW cursor rectangle
5132  }
5133  }
5134  else
5135  {
5136  WB_WARN_PRINT("WARNING: %s line %d - invalidate entire window rect\n", __FUNCTION__, __LINE__);
5137 
5138  __internal_invalidate_rect(pThis, NULL, 1); // invalidate entire screen if I'm here (confusion point)
5139  }
5140  }
5141 }
5142 
5143 static void __internal_cursor_top(TEXT_OBJECT *pThis)
5144 {
5145 //TEXT_BUFFER *pBuf;
5146 
5147 
5148  if(!WBIsValidTextObject(pThis))
5149  {
5150  WB_ERROR_PRINT("ERROR: %p - invalid text object %p\n", __FUNCTION__, pThis);
5151  }
5152  else
5153  {
5154  int iOldRow = pThis->iRow;
5155  int iPageHeight = pThis->rctView.bottom - pThis->rctView.top;
5156 
5157  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
5158  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
5159  __FUNCTION__, __LINE__,
5160  pThis->iRow, pThis->iCol,
5161  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
5162 
5163  pThis->iBlinkState = CURSOR_BLINK_RESET; // this affects the cursor blink, basically resetting it whenever I edit something
5164 
5165  pThis->iRow = 0;
5166  pThis->iCol = 0;
5167 
5168 // pBuf = (TEXT_BUFFER *)(pThis->pText);
5169 
5170  if(pThis->iDragState & DragState_CURSOR)
5171  {
5172  if(SEL_RECT_EMPTY(pThis))
5173  {
5174  WB_DEBUG_PRINT(DebugLevel_Medium | DebugSubSystem_TextObject, "%s line %d - empty selection, starting select\n", __FUNCTION__, __LINE__);
5175 
5176  pThis->rctSel.left = pThis->iCol;
5177  pThis->rctSel.top = iOldRow;
5178  }
5179 
5180  pThis->rctSel.right = pThis->iCol;
5181  pThis->rctSel.bottom = pThis->iRow;
5182  }
5183  else
5184  {
5185  // clear the selection if there is one
5186  if(!SEL_RECT_EMPTY(pThis))
5187  {
5188  __internal_invalidate_rect(pThis, &(pThis->rctHighLight), 0); // invalidate the highlight rectangle
5189  memset(&(pThis->rctSel), 0, sizeof(pThis->rctSel));
5190  memset(&(pThis->rctHighLight), 0, sizeof(pThis->rctHighLight));
5191  }
5192  }
5193 
5194  // force scroll to top
5195  pThis->rctView.top = 0;
5196  pThis->rctView.bottom = iPageHeight;
5197 
5198  WB_DEBUG_PRINT(DebugLevel_Verbose, "%s line %d - need to optimize invalidate rect\n", __FUNCTION__, __LINE__);
5199 
5200  __internal_invalidate_rect(pThis, NULL, 1); // invalidate entire screen if I'm here
5201  }
5202 }
5203 
5204 static void __internal_cursor_bottom(TEXT_OBJECT *pThis)
5205 {
5206 TEXT_BUFFER *pBuf;
5207 
5208 
5209  if(!WBIsValidTextObject(pThis))
5210  {
5211  WB_ERROR_PRINT("ERROR: %p - invalid text object %p\n", __FUNCTION__, pThis);
5212  }
5213  else
5214  {
5215  int iOldRow = pThis->iRow;
5216  int iPageHeight = pThis->rctView.bottom - pThis->rctView.top;
5217 
5218  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
5219  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
5220  __FUNCTION__, __LINE__,
5221  pThis->iRow, pThis->iCol,
5222  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
5223 
5224  pThis->iBlinkState = CURSOR_BLINK_RESET; // this affects the cursor blink, basically resetting it whenever I edit something
5225 
5226  pBuf = (TEXT_BUFFER *)(pThis->pText);
5227 
5228  if(pBuf)
5229  {
5230  pThis->iRow = pBuf->nEntries; // last line, which has no text
5231  pThis->iCol = 0;
5232  }
5233  else
5234  {
5235  pThis->iRow = 0;
5236  pThis->iCol = 0;
5237  }
5238 
5239  if(pThis->iDragState & DragState_CURSOR)
5240  {
5241  if(SEL_RECT_EMPTY(pThis))
5242  {
5243  WB_DEBUG_PRINT(DebugLevel_Medium | DebugSubSystem_TextObject, "%s line %d - empty selection, starting select\n", __FUNCTION__, __LINE__);
5244 
5245  pThis->rctSel.left = pThis->iCol;
5246  pThis->rctSel.top = iOldRow;
5247  }
5248 
5249  pThis->rctSel.right = pThis->iCol;
5250  pThis->rctSel.bottom = pThis->iRow;
5251  }
5252  else
5253  {
5254  // clear the selection if there is one
5255  if(!SEL_RECT_EMPTY(pThis))
5256  {
5257  __internal_invalidate_rect(pThis, &(pThis->rctHighLight), 0); // invalidate the highlight rectangle
5258  memset(&(pThis->rctSel), 0, sizeof(pThis->rctSel));
5259  memset(&(pThis->rctHighLight), 0, sizeof(pThis->rctHighLight));
5260  }
5261  }
5262 
5263  // force scroll to bottom (with an extra line)
5264  if(!pBuf || iPageHeight > pBuf->nEntries)
5265  {
5266  pThis->rctView.top = 0;
5267  pThis->rctView.bottom = iPageHeight;
5268  }
5269  else
5270  {
5271  pThis->rctView.top = pBuf->nEntries + 1 - iPageHeight;
5272  pThis->rctView.bottom = pBuf->nEntries + 1;
5273  }
5274 
5275  WB_DEBUG_PRINT(DebugLevel_Verbose, "%s line %d - need to optimize invalidate rect\n", __FUNCTION__, __LINE__);
5276 
5277  __internal_invalidate_rect(pThis, NULL, 1); // invalidate entire screen if I'm here
5278  }
5279 }
5280 
5281 static void __internal_scroll_vertical(TEXT_OBJECT *pThis, int nRows)
5282 {
5283 TEXT_BUFFER *pBuf;
5284 
5285  if(!WBIsValidTextObject(pThis))
5286  {
5287  WB_ERROR_PRINT("ERROR: %p - invalid text object %p\n", __FUNCTION__, pThis);
5288  }
5289  else
5290  {
5291 // int iOldRow WB_UNUSED = pThis->iRow; // TODO: do I still need this assignment? For now, mark 'unused' and leave for reference
5292 
5293  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
5294  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
5295  __FUNCTION__, __LINE__,
5296  pThis->iRow, pThis->iCol,
5297  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
5298 
5299  WB_DEBUG_PRINT(DebugLevel_Heavy | DebugSubSystem_ScrollBar,
5300  "%s %d - scroll vertical by %d, iCol=%d\n",
5301  __FUNCTION__, __LINE__, nRows, pThis->iCol);
5302 
5303  pThis->iBlinkState = CURSOR_BLINK_RESET; // this affects the cursor blink, basically resetting it whenever I edit something
5304 
5305  if(!nRows)
5306  {
5307  return; // do nothing
5308  }
5309 
5310  pBuf = (TEXT_BUFFER *)(pThis->pText);
5311 
5312  if(!pBuf)
5313  {
5314  pThis->iRow = 0;
5315  pThis->iCol = 0;
5316 
5317  pThis->rctView.bottom -= pThis->rctView.top; // assuming it's right
5318  pThis->rctView.top = 0;
5319  pThis->rctView.right -= pThis->rctView.left; // assuming it's right
5320  pThis->rctView.left = 0;
5321  }
5322  else if(nRows < 0)
5323  {
5324  if(pThis->rctView.top + nRows < 0)
5325  {
5326  pThis->rctView.bottom -= pThis->rctView.top;
5327  pThis->rctView.top = 0;
5328  }
5329  else
5330  {
5331  pThis->rctView.bottom += nRows;
5332  pThis->rctView.top += nRows;
5333  }
5334  }
5335  else // if(nRows > 0)
5336  {
5337  if(pThis->rctView.bottom + nRows > pBuf->nEntries)
5338  {
5339  pThis->rctView.top += pBuf->nEntries - pThis->rctView.bottom;
5340  pThis->rctView.bottom = pBuf->nEntries;
5341  }
5342  else
5343  {
5344  pThis->rctView.bottom += nRows;
5345  pThis->rctView.top += nRows;
5346  }
5347  }
5348 
5349  WB_DEBUG_PRINT(DebugLevel_Verbose, "%s line %d - need to optimize invalidate rect\n", __FUNCTION__, __LINE__);
5350 
5351  __internal_invalidate_rect(pThis, NULL, 1); // invalidate entire screen if I'm here
5352  }
5353 }
5354 
5355 static void __internal_scroll_horizontal(TEXT_OBJECT *pThis, int nCols)
5356 {
5357 TEXT_BUFFER *pBuf;
5358 
5359  if(!WBIsValidTextObject(pThis))
5360  {
5361  WB_ERROR_PRINT("ERROR: %p - invalid text object %p\n", __FUNCTION__, pThis);
5362  }
5363  else
5364  {
5365 // int iOldCol WB_UNUSED = pThis->iCol; // TODO: do I still need this assignment? For now, mark 'unused' and leave for reference
5366 
5367  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
5368  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
5369  __FUNCTION__, __LINE__,
5370  pThis->iRow, pThis->iCol,
5371  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
5372 
5373  WB_DEBUG_PRINT(DebugLevel_Heavy | DebugSubSystem_ScrollBar,
5374  "%s %d - scroll horizontal by %d, iCol=%d\n",
5375  __FUNCTION__, __LINE__, nCols, pThis->iCol);
5376 
5377  pThis->iBlinkState = CURSOR_BLINK_RESET; // this affects the cursor blink, basically resetting it whenever I edit something
5378 
5379  if(!nCols)
5380  {
5381  return; // do nothing
5382  }
5383 
5384  pBuf = (TEXT_BUFFER *)(pThis->pText);
5385 
5386  if(!pBuf)
5387  {
5388  pThis->iRow = 0;
5389  pThis->iCol = 0;
5390 
5391  pThis->rctView.bottom -= pThis->rctView.top; // assuming it's right
5392  pThis->rctView.top = 0;
5393  pThis->rctView.right -= pThis->rctView.left; // assuming it's right
5394  pThis->rctView.left = 0;
5395  }
5396  else if(pThis->iCol < 0)
5397  {
5398  pThis->iCol = 0; // always
5399 
5400  pThis->rctView.right -= pThis->rctView.left; // assuming it's right
5401  pThis->rctView.left = 0;
5402  }
5403  else if(pThis->iCol + nCols > -INT_MAX && pThis->iCol + nCols < INT_MAX) // for now, ignore scroll on overflow
5404  {
5405  if(pThis->iLineFeed == LineFeed_NONE) // single line
5406  {
5407  if(pBuf->nEntries <= 0 || !pBuf->aLines[0])
5408  {
5409  pThis->iCol = 0;
5410 
5411  pThis->rctView.right -= pThis->rctView.left; // assuming it's right
5412  pThis->rctView.left = 0;
5413  }
5414  else
5415  {
5416  int iLen = WBGetMBLength(pBuf->aLines[0]);
5417 
5418  if(pThis->iRow != 0)
5419  {
5420  pThis->iRow = 0; // force it
5421  }
5422 
5423  // no 'virtual space' for single-line but trailing white space OK
5424  if(nCols > 0)
5425  {
5426  if(pThis->rctView.right + nCols > iLen + 1)
5427  {
5428  pThis->rctView.left += (iLen + 1 - nCols - pThis->rctView.right);
5429  pThis->rctView.right = iLen + 1;
5430  }
5431  else
5432  {
5433  pThis->rctView.left += nCols;
5434  pThis->rctView.right += nCols;
5435  }
5436  }
5437  else
5438  {
5439  if(pThis->rctView.left + nCols < 0)
5440  {
5441  pThis->rctView.right -= pThis->rctView.left;
5442  pThis->rctView.left = 0;
5443  }
5444  else
5445  {
5446  pThis->rctView.left += nCols;
5447  pThis->rctView.right += nCols;
5448  }
5449  }
5450  }
5451  }
5452  else
5453  {
5454  if(nCols > 0)
5455  {
5456  pThis->rctView.left += nCols; // scroll right always
5457  pThis->rctView.right += nCols;
5458  }
5459  else
5460  {
5461  if(pThis->rctView.left + nCols < 0)
5462  {
5463  pThis->rctView.right -= pThis->rctView.left;
5464  pThis->rctView.left = 0;
5465  }
5466  else
5467  {
5468  pThis->rctView.left += nCols;
5469  pThis->rctView.right += nCols;
5470  }
5471  }
5472  }
5473  }
5474 
5475  WB_DEBUG_PRINT(DebugLevel_Verbose, "%s line %d - need to optimize invalidate rect\n", __FUNCTION__, __LINE__);
5476 
5477  __internal_invalidate_rect(pThis, NULL, 1); // invalidate entire screen if I'm here
5478  }
5479 }
5480 
5481 
5482 #if 0
5483 static XFontSet __attribute__((noinline)) __internal_verify_get_fontset(Display *pDisplay, XFontSet rFontSet, WBGC gc)
5484 {
5485 XFontSet fSet;
5486 
5487  if(rFontSet == None) /* NULL? */
5488  {
5489  WB_FONT pFont = WBQueryGCFont(pDisplay, gc);
5490 
5491  if(!pFont)
5492  {
5493  WB_ERROR_PRINT("%s - ERROR: WBQueryGCFont returns NULL\n", __FUNCTION__);
5494 
5495  return None; // bad
5496  }
5497 
5498  fSet = WBFontSetFromFont(pDisplay, pFont); // convert font to font set
5499 
5500  XFreeFont(pDisplay, pFont); // free it now (important to do so)
5501  pFont = NULL;
5502 
5503  if(fSet == None)
5504  {
5505  WB_ERROR_PRINT("%s - ERROR: WBFontSetFromFont returns None\n", __FUNCTION__);
5506  }
5507  }
5508  else
5509  {
5510  fSet = rFontSet;
5511  }
5512 
5513  return fSet;
5514 }
5515 #endif // 0
5516 
5517 #if 0
5518 int __attribute__((noinline)) __internal_get_fontset_count_and_max_asc_desc(XFontSet fSet, int *pAsc, int *pDesc)
5519 {
5520 int nFonts, i1, iAsc, iDesc;
5521 XFontStruct **ppFonts = NULL;
5522 char **ppNames = NULL;
5523 
5524  nFonts = XFontsOfFontSet(fSet, &ppFonts, &ppNames); // returns # of items (do NOT free resources! They are owned by the font set)
5525 
5526  if(nFonts <= 0 || !ppFonts)
5527  {
5528  return -1;
5529  }
5530 
5531  for(i1=0, iAsc=0, iDesc=0; i1 < nFonts; i1++)
5532  {
5533  if(iAsc < ppFonts[i1]->ascent)
5534  {
5535  iAsc = ppFonts[i1]->ascent;
5536  }
5537  if(iDesc < ppFonts[i1]->descent)
5538  {
5539  iDesc = ppFonts[i1]->descent;
5540  }
5541  }
5542 
5543  if(pAsc)
5544  {
5545  *pAsc = iAsc;
5546  }
5547 
5548  if(pDesc)
5549  {
5550  *pDesc = iDesc;
5551  }
5552 
5553  return nFonts;
5554 }
5555 #endif // 0
5556 
5557 static void __internal_do_expose(TEXT_OBJECT *pThis, Display *pDisplay, Window wID,
5558  WBGC gc, const WB_GEOM *pPaintGeom, const WB_GEOM *pViewGeom,
5559  WB_FONTC pFont)
5560 {
5561 TEXT_BUFFER *pBuf = NULL;
5562 WB_GEOM geomV, geomP, geomC;
5563 char *pL = NULL;
5564 int iXDelta, iYDelta;
5565 int i1, iLen, iFontHeight, iFontWidth, iAsc, iDesc, iX, iY, iPX, iPY;
5566 //int nFonts;
5567 //XFontSet fSet;
5568 Pixmap pxTemp;
5569 unsigned long clrFG, clrBG, clrHFG, clrHBG;
5570 WBGC gc2 = None;
5571 WB_RECT rctSel; // the NORMALIZED selection rectangle (calculated)
5572 int nEntries, iAutoScrollWidth, iWindowHeightInLines;
5573 
5574 
5575  if(!WBIsValidTextObject(pThis))
5576  {
5577  WB_ERROR_PRINT("ERROR: %s - text object %p not valid\n", __FUNCTION__, pThis);
5578  return;
5579  }
5580 
5581  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_TextObject,
5582  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
5583  __FUNCTION__, __LINE__,
5584  pThis->iRow, pThis->iCol,
5585  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
5586 
5587  iXDelta = iYDelta = 0; // make sure, as they're used in a few places
5588 
5589  if(pViewGeom)
5590  {
5591  memcpy(&geomV, pViewGeom, sizeof(geomV));
5592  }
5593  else
5594  {
5595  WBGetWindowGeom(wID, &geomV);
5596 
5597  geomV.x = geomV.y = 0; // ALWAYS (make sure) - it's client coords now
5598 
5599  if(geomV.height <= MIN_BORDER_SPACING * 4) // absolute minimum height
5600  {
5601  return; // can't do window that's way too small
5602  }
5603 
5604  geomV.y += MIN_BORDER_SPACING; // need a minimum top/bottom border as well
5605  geomV.height -= MIN_BORDER_SPACING * 2;
5606  }
5607 
5608 // WB_ERROR_PRINT("TEMPORARY: %s.%d window geometry: %d, %d, %d, %d\n",
5609 // __FUNCTION__, __LINE__, geomV.x, geomV.y, geomV.width, geomV.height);
5610 
5611  if(geomV.width <= MIN_BORDER_SPACING * 4) // absolute minimum width
5612  {
5613  return; // can't do window that's way too small
5614  }
5615 
5616  // in all cases, make room on right/left edges for the cursor
5617 
5618  geomV.x += MIN_BORDER_SPACING; // required for cursor
5619  geomV.width -= MIN_BORDER_SPACING * 2;
5620 
5621  // TODO: make use of geomV's "border" parameter??
5622 
5623  if(pPaintGeom)
5624  {
5625  memcpy(&geomP, pPaintGeom, sizeof(geomP));
5626  }
5627  else
5628  {
5629  memcpy(&geomP, &geomV, sizeof(geomP));
5630  }
5631 
5632 // fSet = __internal_verify_get_fontset(pDisplay, rFontSet, gc);
5633 // // NOTE: at this point I must use 'fSet' to do all of the font metrics + drawing
5634 //
5635 //
5636 // // NOTE: to find the floor of the font, I'll need to determine max iAsc and iDesc
5637 // // this next function call tells me how many fonts I have, and determines the
5638 // // max ascent and descent and stores that in iAsc and iDesc
5639 // nFonts = __internal_get_fontset_count_and_max_asc_desc(fSet, &iAsc, &iDesc);
5640 
5641  iAsc = 0;
5642  iDesc = 0;
5643 
5644  if(!pFont)
5645  {
5646  // TODO: optimize this with cached information so it goes faster, and have
5647  // some 'font query' APIs for the GC as well
5648 
5649  pFont = WBQueryGCFont(gc); // TODO: optimize this
5650 
5651  if(!pFont)
5652  {
5653  pFont = WBGetDefaultFont();
5654  }
5655  }
5656 
5657  if(pFont)
5658  {
5659  iAsc = WBFontAscent(pFont);
5660  iDesc = WBFontDescent(pFont);
5661 
5662 // WB_ERROR_PRINT("TEMPORARY: %s.%d font ascent=%d, descent=%d\n", __FUNCTION__, __LINE__, iAsc, iDesc);
5663  }
5664  else
5665  {
5666  WB_ERROR_PRINT("%s - ERROR: no fonts!\n", __FUNCTION__);
5667 
5668  goto the_end;
5669  }
5670 
5671  // TODO: XFontStruct::direction indicates LTR RTL TTB or BTT for painting - for now _ONLY_ LTR_TTB will apply
5672 
5673 
5674  // NOW get the font width for a space (TODO: average char width instead?)
5675  iFontWidth = WBFontAvgCharWidth(pFont); // WBTextWidth(pFont, " ", 1); // WB_TEXT_ESCAPEMENT(fSet, " ", 1);
5676 
5677  // get FG/BG color information
5678  clrFG = WBGetGCFGColor(gc);
5679  clrBG = WBGetGCBGColor(gc);
5680 
5681  // TODO: compare clrHFG and clrHBG to a "NULL" XColor?
5682  if(!(pThis->clrHFG.flags & (DoRed | DoGreen | DoBlue)) &&
5683  !(pThis->clrHFG.flags & (DoRed | DoGreen | DoBlue))) // the colors have not been assigned
5684  {
5685  clrHBG = clrFG;
5686  clrHFG = clrBG;
5687  }
5688  else
5689  {
5690  clrHBG = pThis->clrHBG.pixel;
5691  clrHFG = pThis->clrHFG.pixel;
5692  }
5693 
5694  // make a copy of the WBGC since I'm probably going to mess with it
5695 
5696  gc2 = WBCreateGC(pDisplay, wID, 0, NULL);
5697 
5698  if(gc2 != None)
5699  {
5700  if(!WBCopyGC2(gc, GCAll, gc2))
5701  {
5702  WBFreeGC(gc2);
5703  gc2 = None;
5704  }
5705  }
5706 
5707  // font metrics complete. Now THAT wasn't so bad, was it? OK it was but only need to do it here
5708 
5709  // cache the owner window ID
5710  pThis->wIDOwner = wID;
5711 
5712  // cache the font metrics
5713  pThis->iAsc = iAsc;
5714  pThis->iDesc = iDesc;
5715 
5716  pThis->iMaxFontWidth = WBFontMaxCharWidth(pFont);
5717  pThis->iFontWidth = iFontWidth;
5718 
5719 #warning support proportional pitch fonts by assigning a font width < 0 ??? For now only fixed-pitch are supported by this object
5720 
5721  // keep track of the total viewport in window coordinates.
5722  // this will be used later for mouse translation
5723 
5724  pThis->rctWinView.left = geomV.x;
5725  pThis->rctWinView.top = geomV.y;
5726  pThis->rctWinView.right = pThis->rctWinView.left + geomV.width;
5727  pThis->rctWinView.bottom = pThis->rctWinView.top + geomV.height;
5728 
5729  // cache the pointer to the TEXT BUFFER
5730  pBuf = (TEXT_BUFFER *)(pThis->pText);
5731 
5732  // NOTE: in the next sections, deal with NULL pBuf by cacheing 'nEntries'
5733 
5734  if(!pBuf || pBuf->nEntries <= 0) // also test for this condition and treat it as single-line also (temporary)
5735  {
5736  nEntries = 0;
5737  }
5738  else
5739  {
5740  nEntries = pBuf->nEntries;
5741  }
5742 
5743 
5744  // ------------------------------------------
5745  // CALCULATING THE CORRECT VIEWPORT (rctView)
5746  // ------------------------------------------
5747  //
5748  // NOTE: if the owning window has reset the viewport, the entire window SHOULD
5749  // be invalid. If not, it won't paint properly.
5750 
5751  if(pThis->iLineFeed == LineFeed_NONE) // SINGLE LINE
5752  {
5753 // WB_ERROR_PRINT("TEMPORARY: %s.%d single line text object expose\n", __FUNCTION__, __LINE__);
5754 
5755  // AUTO-ASSIGN the viewport whenever right <= left (i.e. viewport is 'NULL' or 'empty')
5756  // or whenever the viewport is not properly assigned (window re-size re-paint)
5757 
5758  iFontHeight = iAsc + iDesc; // does not include interline spacing (that comes later for multi-line only)
5759 
5760 // WB_ERROR_PRINT("TEMPORARY: %s.%d font height=%d\n", __FUNCTION__, __LINE__, iFontHeight);
5761 
5762  if(pThis->rctView.top || pThis->rctView.bottom ||
5763  pThis->rctView.right != pThis->rctView.left + geomV.width / iFontWidth) // not PROPERLY assigned
5764  {
5765  iAutoScrollWidth = AUTO_HSCROLL_SIZE;
5766 
5767  if(pThis->rctView.left < 0) // not already assigned
5768  {
5769  pThis->rctView.left = 0;
5770  }
5771 
5772  pThis->rctView.top = pThis->rctView.bottom = 0; // single line forces this (always)
5773 
5774  pThis->rctView.right = pThis->rctView.left + geomV.width / iFontWidth;
5775 
5776  if(pThis->rctView.right > pThis->rctView.left)
5777  {
5778  while(iAutoScrollWidth > 1 && iAutoScrollWidth >= pThis->rctView.right - pThis->rctView.left)
5779  {
5780  iAutoScrollWidth >>= 1;
5781  }
5782 
5783  // scroll right to expose the cursor
5784 
5785  while(pThis->rctView.right <= pThis->iCol)
5786  {
5787  pThis->rctView.left += iAutoScrollWidth;
5788  pThis->rctView.right += iAutoScrollWidth;
5789  }
5790  }
5791  else
5792  {
5793  pThis->rctView.right = pThis->rctView.left = pThis->iCol; // desperate to have something there
5794  }
5795  }
5796  }
5797  else // MULTI-LINE
5798  {
5799 // WB_ERROR_PRINT("TEMPORARY: %s.%d multi-line text object expose\n", __FUNCTION__, __LINE__);
5800 
5801  // adjust font height to include line spacing (I'll use this to position the lines)
5802 
5803  iFontHeight = WBTextObjectCalculateLineHeight(pThis->iAsc, pThis->iDesc);
5804 
5805  iWindowHeightInLines = geomV.height / iFontHeight; // window height (in lines)
5806  if(!iWindowHeightInLines)
5807  {
5808  iWindowHeightInLines = 1; // just in case so I don't divide by zero
5809  }
5810 
5811  // AUTO-ASSIGN the viewport whenever right <= left (i.e. viewport is 'NULL' or 'empty')
5812  // or whenever the viewport is not properly assigned (window re-size re-paint)
5813 
5814  if(pThis->rctView.right - pThis->rctView.left
5815  != geomV.width / iFontWidth // viewport needs re-calculation
5816  || pThis->rctView.bottom - pThis->rctView.top
5817  != iWindowHeightInLines // viewport needs re-calculation
5818  || pThis->rctView.top > nEntries) // top exceeds total # of lines
5819  {
5820  iAutoScrollWidth = AUTO_HSCROLL_SIZE;
5821 
5822  if(pThis->rctView.left < 0) // not already assigned
5823  {
5824  pThis->rctView.left = 0;
5825  }
5826 
5827  if(pThis->rctView.top < 0 || pThis->rctView.top > nEntries
5828  || (pThis->rctView.top && nEntries < iWindowHeightInLines))
5829  {
5830  pThis->rctView.top = 0; // set viewport to top
5831  }
5832 
5833  if(pThis->rctView.bottom - pThis->rctView.top
5834  != iWindowHeightInLines)
5835  {
5836  pThis->rctView.bottom = pThis->rctView.top + iWindowHeightInLines;
5837 
5838  // always scroll row into view
5839 
5840  while(pThis->rctView.top > pThis->iRow)
5841  {
5842  pThis->rctView.top -= iWindowHeightInLines - 1;
5843  pThis->rctView.bottom -= iWindowHeightInLines - 1;
5844  }
5845  while(pThis->rctView.bottom <= pThis->iRow)
5846  {
5847  pThis->rctView.top += iWindowHeightInLines - 1;
5848  pThis->rctView.bottom += iWindowHeightInLines - 1;
5849  }
5850  }
5851 
5852  if(pThis->rctView.right - pThis->rctView.left != geomV.width / iFontWidth)
5853  {
5854  pThis->rctView.right = pThis->rctView.left + geomV.width / iFontWidth;
5855 
5856  if(pThis->rctView.right > pThis->rctView.left) // just in case, test it
5857  {
5858  while(iAutoScrollWidth > 1 && iAutoScrollWidth >= pThis->rctView.right - pThis->rctView.left)
5859  {
5860  iAutoScrollWidth >>= 1;
5861  }
5862 
5863  // scroll right to expose the cursor
5864 
5865  while(pThis->rctView.right <= pThis->iCol)
5866  {
5867  pThis->rctView.left += iAutoScrollWidth;
5868  pThis->rctView.right += iAutoScrollWidth;
5869  }
5870  }
5871  else
5872  {
5873  pThis->rctView.right = pThis->rctView.left = pThis->iCol; // desperate to have something there
5874  }
5875  }
5876  }
5877  }
5878 
5879 
5880  //--------------------------------------------------------------------------
5881  // convert the highlight rectangle into screen coordinates (fills in rctSel)
5882  //--------------------------------------------------------------------------
5883 
5884  if(!SEL_RECT_EMPTY(pThis))
5885  {
5886  if(SEL_RECT_ALL(pThis))
5887  {
5888  memcpy(&rctSel, &(pThis->rctView), sizeof(rctSel)); // use entire viewport for hightlight rect
5889  // TODO: single-line, only select to end of string?
5890 
5891 // WB_ERROR_PRINT("TEMPORARY: %s.%d select rect is 'select all' - font height %d\n", __FUNCTION__, __LINE__, iFontHeight);
5892  }
5893  else
5894  {
5895  memcpy(&rctSel, &(pThis->rctSel), sizeof(rctSel));
5896  NORMALIZE_SEL_RECT(rctSel);
5897 
5898 // WB_ERROR_PRINT("TEMPORARY: %s.%d select rectangle not empty - font height %d\n", __FUNCTION__, __LINE__, iFontHeight);
5899  }
5900 
5901  pThis->rctHighLight.left = pThis->rctWinView.left
5902  + (rctSel.left - pThis->rctView.left) * iFontWidth;
5903 
5904  pThis->rctHighLight.top = pThis->rctWinView.top
5905  + (rctSel.top - pThis->rctView.top) * iFontHeight;
5906 
5907  if(pThis->iLineFeed == LineFeed_NONE)
5908  {
5909  if(geomV.height >= iAsc + iDesc)
5910  pThis->rctHighLight.top += (geomV.height - (iAsc + iDesc)) / 2; // since single-line text is centered
5911  }
5912 
5913  pThis->rctHighLight.right = pThis->rctHighLight.left
5914  + (rctSel.right - rctSel.left) * iFontWidth;
5915 
5916  pThis->rctHighLight.bottom = pThis->rctHighLight.top // NOTE add 1 to # of lines, always
5917  + (rctSel.bottom - rctSel.top + 1) * iFontHeight;
5918 
5919  // NOTE: this is independent of the selection method. In the special case
5920  // that a single line or an entire line is selected, I may have
5921  // to handle this differently
5922 
5923 // WB_ERROR_PRINT("TEMPORARY: %s.%d select rect: %d, %d, %d, %d of %d, %d, %d, %d\n",
5924 // __FUNCTION__, __LINE__,
5925 // rctSel.left, rctSel.top, rctSel.right, rctSel.bottom,
5926 // pThis->rctView.left, pThis->rctView.top, pThis->rctView.right, pThis->rctView.bottom);
5927 //
5928 // WB_ERROR_PRINT("TEMPORARY: %s.%d highlight rect: %d, %d, %d, %d of %d, %d, %d, %d\n",
5929 // __FUNCTION__, __LINE__,
5930 // pThis->rctHighLight.left, pThis->rctHighLight.top, pThis->rctHighLight.right, pThis->rctHighLight.bottom,
5931 // pThis->rctWinView.left, pThis->rctWinView.top, pThis->rctWinView.right, pThis->rctWinView.bottom);
5932 
5933 
5934 
5935 // WB_ERROR_PRINT("TEMPORARY: %s.%d geomV.height = %d FG/BG colors %08lxH %08lxH %08lxH %08lxH\n",
5936 // __FUNCTION__, __LINE__, geomV.height,
5937 // (unsigned long)clrFG, (unsigned long)clrBG, (unsigned long)clrHFG, (unsigned long)clrHBG);
5938  }
5939  else
5940  {
5941  bzero(&rctSel, sizeof(rctSel)); // zero out selection rectangle
5942  bzero(&(pThis->rctHighLight), sizeof(pThis->rctHighLight));
5943 
5944 // WB_ERROR_PRINT("TEMPORARY: %s.%d select rectangle *EMPTY*\n", __FUNCTION__, __LINE__);
5945  }
5946 
5947 
5948  //--------------------------------------------
5949  // CREATE PIXMAP (for speeding up the process)
5950  //--------------------------------------------
5951 
5952  iPX = pThis->rctWinView.right - pThis->rctWinView.left + 2 * MIN_BORDER_SPACING;
5953  iPY = pThis->rctWinView.bottom - pThis->rctWinView.top + 2 * MIN_BORDER_SPACING;
5954 
5955 // WB_DEBUG_PRINT(DebugLevel_Verbose, "%s window %u (%08xH) pixmap dimensions %d,%d\n"
5956 // " row/col %d,%d\n",
5957 // __FUNCTION__, (int)wID, (int)wID, iPX, iPY,
5958 // pThis->iRow, pThis->iCol);
5959 
5960  if(gc2 != None && iPX > 0 && iPY > 0)
5961  {
5962  pxTemp = XCreatePixmap(pDisplay, wID, iPX, iPY,
5963  DefaultDepth(pDisplay, DefaultScreen(pDisplay)));
5964  }
5965  else
5966  {
5967  pxTemp = None;
5968  }
5969 
5970  //-----------------------
5971  // FILL/ERASE BACKGROUND
5972  //-----------------------
5973 
5974  if(pxTemp != None)
5975  {
5976  iXDelta = geomV.x - MIN_BORDER_SPACING;
5977  iYDelta = geomV.y - MIN_BORDER_SPACING;
5978 
5979  // sometimes the clipping origin, when filling the background, might matter, especially if patterns are involved
5980  WBSetClipOrigin(gc2, -iXDelta, -iYDelta); // so that it matches the display's clipping in gc
5981 
5982  WBSetForeground(gc2, clrBG);
5983  WBFillRectangle(pDisplay, pxTemp, gc2, 0, 0, iPX, iPY);
5984 
5985  WBSetForeground(gc2, clrFG);
5986  }
5987  else // FALLBACK, if no pixmap, go to window directly
5988  {
5989  iXDelta = 0;
5990  iYDelta = 0; // make sure, as they're used in a few places
5991 
5992  WBSetForeground(gc, clrBG);
5993  WBFillRectangle(pDisplay, wID, gc,
5994  pThis->rctWinView.left,
5995  pThis->rctWinView.top,
5996  pThis->rctWinView.right - pThis->rctWinView.left,
5997  pThis->rctWinView.bottom - pThis->rctWinView.top);
5998 
5999  WBSetForeground(gc, clrFG);
6000  }
6001 
6002 // WB_DEBUG_PRINT(DebugLevel_Verbose, "%s line %d - iXDelta is %d, iYDelta is %d\n", __FUNCTION__, __LINE__, iXDelta, iYDelta);
6003 
6004  // At this point, if pBuf is NULL or the # of entries zero, the background is *STILL* erased
6005  // and the cursor has to be drawn.
6006 
6007 // if(!pBuf || pBuf->nEntries <= 0)
6008 // {
6010 // goto almost_the_end; // I use the goto so I can put cleanup code there - it's safer
6011 // }
6012 
6013 
6014  // if it's a single-line object, center it within the window and display only ONE line
6015 
6016  if(pThis->iLineFeed == LineFeed_NONE) // i.e. SINGLE LINE
6017  {
6019  // SINGLE LINE PAINTING
6021 
6022  iY = geomV.height - iFontHeight;
6023  if(iY < 0) // when this happens it screws the calculation up
6024  {
6025  iY = 0;
6026  }
6027 
6028  // calculate a Y position that's essentially centered within the single-line display area
6029 
6030  iY = geomV.y + iY / 2 + iAsc; // iY is now "the baseline" for the font, where it needs to be drawn
6031  iX = geomV.x;
6032 
6033  if(pBuf)
6034  {
6035  pL = pBuf->aLines[0];
6036  }
6037  else
6038  {
6039  pL= NULL;
6040  }
6041 
6042 // if(!pL)
6043 // {
6044 // goto the_end;
6045 // }
6046 
6047 
6048  //-----------------------------
6049  // DRAWING HIGHLIGHT RECTANGLE
6050  //-----------------------------
6051 
6052  if(pThis->rctHighLight.right > pThis->rctHighLight.left &&
6053  WBRectOverlapped(pThis->rctHighLight, pThis->rctWinView))
6054  {
6055  if(pxTemp != None)
6056  {
6057  // NOTE: iXDelta and iYDelta were calculated before, when background was erased
6058  // and the clip origin was also assigned for gc2
6059 // XSetClipOrigin(pDisplay, gc2, -iXDelta, -iYDelta); // so that it matches the display's clipping in gc
6060 
6061  WBSetForeground(gc2, clrHBG); // highlight background color
6062 
6063  WBFillRectangle(pDisplay, pxTemp, gc2,
6064  pThis->rctHighLight.left - iXDelta,
6065  pThis->rctHighLight.top - iYDelta,
6066  pThis->rctHighLight.right - pThis->rctHighLight.left,
6067  pThis->rctHighLight.bottom - pThis->rctHighLight.top);
6068 
6069  WBSetForeground(gc2, clrFG);
6070  }
6071  else // FALLBACK, if no pixmap, go to window directly
6072  {
6073  WBSetForeground(gc, clrHBG); // highlight background color
6074 
6075  WBFillRectangle(pDisplay, wID, gc,
6076  pThis->rctHighLight.left,
6077  pThis->rctHighLight.top,
6078  pThis->rctHighLight.right - pThis->rctHighLight.left,
6079  pThis->rctHighLight.bottom - pThis->rctHighLight.top);
6080 
6081  WBSetForeground(gc, clrFG);
6082  }
6083  }
6084 
6085  //---------------------------------------
6086  // DRAW the text and the vertical cursor
6087  //---------------------------------------
6088 
6089  if(pL)
6090  {
6091  iLen = WBGetMBLength(pL);
6092  }
6093  else
6094  {
6095  iLen = 0;
6096  }
6097 
6098  pThis->iCursorX = pThis->iCursorY = pThis->iCursorHeight = 0; // to indicate "not drawn"
6099 
6100  for(i1=pThis->rctView.left; i1 < pThis->rctView.right; i1++, iX += iFontWidth)
6101  {
6102  WB_RECT rctChar, rctCursor;
6103 
6104  if(i1 == pThis->iCol) // display the cursor (NOTE: row ALWAYS matches)
6105  {
6106  pThis->iCursorX = iX - 1;
6107 
6108  if(pThis->iInsMode == InsertMode_OVERWRITE)
6109  {
6110  pThis->iCursorY = iY + iDesc + 1;
6111  pThis->iCursorHeight = 1;
6112 
6113  rctCursor.left = pThis->iCursorX - iXDelta;
6114  rctCursor.top = pThis->iCursorY - iYDelta;
6115  rctCursor.right = pThis->iCursorX + iFontWidth - iXDelta;
6116  rctCursor.bottom = pThis->iCursorY - iYDelta;
6117  }
6118  else // INSERT mode cursor
6119  {
6120  pThis->iCursorY = iY - iAsc - 1;
6121  pThis->iCursorHeight = iY + iDesc + 1 - pThis->iCursorY;
6122 
6123  rctCursor.left = pThis->iCursorX - iXDelta;
6124  rctCursor.top = pThis->iCursorY - iYDelta;
6125  rctCursor.right = pThis->iCursorX - iXDelta;
6126  rctCursor.bottom = pThis->iCursorY + pThis->iCursorHeight - iYDelta;
6127  }
6128 
6129 
6130  if(__internal_cursor_show(pThis->iBlinkState)) // do I draw the horizontal cursor for overwrite?
6131  {
6132  if(WBRectOverlapped(rctCursor, pThis->rctHighLight))
6133  {
6134  WBSetForeground(gc2 != None ? gc2 : gc, clrHFG);
6135  WBSetBackground(gc2 != None ? gc2 : gc, clrHBG);
6136  }
6137  else
6138  {
6139  WBSetForeground(gc2 != None ? gc2 : gc, clrFG);
6140  WBSetBackground(gc2 != None ? gc2 : gc, clrBG);
6141  }
6142 
6143  WBDrawLine(pDisplay, pxTemp != None ? pxTemp : wID,
6144  gc2 != None ? gc2 : gc,
6145  rctCursor.left, rctCursor.top, rctCursor.right, rctCursor.bottom);
6146  }
6147  }
6148 
6149  rctChar.top = iY - iAsc;
6150  rctChar.bottom = iY + iDesc;
6151  rctChar.left = iX + 1;
6152  rctChar.right = iX + iFontWidth - 1; // since I'm checking overlap, make it a bit 'skinnier'
6153 
6154  if(WBRectOverlapped(rctChar, pThis->rctHighLight))
6155  {
6156  WBSetForeground(gc2 != None ? gc2 : gc, clrHFG);
6157  WBSetBackground(gc2 != None ? gc2 : gc, clrHBG);
6158  }
6159  else
6160  {
6161  WBSetForeground(gc2 != None ? gc2 : gc, clrFG);
6162  WBSetBackground(gc2 != None ? gc2 : gc, clrBG);
6163  }
6164 
6165  // for NOW, draw one character at a time. But make sure it's within
6166  // the invalid region first...
6167 
6168  geomC.x = iX;
6169  geomC.y = iY; // TODO: for single line, move this and the next 2 outside the loop?
6170  geomC.width = iFontWidth;
6171  geomC.height = iFontHeight;
6172 
6173  if(pL && i1 < iLen) // NOTE: pL can be NULL, but iLen should be zero - test anyway
6174  {
6175  if(WBGeomOverlapped(geomC, geomP)) // only if the character's geometry overlaps the paint geometry
6176  {
6177  int iLen2;
6178  const char *p1 = WBGetMBCharPtr(pL, i1, &iLen2);
6179 
6180  if(p1 && iLen2 > 0)
6181  {
6182  DTDrawString(pDisplay, pxTemp ? pxTemp : wID, pFont,
6183  gc2 != None ? gc2 : gc,
6184  iX - iXDelta, iY - iYDelta, p1, iLen2);
6185  }
6186  }
6187  }
6188  else if(i1 > pThis->iCol)
6189  {
6190  break; // slight optimization
6191  }
6192  }
6193  }
6194  else // MULTI-LINE
6195  {
6196  int iCurRow; // my counter
6197 
6199  // MULTI LINE PAINTING
6201 
6202  // 3 different important conditions that affect painting:
6203  // 1. the cursor is (or is not) on this row
6204  // 2. the row has highlighting in it
6205  // 3. if 2, the row is either fully or partially highlighted
6206 
6207  // if 1 and 2 are FALSE, paint the string 'as-is' with DTDrawString
6208  // if 2 is TRUE, but 3 is false, paint the entire line "highlighted".
6209  // otherwise, duplicate (for this row) what single-line painting does,
6210  // dealing with cursor and highlight rectangles as needed, 1 char at a time
6211 
6212  pThis->iCursorX = pThis->iCursorY = pThis->iCursorHeight = 0; // to indicate "not drawn"
6213 
6214  for(iCurRow=pThis->rctView.top; iCurRow <= nEntries && iCurRow <= pThis->rctView.bottom; iCurRow++)
6215  {
6216  int bHighlight = 0;
6217 
6218  // NOTE: geomV already has 'border spacing' taken into account
6219 
6220  iY = geomV.y + iFontHeight * (iCurRow - pThis->rctView.top)
6221  + iAsc; // add the ascent to the top of the text to get "the baseline" for the font
6222 
6223  iX = geomV.x;
6224 
6225  if(pBuf && iCurRow < nEntries) // 'nEntries' was cached from before
6226  {
6227  pL = pBuf->aLines[iCurRow];
6228  }
6229  else
6230  {
6231  pL = NULL;
6232  }
6233 
6234  if(pL)
6235  {
6236  iLen = WBGetMBLength(pL); // length in "characters"
6237  }
6238  else
6239  {
6240  iLen = 0;
6241  }
6242 
6243  // if the row might be highlighted, it will be within 'rctSel', which can
6244  // span multiple lines. It identifies the starting and ending row/col combo, but in pixels.
6245 
6246  if((rctSel.left != rctSel.right || rctSel.top != rctSel.bottom) && // has a selection
6247  iCurRow >= pThis->rctSel.top && // row is in range below or equal to 'top'
6248  (iCurRow < pThis->rctSel.bottom || // above 'last' row
6249  (iCurRow == pThis->rctSel.bottom && pThis->rctSel.right > 0))) // last row but has column > 0
6250  {
6251  bHighlight = 1; // flag for later
6252  }
6253 
6254  if(iCurRow == pThis->iRow || // current row is cursor row
6255  (bHighlight && // I am highlighting this row AND
6256  (iCurRow == pThis->rctSel.top || // it's the starting row for highlight
6257  iCurRow == pThis->rctSel.bottom))) // it's the ending row for highlight
6258  {
6259  for(i1=pThis->rctView.left; i1 <= pThis->rctView.right; i1++, iX += iFontWidth)
6260  {
6261  WB_RECT rctCursor;
6262  int iLen2;
6263  const char *p1;
6264 
6265  if(pL && i1 < iLen)
6266  {
6267  p1 = WBGetMBCharPtr(pL, i1, &iLen2);
6268  }
6269  else
6270  {
6271  p1 = NULL; // past end of string
6272  }
6273 
6274  if(bHighlight &&
6275  ((iCurRow > pThis->rctSel.top &&
6276  iCurRow < pThis->rctSel.bottom) ||
6277  (iCurRow == pThis->rctSel.top &&
6278  i1 >= pThis->rctSel.left &&
6279  (i1 < pThis->rctSel.right || iCurRow != pThis->rctSel.bottom)) ||
6280  (iCurRow == pThis->rctSel.bottom && i1 < pThis->rctSel.right)))
6281  {
6282  int iY0 = iY - iAsc; // NOTE: iY is the BASE of the font, so I need to font ascent to get top of rect
6283 
6284  // fill the rectangle for this character with the correct background color.
6285 
6286  if(pxTemp != None)
6287  {
6288  WBSetForeground(gc2, clrHBG); // highlight background color
6289  WBSetBackground(gc2, clrHBG);
6290 
6291  WBFillRectangle(pDisplay, pxTemp, gc2,
6292  iX - iXDelta, iY0 - iYDelta,
6293  iFontWidth, iFontHeight);
6294 
6295  WBSetForeground(gc2, clrHFG);
6296  }
6297  else // FALLBACK, if no pixmap, go to window directly
6298  {
6299  WBSetForeground(gc, clrHBG); // highlight background color
6300  WBSetBackground(gc, clrHBG);
6301 
6302  WBFillRectangle(pDisplay, wID, gc,
6303  iX, iY0,
6304  iFontWidth, iFontHeight);
6305 
6306  WBSetForeground(gc, clrHFG);
6307  }
6308  }
6309  else // non-highlighted character
6310  {
6311  WBSetForeground(gc2 != None ? gc2 : gc, clrFG);
6312  WBSetBackground(gc2 != None ? gc2 : gc, clrBG);
6313  }
6314 
6315 
6316  //------------
6317  // draw cursor
6318  //------------
6319 
6320  if(iCurRow == pThis->iRow && i1 == pThis->iCol) // display the cursor (matching row/col)
6321  {
6322  pThis->iCursorX = iX - 1;
6323 
6324  if(pThis->iInsMode == InsertMode_OVERWRITE) // NOTE: horizontal cursor for overwrite
6325  {
6326  int iY0 = iY + iDesc + 1;
6327 
6328  // there WAS a bizarre compile error here, optimizing sub-expressions incorrectly, maybe
6329 
6330  pThis->iCursorY = iY0;
6331  pThis->iCursorHeight = 1;
6332 
6333  rctCursor.left = pThis->iCursorX - iXDelta;
6334  rctCursor.top = iY0 - iYDelta;
6335  rctCursor.right = pThis->iCursorX + iFontWidth - iXDelta;
6336  rctCursor.bottom = rctCursor.top;
6337 
6338 // WB_DEBUG_PRINT(DebugLevel_Verbose, "%s - iX=%d, iY=%d A=%d,D=%d dX=%d, dY=%d cursor %d,%d %d,%d,%d,%d\n", __FUNCTION__,
6339 // iX, iY, iAsc, iDesc, iXDelta, iYDelta,
6340 // pThis->iCursorX, pThis->iCursorY,
6341 // rctCursor.left, rctCursor.top, rctCursor.right, rctCursor.bottom);
6342  }
6343  else // INSERT mode cursor (normally will be this)
6344  {
6345  int iY0 = iY - iAsc - 1;
6346  int iY1 = iY + iDesc + 1;
6347 
6348  pThis->iCursorY = iY0;
6349  pThis->iCursorHeight = iY1 - iY0;
6350 
6351  rctCursor.left = pThis->iCursorX - iXDelta;
6352  rctCursor.top = iY0 - iYDelta;
6353  rctCursor.right = rctCursor.left;
6354  rctCursor.bottom = iY1 - iYDelta;
6355  }
6356 
6357  if(__internal_cursor_show(pThis->iBlinkState)) // do I draw the cursor?
6358  {
6359  WBDrawLine(pDisplay, pxTemp != None ? pxTemp : wID,
6360  gc2 != None ? gc2 : gc,
6361  rctCursor.left, rctCursor.top, rctCursor.right, rctCursor.bottom);
6362  }
6363 
6364 // WB_DEBUG_PRINT(DebugLevel_Verbose, "%s - cursor x,y = %d,%d\n", __FUNCTION__, pThis->iCursorX, pThis->iCursorY);
6365  }
6366 // else
6367 // {
6368 // WB_DEBUG_PRINT(DebugLevel_Verbose, "%s - pThis->iRow=%d,iCol=%d,iCurRow=%d,i1=%d\n",
6369 // __FUNCTION__, pThis->iRow, pThis->iCol, iCurRow, i1);
6370 // }
6371 
6372 
6373  if(p1 && iLen2 > 0)
6374  {
6375  DTDrawString(pDisplay, pxTemp ? pxTemp : wID, pFont,
6376  gc2 != None ? gc2 : gc,
6377  iX - iXDelta, iY - iYDelta, p1, iLen2);
6378  }
6379  }
6380  }
6381  else if(pL) // current row is NOT cursor row (and line is not blank)
6382  {
6383  const char *p1 = WBGetMBCharPtr(pL, pThis->rctView.left, NULL);
6384  const char *p2 = WBGetMBCharPtr(pL, pThis->rctView.right, NULL);
6385 
6386  if(bHighlight)
6387  {
6388  int iY0 = iY - iAsc; // NOTE: iY is the BASE of the font, so I need to font ascent to get top of rect
6389 
6390  if(pxTemp != None)
6391  {
6392  WBSetForeground(gc2, clrHBG); // highlight background color
6393  WBSetBackground(gc2, clrHBG);
6394 
6395  WBFillRectangle(pDisplay, pxTemp, gc2,
6396  iX - iXDelta, iY0 - iYDelta,
6397  geomV.width, iFontHeight); // entire line
6398 
6399  WBSetForeground(gc2, clrHFG);
6400  }
6401  else // FALLBACK, if no pixmap, go to window directly
6402  {
6403  WBSetForeground(gc, clrHBG); // highlight background color
6404  WBSetBackground(gc, clrHBG);
6405 
6406  WBFillRectangle(pDisplay, wID, gc,
6407  iX, iY0,
6408  geomV.width, iFontHeight); // entire line
6409 
6410  WBSetForeground(gc, clrHFG);
6411  }
6412  }
6413  else
6414  {
6415  WBSetForeground(gc2 != None ? gc2 : gc, clrFG);
6416  WBSetBackground(gc2 != None ? gc2 : gc, clrBG);
6417  }
6418 
6419  if(p1 && p1 && p2 > p1)
6420  {
6421  DTDrawString(pDisplay, pxTemp ? pxTemp : wID, pFont,
6422  gc2 != None ? gc2 : gc,
6423  iX - iXDelta, iY - iYDelta,
6424  p1, p2 - p1);
6425  }
6426 // else (this happens when lines are blank)
6427 // {
6428 // WB_DEBUG_PRINT(DebugLevel_Verbose, "%s - NOT drawing \"%s\"\n p1=%p p2=%p delta=%d\n",
6429 // __FUNCTION__, pL, p1, p2, (int)(p2 - p1));
6430 // }
6431  }
6432  }
6433  }
6434 
6435  //------------------------------------------
6436  // PIXMAP OPTIMIZATION - TRANSFER TO WINDOW
6437  //------------------------------------------
6438 
6439 //almost_the_end:
6440 
6441  if(pxTemp != None) // OPTIMIZATION (using pixmap)
6442  {
6443  int iX0=0, iY0=0, iW0=iPX, iH0=iPY;
6444 
6445  iX = pThis->rctWinView.left - MIN_BORDER_SPACING;
6446  iY = pThis->rctWinView.top - MIN_BORDER_SPACING;
6447 
6448  // only copy the invalid area (this should save some time)
6449 
6450  if(geomP.x > iX)
6451  {
6452  iX0 = geomP.x - iX;
6453  iW0 -= iX0;
6454  iX = geomP.x;
6455  }
6456 
6457  if(geomP.x + geomP.width < iX + iW0)
6458  {
6459  iW0 = geomP.x + geomP.width - iX;
6460  }
6461 
6462  if(geomP.y > iY)
6463  {
6464  iY0 = geomP.y - iY;
6465  iH0 -= iY0;
6466  iY = geomP.y;
6467  }
6468 
6469  if(geomP.y + geomP.height < iY + iH0)
6470  {
6471  iH0 = geomP.y + geomP.height - iY;
6472  }
6473 
6474  if(iH0 > 0 && iW0 > 0)
6475  {
6476  XCopyArea(pDisplay, pxTemp, wID, gc->gc, // this time use GC to do the copy
6477  iX0, iY0, iW0, iH0, iX, iY);
6478  }
6479 
6480  XFreePixmap(pDisplay, pxTemp);
6481  }
6482 
6483 
6484 the_end:
6485 
6486  // mandatory resource free-up goes here, hence the 'goto' label
6487 
6488  if(gc2 != None)
6489  {
6490  WBFreeGC(gc2);
6491  }
6492 }
6493 
6494 static int __internal_cursor_show(int iBlinkState) // determines if blink state shows the cursor
6495 {
6496  return iBlinkState != CURSOR_BLINK_OFF ? 1 : 0;
6497 }
6498 
6499 static void __internal_cursor_blink(TEXT_OBJECT *pThis, int bHasFocus)
6500 {
6501  if(WBIsValidTextObject(pThis))
6502  {
6503  int bShow = __internal_cursor_show(pThis->iBlinkState);
6504 
6505  WB_DEBUG_PRINT(DebugLevel_Verbose | DebugSubSystem_TextObject,
6506  "%s line %d: pThis iRow=%d iCol=%d rctSel=(%d,%d,%d,%d)\n",
6507  __FUNCTION__, __LINE__,
6508  pThis->iRow, pThis->iCol,
6509  pThis->rctSel.left, pThis->rctSel.top, pThis->rctSel.right, pThis->rctSel.bottom);
6510 
6511  if(!bHasFocus)
6512  {
6513  if(pThis->iBlinkState != CURSOR_BLINK_OFF)
6514  {
6515  pThis->iBlinkState = CURSOR_BLINK_OFF; // no cursor
6516 
6517  __internal_invalidate_cursor(pThis, 1);
6518  }
6519  }
6520  else
6521  {
6522  pThis->iBlinkState = (pThis->iBlinkState + 1) % CURSOR_BLINK_PERIOD;
6523 
6524  __internal_invalidate_cursor(pThis, bShow != __internal_cursor_show(pThis->iBlinkState));
6525  //pThis->iBlinkState == 0 || pThis->iBlinkState == 1);
6526  }
6527  }
6528 }
6529 
6530 static void __internal_set_save_point(TEXT_OBJECT *pThis)
6531 {
6532  // TODO: implement
6533 
6534  WB_DEBUG_PRINT(DebugLevel_Verbose, "%s - not yet implemented\n", __FUNCTION__);
6535 }
6536 
6537 static int __internal_get_modified(TEXT_OBJECT *pThis)
6538 {
6539  WB_DEBUG_PRINT(DebugLevel_Verbose, "%s - not yet implemented - returning 'modified'\n", __FUNCTION__);
6540 
6541  return 1; // for now, ALWAYS modified
6542 }
6543 
6544 
6545 
6546 
6547 // ----------------------------
6548 // MBCS UTF-8 UTILITY FUNCTIONS
6549 // ----------------------------
6550 
6551 // see RFC3629 and https://en.wikipedia.org/wiki/UTF-8#Codepage_layout
6552 
6553 static int internal_IsMBCharValid(const char *pChar, int *piLen)
6554 {
6555 const unsigned char *p1;
6556 int iRval;
6557 
6558 
6559  if((unsigned char)*pChar < 0x80) // normal ASCII (including '0' byte)
6560  {
6561  if(piLen)
6562  {
6563  if(*pChar)
6564  {
6565  *piLen = 1;
6566  }
6567  else
6568  {
6569  *piLen = 0;
6570  }
6571  }
6572 
6573  return 1; // valid char
6574  }
6575 
6576  p1 = (const unsigned char *)pChar;
6577  iRval = 0; // initially, call it 'invalid'
6578 
6579  // 80-BF - values 0-3F 'continuation' bytes for mbcs [sequence will not start with this]
6580  // C0-DF - 2-byte character
6581  // (C0=0, C1=40H, C2=80H, up to 7C0H for DF; values C0 and C1 are actually 'not valid' as it's pointless)
6582  // E0-EF - 3-byte character
6583  // (E0 adds 800H; E1 adds 1000H; E2 2000H; etc. up to F000H for EF. not all E0 sequences are valid)
6584  // F0-FF - 4+byte character
6585  // (F0 is 10000H; F1 40000H; F2 80000H; F3 C0000H; F4 100000H; values F5 and above are 'not valid'
6586  // Not all F0's are valid, either)
6587  //
6588  // For invalid sequences, this function will attempt to work around it; however RFC3629 requires them to be error conditions
6589  // to avoid security-related problems caused by invalid UTF8 sequences.
6590 
6591  if(*p1 < 0xc2 || // a continuation character? an 'invalid' sequence?
6592  *p1 >= 0xf5) // assume value cannot be > 0xff, right?
6593  {
6594  p1++; // invalid; length is 1 (like ASCII)
6595  }
6596  else if(*p1 < 0xe0) // 2-byte sequence
6597  {
6598  if((unsigned char)p1[1] >= 0x80 && (unsigned char)p1[1] <= 0xbf) // valid 2-byte sequence
6599  {
6600  p1 += 2;
6601  iRval = 1;
6602  }
6603  else
6604  {
6605  p1++; // invalid; length is 1 (like ASCII)
6606  }
6607  }
6608  else if(*p1 < 0xf0) // 3-byte sequence
6609  {
6610  if(p1[1] >= 0x80 && p1[1] <= 0xbf && // valid 3-byte sequence
6611  p1[2] >= 0x80 && p1[2] <= 0xbf)
6612  {
6613  // TODO: validate E0 sequences
6614 
6615  p1 += 3;
6616  iRval = 1;
6617  }
6618  else // now it gets trickier...
6619  {
6620  // by convention, treat it like 8-bit ASCII with a length of 1
6621  p1++;
6622  }
6623  }
6624  else // if(*p1 < 0xf5)
6625  {
6626  if(p1[1] >= 0x80 && p1[1] <= 0xbf && // valid 4-byte sequence
6627  p1[2] >= 0x80 && p1[2] <= 0xbf &&
6628  p1[3] >= 0x80 && p1[3] <= 0xbf)
6629  {
6630  // TODO: validate F0 sequences
6631 
6632  p1 += 4;
6633  iRval = 1;
6634  }
6635  else // now it gets trickier...
6636  {
6637  // by convention, treat it like 8-bit ASCII with a length of 1
6638  p1++;
6639  }
6640  }
6641 
6642  if(piLen)
6643  {
6644  *piLen = (const char *)p1 - pChar; // this simplified length indicator helps with copy/pasta [if I need it]
6645  }
6646 
6647 #ifndef NO_DEBUG
6648  if(!iRval || ((const char *)p1 - pChar) > 1)
6649  {
6650  int iLen = ((const char *)p1 - pChar);
6651 
6652  WB_DEBUG_PRINT(DebugLevel_Verbose, "%s line %d - return=%d char=%02xH", __FUNCTION__, __LINE__, iRval, (unsigned char)*pChar);
6653  while(iLen > 1)
6654  {
6655  iLen--;
6656  pChar++;
6657 
6658  WBDebugPrint(",%02xH", (unsigned char)*pChar);
6659  }
6660  WBDebugPrint("\n");
6661  }
6662 #endif // NO_DEBUG
6663 
6664  return iRval;
6665 }
6666 
6667 static int internal_MBstrlen(const char *pString)
6668 {
6669 const char *p1 = pString;
6670 int iLen, iRval;
6671 
6672 
6673  iRval = 0;
6674 
6675  while(*p1)
6676  {
6677  // step through 1 character at a time until I reach the end of the string, or iCol reaches zero
6678 
6679  if(!internal_IsMBCharValid(p1, &iLen))
6680  {
6681  p1++; // treat like 8-bit ASCII if it's an invalid UTF-8 sequence
6682 
6683  // NOTE: may change or render as EF BF BD i.e. U+FFFD (a diamond with question mark)
6684  }
6685  else
6686  {
6687  p1 += iLen;
6688  }
6689 
6690  iRval++; // we only count blue cars (and multi-byte chars as "1")
6691  }
6692 
6693  return iRval;
6694 }
6695 
6696 // return the 'character' index for the 'column' specified by 'iCol' for MBCS
6697 
6698 static int internal_MBColIndex(const char *pString, int iCol)
6699 {
6700 const char *p1 = pString;
6701 int iLen;
6702 
6703 
6704  while(iCol > 0 && *p1)
6705  {
6706  // step through 1 character at a time until I reach the end of the string, or iCol reaches zero
6707 
6708  if(!internal_IsMBCharValid(p1, &iLen))
6709  {
6710  p1++; // treat like 8-bit ASCII if it's an invalid UTF-8 sequence
6711 
6712  // NOTE: may change or render as EF BF BD i.e. U+FFFD (a diamond with question mark)
6713  }
6714  else
6715  {
6716  p1 += iLen;
6717  }
6718 
6719  iCol--;
6720  }
6721 
6722  return (p1 - pString); // NOTE: need to verify length before calling or this could be WRONG
6723 }
6724 
6725 
6726 char * WBInsertMBChars(char *pString, int iCol, const char *pszMBString, int cbString,
6727  int fTab, int fOverwrite, int *piNewCol, char **ppInserted)
6728 {
6729  if(ppInserted)
6730  {
6731  *ppInserted = NULL; // initial value
6732  }
6733 
6734  if(piNewCol)
6735  {
6736  *piNewCol = iCol; // initial value
6737  }
6738 
6739 
6740 // POOBAH
6741 
6742 
6743  return NULL;
6744 }
6745 
6746 char * WBSplitMBLine(char *pString, int iCol)
6747 {
6748 char *p1, *pRval;
6749 int iLen, i1;
6750 
6751 
6752  iLen = internal_MBstrlen(pString);
6753 
6754  if(iCol >= iLen) // past end of string?
6755  {
6756  pRval = WBCopyString(""); // allocated pointer that's a copy of an empty string - it's what I'll start with
6757  }
6758  else
6759  {
6760  i1 = internal_MBColIndex(pString, iCol);
6761 
6762  pRval = WBCopyString(pString + i1);
6763 
6764  if(pRval)
6765  {
6766  pString[i1] = 0; // terminate it here (my 'split')
6767  }
6768  }
6769 
6770  if(pRval) // if I have a valid return, right-trim the original line
6771  {
6772  p1 = pString + strlen(pString);
6773 
6774  while(p1 > pString && *(p1 - 1) <= ' ') // right-trim it
6775  {
6776  *(--p1) = 0; // right-trim the string - make ALL white space GO AWAY
6777  }
6778  }
6779 
6780  return(pRval);
6781 }
6782 
6783 char * WBJoinMBLine(char *pString, int iCol, const char *pJoin)
6784 {
6785 int iLen, iLenNew, iJoin;
6786 char *pRval, *p1;
6787 
6788 
6789  iLen = internal_MBstrlen(pString);
6790  if(iLen < iCol)
6791  {
6792  iLenNew = iCol;
6793  }
6794  else
6795  {
6796  iLenNew = iLen;
6797  }
6798 
6799  iJoin = internal_MBstrlen(pJoin);
6800 
6801  pRval = WBReAlloc(pString, iLenNew + iJoin + 2);
6802  if(!pRval)
6803  {
6804  return NULL;
6805  }
6806 
6807  p1 = pRval + strlen(pRval); // regardless of actual length, this works
6808 
6809  while(iLen < iLenNew)
6810  {
6811  *(p1++) = ' '; // add white space up to 'iCol'
6812  iLen++;
6813  }
6814 
6815  strcpy(p1, pJoin);
6816 
6817  return pRval;
6818 }
6819 
6820 int WBDelMBChars(char *pString, int iCol, int nDel, int *piNewCol, char **ppDeleted)
6821 {
6822 int iLen, iIndex, i1, iRval = nDel;
6823 char *pDelChar, *p1;
6824 
6825 
6826  if(ppDeleted)
6827  {
6828  *ppDeleted = NULL; // initial value
6829  }
6830 
6831  if(piNewCol)
6832  {
6833  *piNewCol = iCol; // initial value
6834  }
6835 
6836  if(!pString || !nDel || !*pString)
6837  {
6838  if(piNewCol)
6839  {
6840  *piNewCol = iCol; // for now, just do this, even if string is blank
6841  }
6842 
6843  return nDel; // parameter validation
6844  }
6845 
6846  iLen = internal_MBstrlen(pString);
6847 
6848  if(iRval > 0)
6849  {
6850  if(ppDeleted && iLen > iCol)
6851  {
6852  pDelChar = WBAlloc(4 // max size of MB char
6853  * (iLen - iCol + 1) // 1 more than # of chars to delete
6854  + 1); // additional storage for zero byte
6855 
6856  *ppDeleted = pDelChar; // NOTE: caller checks for NULL to determine error state
6857 
6858  if(pDelChar)
6859  {
6860  *pDelChar = 0; // make sure
6861  }
6862  }
6863  else
6864  {
6865  pDelChar = NULL;
6866  }
6867 
6868  while(iLen > iCol && iRval > 0)
6869  {
6870  iIndex = internal_MBColIndex(pString, iCol);
6871 
6872  p1 = pString + iIndex; // always
6873 
6874  if(!*p1)
6875  {
6876  break; // I'm at the end of the string (sanity test)
6877  }
6878 
6879  if(!internal_IsMBCharValid(p1, &i1))
6880  {
6881  // invalid character - truncate the line here!
6882 
6883  *p1 = 0;
6884  iRval = 0; // assume 'end of line' and terminate deleting
6885 
6886  break;
6887  }
6888 
6889  if(pDelChar && i1 > 0)
6890  {
6891  memcpy(pDelChar, p1, i1);
6892  pDelChar += i1;
6893 
6894  *pDelChar = 0; // mark the end of the string
6895  }
6896 
6897  iRval --; // deleting one character (so remove a 'delete')
6898  iLen--; // update the new length
6899 
6900  // i1 is the length of the next character. slide 'em down! [this is less efficient, but more 'stable']
6901  strcpy(p1, p1 + i1); // simplest method
6902  }
6903  }
6904  else
6905  {
6906  if(ppDeleted && iCol > 0)
6907  {
6908  pDelChar = WBAlloc(4 // max size of MB char
6909  * (iCol + 1) // 1 more than # of chars to delete
6910  + 1); // additional storage for zero byte
6911 
6912  *ppDeleted = pDelChar; // NOTE: caller checks for NULL to determine error state
6913 
6914  if(pDelChar)
6915  {
6916  *pDelChar = 0; // make sure
6917  }
6918  }
6919  else
6920  {
6921  pDelChar = NULL;
6922  }
6923 
6924  // NOW we do backspacing, which preserves the character at 'iCol'. this is a little trickier.
6925  // when we end up at column zero, we return the 'iRval' count.
6926 
6927  while(iCol > 0 && iRval < 0)
6928  {
6929  if(iCol <= iLen) // I'm NOT past the end
6930  {
6931  iIndex = internal_MBColIndex(pString, iCol - 1); // PREVIOUS column
6932 
6933  p1 = pString + iIndex; // always
6934 
6935  if(!*p1)
6936  {
6937  break; // I'm at the end of the string (sanity test, unlikely to happen)
6938  }
6939 
6940  if(!internal_IsMBCharValid(p1, &i1))
6941  {
6942  // invalid character - truncate the line here!
6943 
6944  *p1 = 0;
6945  iRval = 0; // assume 'end of line' at this point, and terminate deleting
6946 
6947  break;
6948  }
6949 
6950  if(pDelChar && i1 > 0)
6951  {
6952  // insert this character into the beginning of the 'undo' info string
6953  // there are probably BETTER ways to do this, but this is what I'll do.
6954 
6955  memmove(pDelChar + i1, pDelChar, strlen(pDelChar) + 1); // open up space. make sure ending zero byte is included.
6956  memcpy(pDelChar, p1, i1);
6957  }
6958 
6959  // i1 is the length of the prev character, so slide 'em down! [this is less efficient, but more 'stable']
6960  strcpy(p1, p1 + i1); // simplest method (I could also get the pointer to the 'iCol'th char, but this is more consistent)
6961  }
6962  else
6963  {
6964  // since I'm at the end, just delete the last char in the line (effectively) by decrementing the column
6965  // so nothing else to do here other than the end of the loop
6966  }
6967 
6968  iCol--;
6969  iRval++; // because it's NEGATIVE, so we increment it
6970  }
6971  }
6972 
6973  // it seems in both cases (above) this section remains exactly the same
6974  // how amazing that works out!
6975 
6976  if(piNewCol)
6977  {
6978  *piNewCol = iCol; // 'iCol' will be the NEW column for the cursor
6979  }
6980 
6981  return iRval;
6982 }
6983 
6984 int WBGetMBLength(const char *pString)
6985 {
6986  if(!pString)
6987  {
6988  return 0;
6989  }
6990 
6991  return internal_MBstrlen(pString);
6992 }
6993 
6994 char * WBGetMBCharPtr(char *pString, int iCol, int *pcbLen)
6995 {
6996 int iIndex;
6997 
6998 
6999  if(!pString)
7000  {
7001  return NULL;
7002  }
7003 
7004  iIndex = internal_MBColIndex(pString, iCol);
7005 
7006  if(pString[iIndex]) // it's not a 0-byte
7007  {
7008  if(!internal_IsMBCharValid(pString + iIndex, pcbLen))
7009  {
7010  return NULL; // character is not valid
7011  }
7012  }
7013  else
7014  {
7015  if(pcbLen)
7016  {
7017  *pcbLen = 0;
7018  }
7019 
7020  // TODO: anything else?
7021  }
7022 
7023  return pString + iIndex;
7024 }
7025 
7026 int WBGetMBColIndex(const char *pString, const char *pChar)
7027 {
7028 const char *p1 = pString;
7029 int iLen, iRval;
7030 
7031 
7032  if(pChar <= pString)
7033  {
7034  return 0;
7035  }
7036 
7037  iRval = 0;
7038 
7039  while(*p1 && p1 < pChar)
7040  {
7041  // step through 1 character at a time until I reach the end of the string, or iCol reaches zero
7042 
7043  if(!internal_IsMBCharValid(p1, &iLen))
7044  {
7045  p1++; // treat like 8-bit ASCII if it's an invalid UTF-8 sequence
7046 
7047  // NOTE: may change or render as EF BF BD i.e. U+FFFD (a diamond with question mark)
7048  }
7049  else
7050  {
7051  p1 += iLen;
7052  }
7053 
7054  iRval++; // we only count blue cars (and multi-byte chars as "1")
7055  }
7056 
7057  if(p1 != pChar)
7058  {
7059  return 0; // this is an error condition
7060  }
7061 
7062  return iRval; // returns the actual column index for 'pChar' within the string pointed by 'pString'
7063 }
7064 
7065 
GC gc
the associated 'GC'
const char * CHGetHighlightForegroundColor(Display *pDisplay)
returns highlight foreground color
Definition: conf_help.c:3409
no drag in progress
Definition: text_object.h:180
#define WB_LIKELY(x)
optimization for code branching when condition is 'likely'. use within conditionals
'base class' structure for TEXT_OBJECT
Definition: text_object.h:1087
#define WBRectOverlapped(R1, R2)
Returns logical TRUE if the rectangle R1 overlaps/intersects R2.
WB_RECT rctWinViewOld
previous viewport, in window coordinates [for invalidating window efficiently]
Definition: text_object.h:936
int WBGetMBColIndex(const char *pString, const char *pChar)
Obtain the column index from a pointer within a multi-byte character string.
Definition: text_object.c:7026
char * WBGetMBCharPtr(char *pString, int iCol, int *pcbLen)
Obtain the pointer to a specific multi-byte character within a multi-byte character string,...
Definition: text_object.c:6994
TEXT_OBJECT * WBTextObjectConstructor(unsigned long cbStructSize, const char *szText, unsigned long cbLen, Window wIDOwner)
Generic constructor for a TEXT_OBJECT using defaults.
Definition: text_object.c:826
void WBTextObjectSetColorContextCallback(TEXT_OBJECT *pThis, unsigned long(*callback)(TEXT_OBJECT *pThis, int nRow, int nCol), void *pColorContextPointer)
assign callback function for 'color context' for a given character
int WBDrawLine(Display *display, Drawable d, WBGC gc, int x1, int y1, int x2, int y2)
Wrapper for XDrawLines()
#define MIN_LINE_SPACING
Definition: text_object.h:199
#define WB_DEBUG_PRINT(L,...)
Preferred method of implementing conditional debug output.
Definition: debug_helper.h:364
void(* destroy)(TEXT_OBJECT *pThis)
Call this prior to de-allocating memory to free up any internal objects or storage.
Definition: text_object.h:220
int WBFontAscent(WB_FONTC pFont0)
Get the maximum character ascent from a WB_FONT.
Definition: font_helper.c:495
Utilities for copying and drawing text, determining text extents, and so on.
int iMaxFontWidth
maximum width of font in pixels (mostly for proportional pitch) - cached by 'expose' handler
Definition: text_object.h:927
int x
the 'x' value of the point. can be negative.
int iFontWidth
average width of font in pixels (mostly for fixed pitch) - cached by 'expose' handler
Definition: text_object.h:926
int iFileType
file type - -1=Makefile, 0=plain text, others are 'file type' constants, bit flags to preserve hard t...
Definition: text_object.h:905
void WBTextBufferLineChange(TEXT_BUFFER *pBuf, unsigned long nLine, int nNewLen)
Text buffer 'cached information' update function indicating a change to a line's length.
Definition: text_object.c:516
OVERWRITE mode, character inserted on top of character to the right of the cursor (if any),...
Definition: text_object.h:169
int iScrollMode
scroll mode - 0=normal, 1='scroll lock'
Definition: text_object.h:909
char * aLines[2]
array of 'lines'. each pointer is suballocated via WBAlloc()
Definition: text_object.h:1100
BOX (selects a 'box' using virtual spacing)
Definition: text_object.h:157
int WBStringLineCount(const char *pSrc, unsigned int nMaxChars)
Determine how many 'lines' are in a block of text by counting 'linefeed' characters.
int WBFontAvgCharWidth(WB_FONTC pFont0)
Get the average character width for a font.
Definition: font_helper.c:343
char * WBSplitMBLine(char *pString, int iCol)
Split a multi-byte characters into a WBAlloc'd string, at a specified column, terminating the origina...
Definition: text_object.c:6746
WB_RECT rctHighLight
highlight rect {0,0,0,0} implies NONE
Definition: text_object.h:900
static __inline__ void WBInitializeInPlaceTextObject(TEXT_OBJECT *pTextObject, Window wIDOwner)
initialize an 'in-place' TEXT_OBJECT structure
Definition: text_object.h:1045
unsigned int width
#define HARD_TAB_CHAR
Definition: text_object.h:187
int WBDelMBChars(char *pString, int iCol, int nDel, int *piNewCol, char **ppDeleted)
Delete a specified number of multi-byte characters from a string 'in place', starting at a specified ...
Definition: text_object.c:6820
int iCursorX
X position of cursor as last drawn in expose event.
Definition: text_object.h:917
unsigned int aLineCacheLen[TEXT_BUFFER_LINE_CACHE_SIZE]
The actual line lengths associated with 'aLineCache' (zero if unused)
Definition: text_object.h:1097
XFontSet WBFontSetFromFont(Display *pDisplay, const XFontStruct *pFont)
Creates an 'XFontSet' from an XFontStruct for a given display.
Definition: font_legacy.c:1579
int WBGetMBLength(const char *pString)
Obtain the length of a multi-byte character string in 'characters' (not bytes)
Definition: text_object.c:6984
unsigned long(* pColorContextCallback)(TEXT_OBJECT *, int, int)
callback function to get the context color of a character. default is NULL.
Definition: text_object.h:947
mouse 'drag' (select mode)
Definition: text_object.h:182
WB_RECT rctSel
select boundary in characters, {0,0,0,0} implies NONE, {-1,x,x,x} implies ALL. if top == bottom,...
Definition: text_object.h:899
int WBPostPriorityEvent(Window wID, XEvent *pEvent)
Places a copy of the specified event at the end of the priority (internal) event queue.
int WBTextObjectCalculateLineHeight(int iAscent, int iDescent)
Calculate the correct per-line height (in pixels) for a specified font ascent and descent.
Definition: text_object.c:864
static __inline__ Display * WBGetDefaultDisplay(void)
Returns the default Display.
internal wrapper struct for X11 'geometry' definition
'configuration helper' main header file for the X11 Work Bench Toolkit API
Atom aRECALC_LAYOUT
notify window that it should re-calculate things like scrollbars and viewports
#define GCAll
A bit mask for ALL GC properties (used when copying a GC)
int iInsMode
insert mode - 0=overwrite, 1=insert
Definition: text_object.h:907
int iSelMode
selection mode - 0=normal, 1=line, 2=box
Definition: text_object.h:908
unsigned long nArraySize
allocated size of aLines array
Definition: text_object.h:1089
int WBCheckReAllocTextBuffer(TEXT_BUFFER **ppBuf, int nLinesToAdd)
Re-allocator for TEXT_BUFFER object.
Definition: text_object.c:436
void * WBReAlloc(void *pBuf, int nNewSize)
High performance memory sub-allocator 're-allocate'.
LINE (always selects entire line when spanning multiple lines)
Definition: text_object.h:156
int WBSetForeground(WBGC hGC, unsigned long foreground)
Assign foreground color, a wrapper for XSetForeground()
static void __internal_do_expose(TEXT_OBJECT *pThis, Display *pDisplay, Window wID, WBGC gc, const WB_GEOM *pPaintGeom, const WB_GEOM *pViewGeom, WB_FONTC pFont)
Definition: text_object.c:5557
char * WBJoinMBLine(char *pString, int iCol, const char *pJoin)
Split a multi-byte characters into a WBAlloc'd string, at a specified column, terminating the origina...
Definition: text_object.c:6783
WB_RECT rctViewOld
previous viewport [for invalidating window efficiently]
Definition: text_object.h:935
#define COPY_COLOR_NAME(X, Y, Z)
macro to get a color name or use default if it does not exist in settings
unsigned int nMinMaxCol
The 'smallest maximum' recorded in 'aLineCache'.
Definition: text_object.h:1094
'base class' structure for TEXT_OBJECT
Definition: text_object.h:893
void * pText
pointer to (abstracted) object containing the text. void pointer allows abstraction....
Definition: text_object.h:940
#define WBGeomOverlapped(G1, G2)
Returns logical TRUE if the geometry G1 overlaps/intersects G2.
int iCol
current col (cursor)
Definition: text_object.h:914
int iCursorHeight
height of cursor as last drawn in expose event (always 1 if 'overwrite' cursor)
Definition: text_object.h:919
int iPos
position within buffer (reserved, MBCS may require it)
Definition: text_object.h:915
reserved - specifies custom array, indicating the size of the array containing the character values,...
Definition: text_object.h:144
Internal-only structure for undo/redo buffer for text object.
Definition: text_object.c:78
int iCursorY
Y position of cursor as last drawn in expose event (top of cursor)
Definition: text_object.h:918
void * WBAlloc(int nSize)
High performance memory sub-allocator 'allocate'.
enum e_undo_operation UNDO_OPERATION
Internal-only enumeration for undo/redo buffer 'iOperation' member.
Definition: text_object.c:115
char * WBInsertMBChars(char *pString, int iCol, const char *pszMBString, int cbString, int fTab, int fOverwrite, int *piNewCol, char **ppInserted)
Insert multi-byte characters into a WBAlloc'd string, at a specified column.
Definition: text_object.c:6726
void * pUndo
pointer to 'undo' buffer. NULL if empty.
Definition: text_object.h:942
void * pRedo
pointer to 'redo' buffer. NULL if empty.
Definition: text_object.h:943
const char * CHGetHighlightBackgroundColor(Display *pDisplay)
returns highlight background color
Definition: conf_help.c:3425
#define AUTO_HSCROLL_SIZE
Definition: text_object.h:195
#define WB_ERROR_PRINT(...)
Preferred method of implementing an 'error level' debug message for all subsystems.
Definition: debug_helper.h:474
void WBTextObjectDestructor(TEXT_OBJECT *pObj)
Generic detructor for a TEXT_OBJECT.
Definition: text_object.c:848
unsigned int nMaxCol
The maximum column number for any line, rounded up by 'DEFAULT_TAB_WIDTH'.
Definition: text_object.h:1093
void WBFree(void *pBuf)
High performance memory sub-allocator 'free'.
static __inline__ void WBInvalidateRect(Window wID, const WB_RECT *pRCT, int bPaintFlag)
'Paint' helper, invalidates a WB_RECT for asynchronous Expose event generation
XColor clrHFG
highlight FG color
Definition: text_object.h:902
WBGC WBCreateGC(Display *pDisplay, Drawable dw, unsigned long valuemask, const XGCValues *values)
Creates a WBGC, wrapper for XCreateGC()
int y
the 'y' value of the point. can be negative.
int iBlinkState
cursor blink state
Definition: text_object.h:921
INSERT mode, character inserted at cursor.
Definition: text_object.h:168
WB_RECT rctView
viewport, in characters (rctView.top is the top visible line, always)
Definition: text_object.h:931
unsigned long nEntries
number of entries currently in the array - call WBCheckReAllocTextBuffer() before increasing
Definition: text_object.h:1090
int WBCopyGC2(WBGC hGCOrig, unsigned long valuemask, WBGC hGCDest)
makes a copy of a WBGC, a wrapper for XCopyGC()
const TEXT_OBJECT_VTABLE * vtable
method function pointers (similar to C++ virtual member functions)
Definition: text_object.h:895
#define WB_POINTER_BUTTON1
WB_POINTER button bitmask indicating that button 1 is pressed.
e_undo_operation
Internal-only enumeration for undo/redo buffer 'iOperation' member.
Definition: text_object.c:101
A 'C++'-like object for managing text, that can be overridden for custom behavior.
#define DEFAULT_TAB_WIDTH
Definition: text_object.h:191
e_LineFeed
line feed (line ending) types for TEXT_OBJECT
Definition: text_object.h:136
void DTDrawString(Display *pDisplay, Drawable drawable, WB_FONTC pFont, WBGC gc, int x, int y, const char *pString, int nLength)
Draw text in a platform-independent manner for UTF-8 or multi-byte text, equivalent to WBDrawString()...
Definition: draw_text.c:111
unsigned long aLineCache[TEXT_BUFFER_LINE_CACHE_SIZE]
An array of line indices, sorted longest to least, for the 'longest lines'.
Definition: text_object.h:1095
int WBTextBufferLineLength(TEXT_BUFFER *pBuf, unsigned long nLine)
Text buffer 'cached information' query function indicating a line's cached length.
Definition: text_object.c:494
const char * WBStringNextLine(const char *pSrc, unsigned int *pnMaxChars)
Locate the next line in a block of text, returning its pointer (and updating remaining length)
XColor clrHBG
highlight BG color
Definition: text_object.h:903
unsigned int height
internal wrapper struct for 'rectangle' definition
int WBFontDescent(WB_FONTC pFont0)
Get the maximum character descent from a WB_FONT.
Definition: font_helper.c:443
int WBSetClipOrigin(WBGC hGC, int clip_x_origin, int clip_y_origin)
Set clip origin, a wrapper for XSetClipOrigin()
Display * WBGetWindowDisplay(Window wID)
returns the Display associated with a window
void WBFreeGC(WBGC hGC)
Free resources for a WBGC, wrapper for XFreeGC()
int WBFontMaxCharWidth(WB_FONTC pFont0)
Get the maximum character width for a font.
Definition: font_helper.c:394
WB_RECT rctWinView
viewport, in window coordinates, or 'empty' if unknown
Definition: text_object.h:932
void * pColorContext
a user-controlled 'color context' pointer - can be anything, however
Definition: text_object.h:946
unsigned long WBGetGCFGColor(WBGC gc)
returns the currently assigned foreground color for a WBGC
unsigned long WBGetGCBGColor(WBGC gc)
returns the currently assigned background color for a WBGC
#define MIN_BORDER_SPACING
Definition: text_object.h:203
void WBFreeTextBuffer(TEXT_BUFFER *pBuf)
Re-allocator for TEXT_BUFFER object, returns ZERO on success. Pointer may be modified (or not).
Definition: text_object.c:471
static __inline__ int WBIsValidTextObject(const TEXT_OBJECT *pObj)
'TEXT_OBJECT' validator
Definition: text_object.h:1028
int WBFillRectangle(Display *display, Drawable d, WBGC gc, int x, int y, unsigned int width, unsigned int height)
Wrapper for XFillRectangle()
void WBGetWindowGeom(Window wID, WB_GEOM *pGeom)
Returns the RAW geometry of the window as reported by the window manager.
Window wIDOwner
owner window (cached from paint/expose handling and/or cursor blink)
Definition: text_object.h:898
int iAsc
font height ascension
Definition: text_object.h:924
int WBSetBackground(WBGC hGC, unsigned long background)
Assign background color, a wrapper for XSetBackground()
void WBDebugPrint(const char *pFmt,...) __attribute__((format(printf
conditional debug message output
default for OS
Definition: text_object.h:139
enum e_LineFeed iLineFeed
linefeed type (see enum). LineFeed_NONE implies SINGLE LINE
Definition: text_object.h:906
An allocated structure containing XFontStruct, XFontInfo, and XftFont [as applicable] for a specified...
Definition: font_helper.h:152
int iTab
tab width in characters (0 = system default)
Definition: text_object.h:910
char * WBCopyString(const char *pSrc)
A simple utility that returns a WBAlloc() copy of a 0-byte terminated string.
#define TEXT_OBJECT_TAG
The 'tag' for a TEXT_OBJECT structure.
Definition: text_object.h:887
internal wrapper struct for 'point' definition
single line
Definition: text_object.h:138
internal wrapper struct for GC with local cache
int iDesc
font height descension
Definition: text_object.h:925
'vtable' structure for TEXT_OBJECT
Definition: text_object.h:214
int iDragState
if '1' bit is set, cursor drag. if '2' bit is set, mouse drag
Definition: text_object.h:922
unsigned int ulTag
tag word, always assigned to TEXT_OBJECT_TAG
Definition: text_object.h:897
#define WB_WARN_PRINT(...)
Preferred method of implementing a 'warning level' debug message for all subsystems.
Definition: debug_helper.h:467
int iRow
current row (cursor)
Definition: text_object.h:913
cursor 'drag' (select mode)
Definition: text_object.h:181
void WBTextBufferRefreshCache(TEXT_BUFFER *pBuf)
Text buffer 'cached information' refresh function.
Definition: text_object.c:691
WB_FONTC WBQueryGCFont(WBGC gc)
return the WB_FONTC object that was assigned to a WBGC
TEXT_BUFFER * WBAllocTextBuffer(const char *pBuf, unsigned int cbBufSize)
Generic constructor for a TEXT_BUFFER using defaults.
Definition: text_object.c:320
WB_FONTC WBGetDefaultFont(void)
Returns a pointer to the default font WB_FONT for the default display. This is a shared resource; do ...