X11 Work Bench Toolkit  1.0
text_object.c
Go to the documentation of this file.
1 
2 // //
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-2016 by Bob Frazier (aka 'Big Bad Bombastic Bob')
18  all rights reserved
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  BSD-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  BSD-like license. See COPYING and README files for more information.
44 
45 
46  Additional information at http://sourceforge.net/projects/X11workbench
47 
48 ******************************************************************************/
49 
57 #include <stdio.h>
58 #include <stdlib.h>
59 #include <unistd.h>
60 #include <memory.h>
61 #include <string.h>
62 #include <strings.h>
63 #include <errno.h>
64 #include <limits.h>
65 
66 #include "draw_text.h"
67 #include "text_object.h"
68 #include "conf_help.h"
69 
70 
71 // INTERNAL STRUCTURES
72 
73 struct __internal_undo_redo_buffer
74 {
75  struct __internal_undo_redo_buffer *pNext; // singly linked list [for now]
76  // NOTE: a background process can trim this to a reasonable size
77 
78  // NOTE: for a simple row/col insert or paste, left=right, top=bottom
79  // for all other operations, the rctSel will apply accordingly
80  WB_RECT rctSelOld; // original select rectangle (as applicable)
81  WB_RECT rctSelNew; // new select rectangle after the operation
82 
83  int iOperation; // select=0, delete=1, insert/paste=2, replace=3
84  int iSelMode; // selection mode
85 
86  int nOld; // size of 'old' buffer (zero if none)
87  int nNew; // size of 'new' buffer (zero if none)
88 
89  char aData[2]; // actual data for operation
90 };
91 
92 #define UNDO_SELECT 0
93 #define UNDO_DELETE 1
94 #define UNDO_INSERT 2
95 #define UNDO_REPLACE 3 /* currently not used */
96 #define UNDO_INDENT 4
97 
98 
99 #define SEL_RECT_ALL(X) ((X)->rctSel.left < 0)
100 #define SEL_RECT_EMPTY(X) (!SEL_RECT_ALL(X) && ((X)->rctSel.left == (X)->rctSel.right && (X)->rctSel.bottom == (X)->rctSel.top))
101 #define NORMALIZE_SEL_RECT(X) {if((X).top > (X).bottom || ((X).top == (X).bottom && (X).left > (X).right)) \
102  { int i1 = (X).left; (X).left = (X).right; (X).right = i1; i1 = (X).top; (X).top = (X).bottom; (X).bottom = i1; }}
103 
104 
105 // *************************
106 // LOCAL FUNCTION PROTOTYPES
107 // *************************
108 
109 static void __internal_destroy(struct _text_object_ *pThis);
110 static void __internal_init(struct _text_object_ *pThis);
111 static void __internal_highlight_colors(struct _text_object_ *pThis, XColor clrHFG, XColor clrHBG);
112 static char * __internal_get_text(struct _text_object_ *pThis);
113 static void __internal_set_text(struct _text_object_ *pThis, const char *szText, unsigned long cbLen);
114 static int __internal_get_rows(const struct _text_object_ *pThis);
115 static int __internal_get_cols(struct _text_object_ *pThis);
116 static int __internal_get_filetype(const struct _text_object_ *pThis);
117 static void __internal_set_filetype(struct _text_object_ *pThis, int iFileType);
118 static int __internal_get_linefeed(const struct _text_object_ *pThis);
119 static void __internal_set_linefeed(struct _text_object_ *pThis, int iLineFeed);
120 static int __internal_get_insmode(const struct _text_object_ *pThis);
121 static void __internal_set_insmode(struct _text_object_ *pThis, int iInsMode);
122 static int __internal_get_selmode(const struct _text_object_ *pThis);
123 static void __internal_set_selmode(struct _text_object_ *pThis, int iSelMode);
124 static int __internal_get_tab(const struct _text_object_ *pThis);
125 static void __internal_set_tab(struct _text_object_ *pThis, int iTab);
126 static int __internal_get_scrollmode(const struct _text_object_ *pThis);
127 static void __internal_set_scrollmode(struct _text_object_ *pThis, int iScrollMode);
128 static void __internal_get_select(const struct _text_object_ *pThis, WB_RECT *pRct);
129 static void __internal_set_select(struct _text_object_ *pThis, const WB_RECT *pRct);
130 static int __internal_has_select(const struct _text_object_ *pThis);
131 static char* __internal_get_sel_text(const struct _text_object_ *pThis, const WB_RECT *pRct);
132 static int __internal_get_row(const struct _text_object_ *pThis);
133 static void __internal_set_row(struct _text_object_ *pThis, int iRow);
134 static int __internal_get_col(const struct _text_object_ *pThis);
135 static void __internal_set_col(struct _text_object_ *pThis, int iCol);
136 static void __internal_del_select(struct _text_object_ *pThis);
137 static void __internal_replace_select(struct _text_object_ *pThis, const char *szText, unsigned long cbLen);
138 static void __internal_del_chars(struct _text_object_ *pThis, int nChar);
139 static void __internal_ins_chars(struct _text_object_ *pThis, const char *pChar, int nChar);
140 static void __internal_indent(struct _text_object_ *pThis, int nCol);
141 static int __internal_can_undo(struct _text_object_ *pThis);
142 static void __internal_undo(struct _text_object_ *pThis);
143 static int __internal_can_redo(struct _text_object_ *pThis);
144 static void __internal_redo(struct _text_object_ *pThis);
145 static void __internal_get_view(const struct _text_object_ *pThis, WB_RECT *pRct);
146 static void __internal_set_view(struct _text_object_ *pThis, const WB_RECT *pRct);
147 static void __internal_begin_highlight(struct _text_object_ *pThis);
148 static void __internal_end_highlight(struct _text_object_ *pThis);
149 
150 static void __internal_mouse_click(struct _text_object_ *pThis, int iMouseXDelta, int iMouseYDelta, int iType, int iACS);
151 static void __internal_begin_mouse_drag(struct _text_object_ *pThis);
152 static void __internal_end_mouse_drag(struct _text_object_ *pThis);
153 static void __internal_cursor_up(struct _text_object_ *pThis);
154 static void __internal_cursor_down(struct _text_object_ *pThis);
155 static void __internal_cursor_left(struct _text_object_ *pThis);
156 static void __internal_cursor_right(struct _text_object_ *pThis);
157 static void __internal_page_up(struct _text_object_ *pThis);
158 static void __internal_page_down(struct _text_object_ *pThis);
159 static void __internal_page_left(struct _text_object_ *pThis);
160 static void __internal_page_right(struct _text_object_ *pThis);
161 
162 static void __internal_cursor_home(struct _text_object_ *pThis);
163 static void __internal_cursor_end(struct _text_object_ *pThis);
164 static void __internal_cursor_top(struct _text_object_ *pThis);
165 static void __internal_cursor_bottom(struct _text_object_ *pThis);
166 
167 static void __internal_do_expose(struct _text_object_ *pThis, Display *pDisplay, Window wID,
168  GC gc, const WB_GEOM *pPaintGeom, const WB_GEOM *pViewGeom,
169  XFontSet rFontSet);
170 static void __internal_cursor_blink(struct _text_object_ *pThis, int bHasFocus);
171 
172 // *********************************
173 // LOCALLY DEFINED GLOBAL STRUCTURES
174 // *********************************
175 
176 const TEXT_OBJECT_VTABLE WBDefaultTextObjectVTable =
177 {
178  __internal_destroy,
179  __internal_init,
180  __internal_highlight_colors,
181  __internal_get_text,
182  __internal_set_text,
183  __internal_get_rows,
184  __internal_get_cols,
185  __internal_get_filetype,
186  __internal_set_filetype,
187  __internal_get_linefeed,
188  __internal_set_linefeed,
189  __internal_get_insmode,
190  __internal_set_insmode,
191  __internal_get_selmode,
192  __internal_set_selmode,
193  __internal_get_tab,
194  __internal_set_tab,
195  __internal_get_scrollmode,
196  __internal_set_scrollmode,
197  __internal_get_select,
198  __internal_set_select,
199  __internal_has_select,
200  __internal_get_sel_text,
201  __internal_get_row,
202  __internal_set_row,
203  __internal_get_col,
204  __internal_set_col,
205  __internal_del_select,
206  __internal_replace_select,
207  __internal_del_chars,
208  __internal_ins_chars,
209  __internal_indent,
210  __internal_can_undo,
211  __internal_undo,
212  __internal_can_redo,
213  __internal_redo,
214  __internal_get_view,
215  __internal_set_view,
216  __internal_begin_highlight,
217  __internal_end_highlight,
218  __internal_mouse_click,
219  __internal_begin_mouse_drag,
220  __internal_end_mouse_drag,
221  __internal_cursor_up,
222  __internal_cursor_down,
223  __internal_cursor_left,
224  __internal_cursor_right,
225  __internal_page_up,
226  __internal_page_down,
227  __internal_page_left,
228  __internal_page_right,
229 
230  __internal_cursor_home,
231  __internal_cursor_end,
232  __internal_cursor_top,
233  __internal_cursor_bottom,
234 
235  __internal_do_expose,
236  __internal_cursor_blink
237 };
238 
239 
240 
242 // GLOBAL (yet static) INLINE UTILITIES
244 
245 // line endings translated from 'enum' to 'const char *'
246 
247 static __inline__ const char * __internal_get_line_ending_text(enum _LineFeed_ iIndex)
248 {
249  static const char * const szLineEndings[LineFeed_ENTRYCOUNT] =
250  {
251 #ifdef WIN32
252  "\r\n"
253 #else // POSIX
254  "\n",
255 #endif // OS-dependent line endings
256  "\n","\r","\r\n","\n\r"
257  };
258 
259  if((int)iIndex < 0 || (int)iIndex >= LineFeed_ENTRYCOUNT)
260  {
261  return NULL; // single-line or invalid
262  }
263  return szLineEndings[iIndex];
264 }
265 
266 
267 
268 
269 
270 // ***********
271 // TEXT BUFFER
272 // ***********
273 
274 // TODO: an API function for single-line text
275 
276 #define DEFAULT_TEXT_BUFFER_LINES 16384
277 
278 TEXT_BUFFER * WBAllocTextBuffer(const char *pBuf, unsigned int cbBufSize)
279 {
280 TEXT_BUFFER *pRval;
281 int nLines = 0;
282 int cbLen;
283 
284  if(pBuf && (cbBufSize || *pBuf))
285  {
286  nLines = WBStringLineCount(pBuf, cbBufSize);
287  }
288 
289  if(nLines < DEFAULT_TEXT_BUFFER_LINES)
290  {
291  nLines = DEFAULT_TEXT_BUFFER_LINES;
292  }
293 
294  cbLen = sizeof(*pRval) + nLines * sizeof(pRval->aLines[0]);
295  pRval = WBAlloc(cbLen);
296 
297  if(!pRval)
298  {
299  WB_ERROR_PRINT("ERROR - %s - not enough memory (%ld)\n", __FUNCTION__,
300  (unsigned long)(sizeof(*pRval) + nLines * sizeof(pRval->aLines[0])));
301  return NULL;
302  }
303 
304  memset(pRval, 0, cbLen); // zero out entire structure (always)
305 
306  pRval->nArraySize = nLines; // pre-assigned values
307 // pRval->nEntries = 0; already zero, comment left for reference
308 
309  if(pBuf && (cbBufSize || *pBuf))
310  {
311  int nL = 0;
312  const char *p1;
313 
314  if(!cbBufSize)
315  {
316  cbBufSize = strlen(pBuf);
317  }
318 
319  do
320  {
321  int cbLen;
322  char *p2, *p3;
323 
324  p1 = WBStringNextLine(pBuf, &cbBufSize);
325 
326  if(p1) // another line remains
327  {
328  cbLen = p1 - pBuf;
329  }
330  else
331  {
332  cbLen = cbBufSize;
333  }
334 
335  p2 = WBAlloc(cbLen + 2);
336 
337  if(!p2)
338  {
339  WBFreeTextBuffer(pRval);
340  pRval = NULL;
341  break;
342  }
343 
344  memcpy(p2, pBuf, cbLen); // copy the data
345 
346  p3 = p2 + cbLen; // the end of the string
347  *p3 = 0; // always zero-byte terminate it first
348 
349  while(p3 > p2 && (*(p3 - 1) <= ' ' || *(p3 - 1) == HARD_TAB_CHAR)) // trim ALL trailing white space including CR, LF, tab, space, FF, etc.
350  {
351  // TODO: handle <FF> or <VT> differently?
352  // TODO: leave white space to mark 'extent' of line? naaw, probably not
353 
354  *(p3--) = 0; // for now just trim it all
355  }
356 
357  pRval->aLines[nL++] = p2;
358 
359  pBuf = p1;
360 
361  } while(pBuf && cbBufSize && nL < nLines);
362 
363  pRval->nEntries = nL; // NOTE: on error, this will be needed for cleanup
364 
366  }
367 
368 // if(pRval)
369 // {
370 // int nL;
371 //
372 // WB_ERROR_PRINT("\nTEMPORARY - %s - %ld lines\n", __FUNCTION__, pRval->nEntries);
373 //
374 // for(nL=0; nL < pRval->nEntries; nL++)
375 // {
376 // WB_ERROR_PRINT(" \"%s\"\n", pRval->aLines[nL]);
377 // }
378 //
379 // WB_ERROR_PRINT("END TEMPORARY - %s\n\n", __FUNCTION__);
380 // }
381 
382  return pRval;
383 }
384 
385 int WBCheckReAllocTextBuffer(TEXT_BUFFER **ppBuf, int nLinesToAdd)
386 {
387 TEXT_BUFFER *pBuf;
388 int nNew;
389 
390  if(!ppBuf || !*ppBuf)
391  {
392  return -1; // error
393  }
394 
395  // TODO: parameter validation
396 
397  pBuf = *ppBuf;
398 
399  nNew = pBuf->nEntries + nLinesToAdd;
400 
401  if(nNew > pBuf->nArraySize)
402  {
403  nNew += DEFAULT_TEXT_BUFFER_LINES;
404  nNew -= (nNew % (DEFAULT_TEXT_BUFFER_LINES / 2));
405 
406  pBuf = WBReAlloc(pBuf, sizeof(*pBuf) + nNew * sizeof(pBuf->aLines[0]));
407 
408  if(!pBuf)
409  {
410  return -1; // error
411  }
412 
413  pBuf->nArraySize = nNew;
414  *ppBuf = pBuf; // potentially new pointer - probably is, though
415  }
416 
417  return 0; // no problems
418 }
419 
421 {
422 int i1;
423 
424  if(!pBuf)
425  {
426  return;
427  }
428 
429  // TODO: parameter validation
430 
431  for(i1=0; i1 < pBuf->nEntries && i1 < pBuf->nArraySize; i1++)
432  {
433  if(pBuf->aLines[i1])
434  {
435  WBFree(pBuf->aLines[i1]);
436  pBuf->aLines[i1] = NULL; // by convention
437  }
438  }
439 
440  WBFree(pBuf);
441 }
442 
443 void WBTextBufferLineChange(TEXT_BUFFER *pBuf, unsigned long nLine, int nNewLen)
444 {
445 int i1, i2;
446 unsigned int nNewMax = 0, nNewMinMax = UINT_MAX;
447 
448 
449  if(pBuf->nEntries <= 1) // zero or one lines?
450  {
451  WBTextBufferRefreshCache(pBuf); // always do it THIS way
452  return;
453  }
454 
455  if(nLine > pBuf->nEntries) // line number isn't sane?
456  {
457  return; // sanity check failed (do nothing)
458  }
459 
460  // update the line info in my cache. If this line appears, remove it (always).
461  // If I'm deleting the line, subtract one from every line higher than this one.
462 
463  for(i1=0; i1 < TEXT_BUFFER_LINE_CACHE_SIZE && pBuf->aLineCacheLen[i1]; i1++)
464  {
465  if(pBuf->aLineCache[i1] == nLine)
466  {
467  // move everything up, remove the last entry, decrement i1
468  for(i2=i1 + 1; i2 < TEXT_BUFFER_LINE_CACHE_SIZE && pBuf->aLineCacheLen[i2]; i2++)
469  {
470  pBuf->aLineCache[i2] = pBuf->aLineCache[i2 + 1];
471  pBuf->aLineCacheLen[i2] = pBuf->aLineCacheLen[i2 + 1];
472 
473  // now by convention mark that last entry as if it's unused
474  pBuf->aLineCache[i2 + 1] = 0;
475  pBuf->aLineCacheLen[i2 + 1] = 0; // an 'end' marker (if the loop exits, it becomes the new 'end')
476  }
477 
478  i1--; // so I do the 'next' one as I should
479  }
480  else
481  {
482  if(pBuf->aLineCache[i1] > nLine)
483  {
484  if(nNewLen < 0) // a line deletion
485  {
486  pBuf->aLineCache[i1] --; // line was removed, so new line count must match
487  }
488  }
489 
490  if(nNewMax < pBuf->aLineCacheLen[i1])
491  {
492  nNewMax = pBuf->aLineCacheLen[i1]; // done this way as sanity check
493  }
494 
495  if(nNewMinMax > pBuf->aLineCacheLen[i1])
496  {
497  nNewMinMax = pBuf->aLineCacheLen[i1]; // done this way as sanity check
498  }
499  }
500  }
501 
502  // if the cache is NOW empty, re-evaluate it.
503  if(!pBuf->aLineCacheLen[0]) // empty
504  {
506  return;
507  }
508 
509  // I've re-evaluated the max and min-max from the cache, so use that info
510 
511  pBuf->nMaxCol = nNewMax;
512 
513  if(nNewMinMax <= nNewMax) // NOTE: if cache is empty, nNewMinMax will be UINT_MAX
514  {
515  pBuf->nMinMaxCol = nNewMinMax; // sanity checked value
516  }
517  else
518  {
519  pBuf->nMinMaxCol = nNewMax; // don't put insane values in there (just in case)
520  // TODO: consider refactoring this code out once the algorithm is proved to work
521  }
522 
523  if(nNewLen < 0) // a line deletion
524  {
525  if(!nNewMax) // in case my cache has dwindled to nothing
526  {
527  WBTextBufferRefreshCache(pBuf); // time to re-evaluate
528  }
529 
530  return; // I'm done for delete. The rest is for updates
531  }
532 
533  // see if the length exceeds any of the maximums, and do an 'insertion sort'
534  // into my cache array if it does. Otherwise I can just leave.
535 
536  if(nNewLen >= pBuf->nMaxCol) // bigger than maximum (or equal to it)
537  {
538  i2 = 0; // my insert point
539 
540  pBuf->nMaxCol = nNewLen; // new 'max'
541  if(nNewLen < nNewMinMax) // sanity test for empty cache
542  {
543  pBuf->nMinMaxCol = nNewLen; // may happen, if new cache is totally empty
544  }
545  }
546  else if(nNewLen >= pBuf->nMinMaxCol) // between min max and max
547  {
548  // find the place to insert this line into the cache
549  i2 = 0; // NOTE: platform-independent loop structure, probably not necessary, but...
550  while(i2 < TEXT_BUFFER_LINE_CACHE_SIZE && pBuf->aLineCacheLen[i2])
551  {
552  if(pBuf->aLineCacheLen[i2] < nNewLen) // the insert point
553  {
554  break;
555  }
556 
557  i2++; // don't forget this or infinite loop will result, heh
558  }
559  }
560  else
561  {
562  return; // I'm done - no need to re-insert this line into the cache (yet)
563  // NOTE: re-inserting the line into the cache would require re-doing the
564  // cache information from scratch. this is an optimization, so I'll
565  // avoid THAT operation until it's truly necessary.
566  }
567 
568  if(i2 < TEXT_BUFFER_LINE_CACHE_SIZE) // sanity check, might happen
569  {
570  // insert at 'i2'
571  for(i1=TEXT_BUFFER_LINE_CACHE_SIZE - 1; i1 > i2; i1--)
572  {
573  pBuf->aLineCache[i1] = pBuf->aLineCache[i1 - 1];
574  pBuf->aLineCacheLen[i1] = pBuf->aLineCacheLen[i1 - 1];
575  }
576 
577  pBuf->aLineCache[i2] = nLine;
578  pBuf->aLineCacheLen[i2] = nNewLen;
579  }
580 
581  // everything should be ok now, unless I b0rked something
582 }
583 
585 {
586 int iLine, i1, i2, i3;
587 char *p1;
588 
589 
590  // zero out the cache arrays
591  memset(pBuf->aLineCache, 0, sizeof(pBuf->aLineCache));
592  memset(pBuf->aLineCacheLen, 0, sizeof(pBuf->aLineCacheLen));
593 
594  pBuf->nMaxCol = pBuf->nMinMaxCol = 0; // initialize to zero
595 
596  if(!pBuf->nEntries)
597  {
598  return;
599  }
600  else if(pBuf->nEntries == 1)
601  {
602  if(pBuf->aLines[0])
603  {
604  pBuf->aLineCacheLen[0] = // refactored, do this assignment here instead
605  pBuf->nMaxCol = pBuf->nMinMaxCol = WBGetMBLength(pBuf->aLines[0]); // assign length to 'max' and 'minmax'
606 // if(pBuf->nMaxCol) 'refactored out' (left to document process, remove later)
607 // {
608 // pBuf->aLineCache[0] = 0; this value already assigned by 'memset'
609 // pBuf->aLineCacheLen[0] = pBuf->nMaxCol; refactored code does this
610 // }
611  }
612 
613  return;
614  }
615 
616  // the big loop
617 
618  for(iLine=0; iLine < pBuf->nEntries; i1++)
619  {
620  // do not consider blank lines or lines with zero length
621  p1 = pBuf->aLines[iLine];
622 
623  if(!p1)
624  {
625  continue;
626  }
627 
628  i1 = WBGetMBLength(p1);
629 
630  if(!i1)
631  {
632  continue;
633  }
634 
635  if(i1 > pBuf->nMinMaxCol) // assume data is consistent
636  {
637  // perform an insertion sort into 'aLineCache'
638 
639  // step 1: find the insertion point
640 
641  i2 = 0; // NOTE: platform-independent loop structure, probably not necessary, but...
642  while(i2 < TEXT_BUFFER_LINE_CACHE_SIZE && pBuf->aLineCacheLen[i2])
643  {
644  if(pBuf->aLineCacheLen[i2] < i1) // the insert point
645  {
646  break;
647  }
648 
649  i2++; // don't forget this or infinite loop will result, heh
650  }
651 
652  if(i2 >= TEXT_BUFFER_LINE_CACHE_SIZE) // logic fail, clean it up
653  {
654  pBuf->nMaxCol = pBuf->aLineCacheLen[0]; // forced re-evaluation
655  pBuf->nMinMaxCol = pBuf->aLineCacheLen[TEXT_BUFFER_LINE_CACHE_SIZE - 1];
656  }
657  else if(i2 == TEXT_BUFFER_LINE_CACHE_SIZE - 1)
658  {
659  // special case, insert at the end
660  pBuf->aLineCache[i2] = iLine;
661  pBuf->aLineCacheLen[i2] = i1;
662 
663  pBuf->nMinMaxCol = i1; // the new 'min max'
664  }
665  else
666  {
667  // if this is the first line in the cache, update 'nMaxCol'
668  if(!i2) // NOTE: this test is done here 'cause of likely register optimization
669  {
670  pBuf->nMaxCol = i1;
671  }
672 
673  i3 = i2 + 1;
674  while(i3 < TEXT_BUFFER_LINE_CACHE_SIZE && pBuf->aLineCacheLen[i3])
675  {
676  i3++; // finding the end
677  }
678 
679  // move everything down using 'memmove'
680 
681  memmove(&(pBuf->aLineCache[i2 + 1]), &(pBuf->aLineCache[i2]),
682  (i3 - i2 - 1) * sizeof(pBuf->aLineCache[0]));
683  memmove(&(pBuf->aLineCacheLen[i2 + 1]), &(pBuf->aLineCacheLen[i2]),
684  (i3 - i2 - 1) * sizeof(pBuf->aLineCacheLen[0]));
685 
686  // use the final line's index and re-assign the 'max' and 'min max'
687  pBuf->nMinMaxCol = pBuf->aLineCacheLen[i3 - 1];
688 
689  // make sure the last element(s) have a zero length (TODO: optimize?)
690  while(i3 < TEXT_BUFFER_LINE_CACHE_SIZE)
691  {
692  pBuf->aLineCache[i3] = 0; // do this to, just because
693  pBuf->aLineCacheLen[i3++] = 0;
694  }
695 
696  // and finally, insert this line's info at position 'i2'
697 
698  pBuf->aLineCache[i2] = iLine;
699  pBuf->aLineCacheLen[i2] = i1;
700  }
701  }
702  }
703 }
704 
705 
706 // ************************
707 // TEXT OBJECT CONSTRUCTION
708 // ************************
709 
710 TEXT_OBJECT *WBTextObjectConstructor(unsigned long cbStructSize, const char *szText, unsigned long cbLen, Window wIDOwner)
711 {
712 TEXT_OBJECT *pRval;
713 
714  pRval = (TEXT_OBJECT *)WBAlloc(sizeof(*pRval));
715 
716  if(pRval)
717  {
718 // pRval->vtable = &WBDefaultTextObjectVTable;
719 // pRval->ulTag = TEXT_OBJECT_TAG;
720 // pRval->vtable->init(pRval);
721 //
722 // pRval->wIDOwner = wIDOwner;
723 
724  WBInitializeInPlaceTextObject(pRval, wIDOwner);
725 
726  pRval->pText = WBAllocTextBuffer(szText, cbLen);
727  }
728 
729  return(pRval);
730 }
731 
733 {
734  if(WBIsValidTextObject(pObj))
735  {
736  pObj->vtable->destroy(pObj);
737 
738  WBFree(pObj);
739  }
740 }
741 
742 int WBTextObjectCalculateLineHeight(int iAscent, int iDescent) // consistently calculate line height from font ascent/descent
743 {
744 int iFontHeight;
745 
746  iFontHeight = iAscent + iDescent;
747 
748  // adjust font height to include line spacing (I'll use this to position the lines)
749  if(iDescent > MIN_LINE_SPACING / 2)
750  {
751  iFontHeight += iDescent / 2; // line spacing is 1/2 of descent, or MIN_LINE_SPACING
752  }
753  else
754  {
755  iFontHeight += MIN_LINE_SPACING;
756  }
757 
758  return iFontHeight;
759 }
760 
761 
762 
763 // *****************************
764 // TEXT OBJECT UTILITY FUNCTIONS
765 // *****************************
766 
767 // INTERNAL-ONLY utilities that are NOT part of the vtable
768 
769 static void __internal_free_undo_redo_buffer(void *pBuffer)
770 {
771  if(pBuffer)
772  {
773  // TODO: validate that it's an undo/redo buffer
774 
775  struct __internal_undo_redo_buffer *pU = (struct __internal_undo_redo_buffer *)pBuffer;
776 
777  do
778  {
779  struct __internal_undo_redo_buffer *pUsa = pU;
780 
781  pU = pU->pNext;
782  WBFree(pUsa); // for now just do this
783 
784  } while(pU);
785  }
786 }
787 
788 #define UNDO_LIMIT 256
789 
790 // NULL 'prctStartSel' or 'prctEndSel' implies 'NONE' selected, i.e. {0,0,0,0}
791 static void __internal_add_undo(struct _text_object_ *pThis, int iOperation, int iSelMode,
792  int iStartRow, int iStartCol, const WB_RECT *prctStartSel,
793  const char *pStartText, int cbStartText,
794  int iEndRow, int iEndCol, const WB_RECT *prctEndSel,
795  const char *pEndText, int cbEndText)
796 {
797 int cbLen, cbLen2, i1;
798 struct __internal_undo_redo_buffer *pUndo, *pTU, *pTU2;
799 
800 
801  if(!WBIsValidTextObject(pThis))
802  {
803  return;
804  }
805 
806  pThis->iBlinkState = 0; // this affects the cursor blink, basically resetting it whenever I edit something
807 
808  cbLen = cbLen2 = 0;
809 
810  if(pStartText)
811  {
812  if(cbStartText < 0)
813  {
814  cbStartText = strlen(pStartText); // TODO: WBGetMBLength ?
815  }
816 
817  if(cbStartText > 0)
818  {
819  cbLen += cbStartText;
820  }
821  }
822 
823  if(pEndText)
824  {
825  if(cbEndText < 0)
826  {
827  cbEndText = strlen(pEndText); // TODO: WBGetMBLength?
828  }
829 
830  if(cbEndText > 0)
831  {
832  cbLen2 += cbEndText;
833  }
834  }
835 
836  pUndo = (struct __internal_undo_redo_buffer *)WBAlloc(cbLen + cbLen2 + 4 + sizeof(*pUndo));
837  if(!pUndo)
838  {
839  // TODO: walk the 'undo' chain and start removing stuff, and retry the alloc
840 
841  WB_ERROR_PRINT("ERROR - %s - unable to create undo buffer, errno=%d\n", __FUNCTION__, errno);
842  return;
843  }
844 
845  pUndo->iOperation = iOperation;
846  pUndo->iSelMode = iSelMode;
847 
848  if(prctStartSel)
849  {
850  memcpy(&(pUndo->rctSelOld), prctStartSel, sizeof(WB_RECT));
851  }
852  else
853  {
854  memset(&(pUndo->rctSelOld), 0, sizeof(WB_RECT));
855  }
856 
857  if(prctEndSel)
858  {
859  memcpy(&(pUndo->rctSelNew), prctEndSel, sizeof(WB_RECT));
860  }
861  else
862  {
863  memset(&(pUndo->rctSelNew), 0, sizeof(WB_RECT));
864  }
865 
866  // this code should work on multi-byte characters as well...
867 
868  pUndo->nOld = cbLen;
869  if(cbLen)
870  {
871  memcpy(pUndo->aData, pStartText, cbLen);
872  pUndo->aData[cbLen++] = 0; // just because
873  }
874 
875  pUndo->nOld = cbLen;
876 
877  if(cbLen2)
878  {
879  memcpy(pUndo->aData + cbLen, pEndText, cbLen2);
880  pUndo->aData[cbLen + cbLen2++] = 0; // just because
881  }
882 
883  pUndo->nNew = cbLen2;
884 
885  // insert undo buffer into the 'chain'
886 
887  pTU = pUndo->pNext = (struct __internal_undo_redo_buffer *)(pThis->pUndo);
888  pThis->pUndo = pUndo;
889 
890  // NOW walk the chain and remove things past a certain point. For now, UNDO_LIMIT
891  // NOTE: this is a compromise - the alternative is a double-link list and an 'end' pointer
892  // along with a running count of items. If the number of undo/redo items becomes
893  // VERY large, that might be a better alternative. So for now I use a single-link list.
894 
895  if(pTU)
896  {
897  for(i1=1; i1 < UNDO_LIMIT && pTU->pNext; pTU = pTU->pNext, i1++) { }
898  // on a modern CPU, this shouldn't take more than a few microseconds
899 
900  if(pTU->pNext) // will only be true if I have too many entries
901  {
902  // free and unhook the chain
903  pTU2 = pTU->pNext;
904  pTU->pNext = NULL; // generally safer THIS way
905 
906  __internal_free_undo_redo_buffer(pTU2); // NOW free what was once chained from here
907  }
908  }
909 
910  // whenever I add an 'undo' HERE, I screw up the 'redo' so blast it away if it exists
911  if(pThis->pRedo)
912  {
913  pTU = pThis->pRedo;
914  pThis->pRedo = NULL;
915 
916  __internal_free_undo_redo_buffer(pTU);
917  }
918 }
919 
920 
921 #if 0 /* not currently in use - uncomment to implement its functionality */
922 static void __internal_add_redo(struct _text_object_ *pThis, struct __internal_undo_redo_buffer *pUndo)
923 {
924 struct __internal_undo_redo_buffer *pRedo;
925 int cbLen;
926 
927 
928  if(!WBIsValidTextObject(pThis))
929  {
930  return;
931  }
932 
933  // convert an 'undo' into a 'redo' and add it to the 'redo' chain
934  // essentially 'just a copy'
935 
936  cbLen = pUndo->nOld + pUndo->nNew + sizeof(*pUndo);
937 
938  pRedo = (struct __internal_undo_redo_buffer *)WBAlloc(cbLen + 2);
939 
940  if(!pRedo)
941  {
942  // TODO: walk the 'undo' and 'redo' chains and start removing stuff, and retry the alloc
943 
944  WB_ERROR_PRINT("ERROR - %s - unable to create redo buffer, errno=%d\n", __FUNCTION__, errno);
945  return;
946  }
947 
948  memcpy(pRedo, pUndo, cbLen);
949 
950  // now add it to the redo chain
951 
952  pRedo->pNext = (struct __internal_undo_redo_buffer *)(pThis->pRedo);
953  pThis->pRedo = pRedo;
954 }
955 #endif // 0
956 
957 #if 0 /* not currently in use - uncomment to implement its functionality */
958 static void __internal_perform_undo(struct _text_object_ *pThis, struct __internal_undo_redo_buffer *pUndo)
959 {
960 struct __internal_undo_redo_buffer *pDo = NULL;
961 int iOldSel;
962 
963 
964  pDo = pDo; // TEMPORARY, warning avoidance
965 
966  if(!WBIsValidTextObject(pThis) || !pUndo)
967  {
968  return;
969  }
970 
971  // first, make sure the selection mode is correct
972 
973  iOldSel = pThis->iSelMode;
974  pThis->iSelMode = pUndo->iSelMode;
975 
976  if(pUndo->iOperation == UNDO_SELECT)
977  {
978  }
979  else if(pUndo->iOperation == UNDO_DELETE)
980  {
981  // perform an insert of 'Old Text' data at the appropriate cursor spot
982  }
983  else if(pUndo->iOperation == UNDO_INSERT)
984  {
985  // perform a delete of 'new text' data at the appropriate cursor spot
986  }
987  else if(pUndo->iOperation == UNDO_REPLACE)
988  {
989  // replace 'new text' with 'old text' at the appropriate cursor spot
990  }
991  else if(pUndo->iOperation == UNDO_INDENT)
992  {
993  // indents a block of text
994  // to indent, start col will be 0, end col will be the indent
995  // to un-indent, start col will be > 0, end col will be 0
996  // The start/end rows define the block of lines to be indented or un-indented
997 
998  }
999 
1000  // re-select the old selection using the old select method
1001 
1002 
1003  // restore cursor to appropriate spot
1004 
1005 
1006 // // restore the current selection method if restored selection is empty
1007 // pThis->iSelMode = iOldSel;
1008 
1009 
1010  // add a redo buffer that's a copy of the undo operation
1011  __internal_add_redo(pThis, pUndo);
1012 }
1013 #endif // 0
1014 
1015 #if 0 /* not currently in use - uncomment to implement its functionality */
1016 static void __internal_perform_redo(struct _text_object_ *pThis, struct __internal_undo_redo_buffer *pRedo)
1017 {
1018 struct __internal_undo_redo_buffer *pNewUndo;
1019 int iOldSel, cbLen;
1020 
1021 
1022  if(!WBIsValidTextObject(pThis) || !pRedo)
1023  {
1024  return;
1025  }
1026 
1027  cbLen = pRedo->nOld + pRedo->nNew + sizeof(*pRedo);
1028 
1029  // first, make sure the selection mode is correct
1030 
1031  iOldSel = pThis->iSelMode;
1032  pThis->iSelMode = pRedo->iSelMode;
1033 
1034  if(pRedo->iOperation == UNDO_SELECT)
1035  {
1036  }
1037  else if(pRedo->iOperation == UNDO_DELETE)
1038  {
1039  // perform an insert of 'new Text' data at the appropriate cursor spot
1040  }
1041  else if(pRedo->iOperation == UNDO_INSERT)
1042  {
1043  // perform a delete of 'old text' data at the appropriate cursor spot
1044  }
1045  else if(pRedo->iOperation == UNDO_REPLACE)
1046  {
1047  // replace 'old text' with 'new text' at the appropriate cursor spot
1048  }
1049  else if(pRedo->iOperation == UNDO_INDENT)
1050  {
1051  // indents a block of text
1052  // to indent, start col will be 0, end col will be the indent
1053  // to un-indent, start col will be > 0, end col will be 0
1054  // The start/end rows define the block of lines to be indented or un-indented
1055 
1056  }
1057 
1058  // re-select the new selection using the applicable select method
1059 
1060 
1061  // restore cursor to appropriate spot
1062 
1063 
1064 // // restore the current selection method if restored selection is empty
1065 // pThis->iSelMode = iOldSel;
1066 
1067 
1068  // now I must add the 'redo' operation 'as-is' to the 'undo' chain, but NOT blast away the 'redo' chain
1069  // I'll make a copy of it first, since the caller will need to manage the undo/redo pointers
1070 
1071  pNewUndo = (struct __internal_undo_redo_buffer *)WBAlloc(cbLen + 2);
1072 
1073  if(!pNewUndo)
1074  {
1075  // TODO: walk the 'undo' and 'redo' chains and start removing stuff, and retry the alloc
1076 
1077  WB_ERROR_PRINT("ERROR - %s - unable to create undo buffer from redo buffer, errno=%d\n", __FUNCTION__, errno);
1078  return;
1079  }
1080 
1081  memcpy(pNewUndo, pRedo, cbLen);
1082 
1083  // now add it to the undo chain so I can 'undo the re-do' if I want to
1084 
1085  pNewUndo->pNext = (struct __internal_undo_redo_buffer *)(pThis->pUndo);
1086  pThis->pUndo = pNewUndo;
1087 }
1088 #endif // 0
1089 
1090 // ---------------------------------------------------------------------------
1091 // __internal_get_selected_text - arbitrary text retrieval (internal only)
1092 // returns WBAlloc'd string pointer
1093 //
1094 // iRow is the starting row position
1095 // iCol is the starting column position
1096 // iEndRow is the ending row position. In modes OTHER than 'box mode', this may equal 'iCol'
1097 // iEndCol is the ending column position. In modes OTHER than 'box mode' this may be LESS than 'iCol'
1098 static char * __internal_get_selected_text(const struct _text_object_ *pThis, int iRow, int iCol, int iEndRow, int iEndCol)
1099 {
1100 int i1, i2, i3, cb1, cbLF=0;
1101 char *p1, *pRval = NULL;
1102 const char *szLineFeed = NULL;
1103 TEXT_BUFFER *pTB;
1104 int iIsBoxMode, iIsLineMode;
1105 
1106 
1107  if(WBIsValidTextObject(pThis))
1108  {
1109  if(!pThis->pText)
1110  {
1111  return WBCopyString(""); // return a blank string
1112  }
1113 
1114  if(iRow < 0)
1115  {
1116  iRow = 0;
1117  }
1118  if(iCol < 0)
1119  {
1120  iCol = 0;
1121  }
1122  if(iEndRow < 0)
1123  {
1124  iEndRow = INT_MAX;
1125  }
1126  if(iEndCol < 0)
1127  {
1128  iEndCol = INT_MAX;
1129  }
1130 
1131  if(iRow == iEndRow)
1132  {
1133  iIsBoxMode = iIsLineMode = 0;
1134  }
1135  else
1136  {
1137  if(pThis->iSelMode == SelectMode_BOX)
1138  {
1139  iIsBoxMode = 1;
1140  iIsLineMode = 0;
1141  }
1142  else if(pThis->iSelMode == SelectMode_LINE)
1143  {
1144  iIsBoxMode = 0;
1145  iIsLineMode = 1;
1146  }
1147  else // char and default
1148  {
1149  iIsBoxMode = 0;
1150  iIsLineMode = 0;
1151  }
1152  }
1153 
1154  if(iRow > iEndRow // row exceeds ending row
1155  || (iRow == iEndRow && iCol >= iEndCol) // single row with no 'width'
1156  /* || (box mode && iCol >= iEndCol) */) // box mode requires iCol < iEndCol
1157  {
1158  return WBCopyString(""); // return a blank string
1159  }
1160 
1161  pTB = (TEXT_BUFFER *)(pThis->pText);
1162 
1163  // NOTE: when iRow == iEndRow, both box mode and line mode revert to 'stream mode'
1164  // and the 'iIsBoxMode' and 'iIsStreamMode' flags will both be zero
1165 
1166  if(iIsBoxMode)
1167  {
1168  cb1 = (iEndRow - iRow + 1) * (2 + iEndCol - iCol);
1169  }
1170  else
1171  {
1172  // FOR NOW just go through the TEXT_BUFFER array, determine the length (later cache it)
1173 
1174  for(i1=iRow, cb1=4; i1 < pTB->nEntries && i1 <= iEndRow; i1++)
1175  {
1176  i2 = strlen(pTB->aLines[i1]); // use 'strlen' here, as I want the REAL length
1177 
1178  // last line in 'normal' select mode limits width - 'iEndCol' may be 0
1179  if(i1 == iEndRow // i.e. "the last line" in the selected area
1180  && !iIsLineMode) // NOT 'line mode' (will be always false for single-row selects)
1181  {
1182  if(i2 > iEndCol) // trim the indicated length of the line accordingly
1183  {
1184  i2 = WBGetMBCharPtr(pTB->aLines[i1], iEndCol, NULL) - pTB->aLines[i1];
1185  }
1186  }
1187 
1188  cb1 += i2 + 2; // do this regardless (it's only to count up max string length)
1189  }
1190  }
1191 
1192  // now build the string using the specified line ending and selection mode
1193 
1194  szLineFeed = __internal_get_line_ending_text(pThis->iLineFeed);
1195 
1196  if(!szLineFeed) // single-line
1197  {
1198  if(pTB->nEntries > 1)
1199  {
1200  szLineFeed = __internal_get_line_ending_text(LineFeed_DEFAULT); // fallback with multi-line data
1201  }
1202  }
1203 
1204  if(szLineFeed)
1205  {
1206  cbLF = strlen(szLineFeed);
1207  }
1208 
1209  pRval = WBAlloc(cb1); // allocate the buffer
1210 
1211  if(pRval)
1212  {
1213  p1 = pRval;
1214  for(i1=iRow, p1 = pRval; i1 < pTB->nEntries && i1 <= iEndRow; i1++)
1215  {
1216  char *pTheLine = pTB->aLines[i1];
1217 
1218  // TODO: validate pRval + cb1 > p1 + length of "the stuff that follows"
1219  i2 = i3 = strlen(pTheLine); // the TRUE 'binary' length
1220 
1221  // TODO: for box mode limit the width on all lines and pad with white space as needed
1222 
1223  // TODO: handle hard tab translation? For now "leave it". Later, I should use a character
1224  // that prints as 'white space' but translates as a 'hard tab'.
1225  // Example might be 0xA0, i.e. a ' ' + 80H, filling up spaces to the tab point. Delete
1226  // and insert would have to compensate for this. This function ALSO would have to compensate,
1227  // translating tabs to spaces for box mode, and to hard tabs for everything else when 'select all'
1228  // is not in effect and tabs are preserved.
1229  // For 'select all', hard tabs (when preserved) would be translated into a minimum number of
1230  // tab characters; otherwise they would translate into spaces. Also a hard tab should be displayed
1231  // differently (when preserved), such as a greyed |--> or similar.
1232  // When 'do not preserve tabs' is set, inserted tabs should automatically become spaces. ONLY when
1233  // the tabs are being preserved should the translation take place.
1234  //
1235  // CASE FOR USING 'A0' FOR HARD TAB
1236  // a) UTF8 does not use it as a marker for multi-byte character data - http://en.wikipedia.org/wiki/UTF-8
1237  // b) it's basically a ' ' with the high bit set
1238  // c) it's not used for anything significant in 8-bit ASCII other than "non-breaking space" (i.e. &nbsp;)
1239  // d) if it's imported, it will translate to actual spaces
1240  // e) if it's exported by accident, it probably won't matter (other than formatting)
1241  // f) a definition NOW exists - see HARD_TAB_CHAR (text_object.h)
1242 
1243  // last line in 'normal' select mode limits width - 'iEndCol' may be 0
1244  if(i1 == iEndRow // i.e. "the last line" in the selected area
1245  || iIsBoxMode) // box mode limits length differently on all lines
1246  {
1247  if(iIsBoxMode)
1248  {
1249  if(i2 >= iEndCol)
1250  {
1251  i2 = WBGetMBCharPtr(pTheLine, iEndCol, NULL) - WBGetMBCharPtr(pTheLine, iCol, NULL);
1252  }
1253  else if(iCol > 0)
1254  {
1255  i2 -= WBGetMBCharPtr(pTheLine, iCol, NULL) - pTheLine;
1256  }
1257  }
1258  else if(!iIsLineMode)
1259  {
1260  if(i2 >= iEndCol)
1261  {
1262  i2 = WBGetMBCharPtr(pTheLine, iEndCol, NULL) - pTheLine;
1263  }
1264  }
1265  else if(iEndCol == 0)
1266  {
1267  i2 = 0; // typically 'last row'
1268  }
1269  }
1270 
1271  // check for stream mode on the first line, and start with 'iCol'
1272  if(!iIsBoxMode && !iIsLineMode && i1 == iRow) // on first line, start at 'iCol'
1273  {
1274  i2 -= WBGetMBCharPtr(pTheLine, iCol, NULL) - pTheLine; // subtract starting column offset for total length
1275  }
1276 
1277  if(i2 > 0) // might be < 0 depending
1278  {
1279  if(iIsLineMode || i1 > iCol) // multi-line select NOT on first line, or line mode
1280  {
1281  memcpy(p1, pTheLine, i2);
1282  }
1283  else
1284  {
1285  memcpy(p1, WBGetMBCharPtr(pTheLine, iCol, NULL), i2);
1286  }
1287  p1 += i2;
1288  }
1289  else
1290  {
1291  i2 = 0; // for box mode, mostly
1292  }
1293 
1294  if(iIsBoxMode) // iRow < iEndRow also
1295  {
1296  // for box mode, pad any 'remaining' length with space
1297  i2 = iEndCol - iCol // the ending width we're SUPPOSED to have
1298  - WBGetMBLength(WBGetMBCharPtr(pTheLine, iCol, NULL)); // the actual length in 'columns' starting at 'iCol'
1299 
1300  // I need THAT MANY white spaces for box mode
1301  if(i2 > 0)
1302  {
1303  memset(p1, ' ', i2);
1304  p1 += i2; // now the width should be exactly 'iEndCol - iCol'
1305  }
1306  }
1307 
1308  // don't add final line feed in 'stream' mode or in 'line' mode if it ends on column 0
1309 
1310  if(iIsBoxMode || // box mode ALWAYS
1311  (iIsLineMode && iEndCol > 0) || // I'm in line mode and NOT ending on column 0 on the last line
1312  i1 < iEndRow) // not the last line
1313  {
1314  if(szLineFeed)
1315  {
1316  memcpy(p1, szLineFeed, cbLF);
1317  p1 += cbLF;
1318  }
1319  }
1320  }
1321 
1322  *p1 = 0;
1323  }
1324  }
1325 
1326  return pRval;
1327 }
1328 
1329 static void __internal_invalidate_cursor(const struct _text_object_ *pThis, int bPaintFlag)
1330 {
1331 WB_RECT rctCursor;
1332 
1333  if(WBIsValidTextObject(pThis) && pThis->wIDOwner != None)
1334  {
1335  rctCursor.left = pThis->iCursorX - 1;
1336  rctCursor.top = pThis->iCursorY - 1;
1337  rctCursor.bottom = rctCursor.top + pThis->iCursorHeight + 2;
1338 
1339  if(pThis->iInsMode == InsertMode_OVERWRITE)
1340  {
1341  rctCursor.right = rctCursor.left + pThis->iFontWidth;
1342  }
1343  else
1344  {
1345  rctCursor.right = rctCursor.left + 3;
1346  }
1347 
1348  WBInvalidateRect(pThis->wIDOwner, &rctCursor, bPaintFlag);
1349  }
1350 }
1351 
1352 static void __internal_invalidate_rect(const struct _text_object_ *pThis, WB_RECT *pRect, int bPaintFlag)
1353 {
1354 WB_RECT rctInvalid;
1355 
1356  if(WBIsValidTextObject(pThis) && pThis->wIDOwner != None)
1357  {
1358  // TODO: determine if scroll area changed. if not, don't do this next part
1359  {
1360  XClientMessageEvent evt;
1361 
1362  bzero(&evt, sizeof(evt));
1363  evt.type = ClientMessage;
1364  evt.display = WBGetWindowDisplay(pThis->wIDOwner);
1365  evt.window = pThis->wIDOwner;
1366  evt.message_type = aRECALC_LAYOUT;
1367  evt.format = 32;
1368 
1369  WBPostPriorityEvent(pThis->wIDOwner, (XEvent *)&evt); // asynch (for now)
1370  }
1371 
1372  if(!pRect)
1373  {
1374  WB_ERROR_PRINT("TEMPORARY: %s - invalidate entire window %u (%08xH)\n",
1375  __FUNCTION__, (int)pThis->wIDOwner, (int)pThis->wIDOwner);
1376 
1377  WBInvalidateRect(pThis->wIDOwner, NULL, bPaintFlag);
1378  return;
1379  }
1380 
1381  memcpy(&rctInvalid, pRect, sizeof(*pRect));
1382 
1383  if(pThis->rctWinView.left - MIN_BORDER_SPACING > rctInvalid.left)
1384  {
1385  rctInvalid.left = pThis->rctWinView.left - MIN_BORDER_SPACING;
1386  }
1387  if(pThis->rctWinView.right + MIN_BORDER_SPACING < rctInvalid.right)
1388  {
1389  rctInvalid.right = pThis->rctWinView.right + MIN_BORDER_SPACING;
1390  }
1391  if(pThis->rctWinView.top - MIN_BORDER_SPACING > rctInvalid.top)
1392  {
1393  rctInvalid.top = pThis->rctWinView.top - MIN_BORDER_SPACING;
1394  }
1395  if(pThis->rctWinView.bottom + MIN_BORDER_SPACING < rctInvalid.bottom)
1396  {
1397  rctInvalid.bottom = pThis->rctWinView.bottom + MIN_BORDER_SPACING;
1398  }
1399 
1400  WBInvalidateRect(pThis->wIDOwner, &rctInvalid, bPaintFlag);
1401 
1402 // WB_ERROR_PRINT("TEMPORARY: %s - invalidate %d,%d,%d,%d for %u (%08xH)\n",
1403 // __FUNCTION__,
1404 // rctInvalid.left, rctInvalid.top, rctInvalid.right, rctInvalid.bottom,
1405 // (int)pThis->wIDOwner, (int)pThis->wIDOwner);
1406 
1407  }
1408 // else
1409 // {
1410 // if(!WBIsValidTextObject(pThis))
1411 // {
1412 // WB_ERROR_PRINT("TEMPORARY: %s - invalid text object\n", __FUNCTION__);
1413 // }
1414 // else
1415 // {
1416 // WB_ERROR_PRINT("TEMPORARY: %s - owner window ID is 'None'\n", __FUNCTION__);
1417 // }
1418 // }
1419 }
1420 
1421 // NOTE: iStartRow and iStartCol may be 0 but not negative
1422 // iEndRow and iEndCol can be negative to indicate "all"
1423 static void __internal_calc_rect(const struct _text_object_ *pThis, WB_RECT *pRect, int iStartRow, int iStartCol, int iEndRow, int iEndCol)
1424 {
1425 int iFontHeight;
1426 TEXT_BUFFER *pBuf;
1427 
1428  if(!pRect || !WBIsValidTextObject(pThis))
1429  {
1430  if(pRect)
1431  {
1432  memset(pRect, 0, sizeof(*pRect));
1433  }
1434 
1435  return;
1436  }
1437 
1438  pBuf = (TEXT_BUFFER *)(pThis->pText); // might be NULL, must check before use
1439 
1440  if(iEndRow < 0)
1441  {
1442  if(pBuf && pThis->iLineFeed != LineFeed_NONE)
1443  {
1444  iEndRow = pBuf->nEntries;
1445  }
1446  else
1447  {
1448  iEndRow = 0;
1449  }
1450  }
1451 
1452  // examine the viewpoint rect, font height, and other info
1453 
1454  iFontHeight = pThis->iAsc + pThis->iDesc;
1455 
1456  if(pThis->iLineFeed == LineFeed_NONE) // i.e. SINGLE LINE (ignore row)
1457  {
1458  // top of line will always be centered
1459 
1460  pRect->top = pThis->rctWinView.top
1461  + (pThis->rctWinView.bottom - pThis->rctWinView.top
1462  - pThis->iAsc - pThis->iDesc)
1463  / 2;
1464 
1465  pRect->bottom = pRect->top + iFontHeight; // always
1466 
1467  pRect->left = pThis->rctWinView.left
1468  + pThis->iFontWidth * (iStartCol - pThis->rctView.left); // can be negative
1469 
1470  if(iEndCol < 0)
1471  {
1472  pRect->right = INT_MAX;
1473  }
1474  else
1475  {
1476  pRect->right = pThis->iFontWidth * (iEndCol - iStartCol + 1);
1477  }
1478  }
1479  else
1480  {
1481 // pThis->rctWinView.top; what did I intend to do here???
1482 
1483  if(iStartRow == iEndRow) // single row
1484  {
1485  pRect->left = pThis->rctWinView.left
1486  + pThis->iFontWidth * (iStartCol - pThis->rctView.left); // can be negative
1487 
1488  if(iEndCol < 0)
1489  {
1490  pRect->right = INT_MAX;
1491  }
1492  else
1493  {
1494  pRect->right = pThis->iFontWidth * (iEndCol - iStartCol + 1);
1495  }
1496 
1497  pRect->top = pThis->rctWinView.top; // already calculated from earlier
1498  pRect->bottom = pRect->top + iFontHeight; // always
1499  }
1500  else // multiple rows implies including the entire line unless ending column is 0 [in which case last line is excluded]
1501  {
1502  // ignore columns, use the entire viewport width
1503  pRect->left = pThis->rctWinView.left;
1504  pRect->right = INT_MAX; //pThis->rctWinView.right;
1505 
1506  iFontHeight = WBTextObjectCalculateLineHeight(pThis->iAsc, pThis->iDesc); // height PLUS inter-line spacing
1507 
1508  pRect->top = pThis->rctWinView.top
1509  + iFontHeight * (iStartRow - pThis->rctView.top);
1510 
1511  if(iEndCol > 0)
1512  {
1513  pRect->bottom = pThis->rctWinView.top
1514  + iFontHeight * (iEndRow + 1 - pThis->rctView.top);
1515  }
1516  else // nothing selected on last row
1517  {
1518  pRect->bottom = pThis->rctWinView.top
1519  + iFontHeight * (iEndRow - pThis->rctView.top);
1520  }
1521 
1522 // WB_ERROR_PRINT("TEMPORARY: %s - merged rect %d,%d,%d,%d\n", __FUNCTION__,
1523 // pRect->left, pRect->top, pRect->right, pRect->bottom);
1524  }
1525  }
1526 }
1527 
1528 static void __internal_merge_rect(const struct _text_object_ *pThis, WB_RECT *pRect, int iStartRow, int iStartCol, int iEndRow, int iEndCol)
1529 {
1530 WB_RECT rctMerge;
1531 
1532  if(!pRect || !WBIsValidTextObject(pThis))
1533  {
1534  if(pRect)
1535  {
1536  memset(pRect, 0, sizeof(*pRect));
1537  }
1538 
1539  return;
1540  }
1541 
1542  __internal_calc_rect(pThis, &rctMerge, iStartRow, iStartCol, iEndRow, iEndCol);
1543 
1544  if(rctMerge.left < pRect->left)
1545  {
1546  pRect->left = rctMerge.left;
1547  }
1548  if(rctMerge.top < pRect->top)
1549  {
1550  pRect->top = rctMerge.top;
1551  }
1552  if(rctMerge.right > pRect->right)
1553  {
1554  pRect->right = rctMerge.right;
1555  }
1556  if(rctMerge.bottom > pRect->bottom)
1557  {
1558  rctMerge.bottom = pRect->bottom;
1559  }
1560 }
1561 
1562 
1563 // ****************************
1564 // TEXT OBJECT VTABLE FUNCTIONS
1565 // ****************************
1566 
1567 static void __internal_destroy(struct _text_object_ *pThis)
1568 {
1569  if(WBIsValidTextObject(pThis))
1570  {
1571  // TODO: additional validation
1572 
1573  if(pThis->pText)
1574  {
1575  // TODO: validate pointer
1576  WBFreeTextBuffer((TEXT_BUFFER *)pThis->pText);
1577  pThis->pText = NULL;
1578  }
1579 
1580  // now for the undo/redo buffers
1581 
1582  __internal_free_undo_redo_buffer(pThis->pUndo);
1583  pThis->pUndo = NULL;
1584  __internal_free_undo_redo_buffer(pThis->pRedo);
1585  pThis->pRedo = NULL;
1586  }
1587 }
1588 
1589 static void __internal_init(struct _text_object_ *pThis)
1590 {
1591  pThis->ulTag = TEXT_OBJECT_TAG;
1592  pThis->wIDOwner = None;
1593 
1594  bzero(&pThis->rctSel, sizeof(pThis->rctSel));
1595  bzero(&pThis->rctHighLight, sizeof(pThis->rctHighLight));
1596 
1597  pThis->iFileType = 0; // plain text
1598  pThis->iLineFeed = LineFeed_DEFAULT;
1599  pThis->iInsMode = 1; // insert
1600  pThis->iSelMode = 0; // normal
1601  pThis->iScrollMode = 0; // normal
1602  pThis->iRow = 0;
1603  pThis->iCol = 0;
1604  pThis->iPos = 0; // reserved
1605  bzero(&pThis->rctView, sizeof(pThis->rctView));
1606  pThis->pText = NULL;
1607  pThis->pUndo = NULL;
1608  pThis->pRedo = NULL;
1609 
1610  // get initial default highlight colors
1611 
1612  {
1613  char szHFG[16], szHBG[16];
1614  Colormap colormap = DefaultColormap(WBGetDefaultDisplay(), DefaultScreen(WBGetDefaultDisplay()));
1615 
1616 #define LOAD_COLOR(X,Y,Z) if(CHGetResourceString(WBGetDefaultDisplay(), X, Y, sizeof(Y)) <= 0){ WB_WARN_PRINT("%s - WARNING: can't find color %s, using default value %s\n", __FUNCTION__, X, Z); strcpy(Y,Z); }
1617 
1618  LOAD_COLOR("selected_bg_color", szHBG, "#0040FF"); // a slightly greenish blue for the 'selected' BG color
1619  LOAD_COLOR("selected_fg_color", szHFG, "white"); // white FG when selected
1620 
1621 #undef LOAD_COLOR
1622 
1623  XParseColor(WBGetDefaultDisplay(), colormap, szHFG, &(pThis->clrHFG));
1624  XAllocColor(WBGetDefaultDisplay(), colormap, &(pThis->clrHFG)); // TODO: calculate missing pieces myself?
1625  XParseColor(WBGetDefaultDisplay(), colormap, szHBG, &(pThis->clrHBG));
1626  XAllocColor(WBGetDefaultDisplay(), colormap, &(pThis->clrHBG)); // TODO: calculate missing pieces myself?
1627  }
1628 
1629  // TODO: do I re-initialize the owner-maintained values? for now, NO!
1630 }
1631 
1632 static void __internal_highlight_colors(struct _text_object_ *pThis, XColor clrHFG, XColor clrHBG)
1633 {
1634  if(WBIsValidTextObject(pThis))
1635  {
1636  pThis->clrHFG = clrHFG;
1637  pThis->clrHBG = clrHBG;
1638  }
1639 }
1640 
1641 static char * __internal_get_text(struct _text_object_ *pThis)
1642 {
1643  return __internal_get_selected_text(pThis, -1, -1, -1, -1);
1644 }
1645 
1646 static void __internal_set_text(struct _text_object_ *pThis, const char *szText, unsigned long cbLen)
1647 {
1648 TEXT_BUFFER *pTemp;
1649 
1650  if(WBIsValidTextObject(pThis))
1651  {
1652  // for now, allocate a NEW text buffer and replace the old one with it
1653 
1654  pTemp = WBAllocTextBuffer(szText, cbLen);
1655 
1656  if(pTemp)
1657  {
1658  if(pThis->pText)
1659  {
1660  WBFreeTextBuffer(pThis->pText);
1661  }
1662 
1663  pThis->pText = pTemp; // and I'm spent
1664  }
1665  else
1666  {
1667  WB_ERROR_PRINT("TEXT OBJECT: %s - not enough memory for TEXT BUFFER errno=%d (no change)\n", __FUNCTION__, errno);
1668  }
1669  }
1670  else
1671  {
1672  WB_ERROR_PRINT("ERROR - %s - NOT a valid TEXT_OBJECT - %p\n", __FUNCTION__, pThis);
1673  }
1674 }
1675 
1676 static int __internal_get_rows(const struct _text_object_ *pThis)
1677 {
1678 TEXT_BUFFER *pBuf;
1679 
1680  if(WBIsValidTextObject(pThis))
1681  {
1682  pBuf = (TEXT_BUFFER *)(pThis->pText);
1683 
1684  if(pBuf && pBuf->nEntries > 0) // only if NOT empty
1685  {
1686  if(pThis->iLineFeed == LineFeed_NONE ||
1687  !pBuf->aLines[0] || !*(pBuf->aLines[0])) // strlen(pBuf->aLines[0])) // "blank line"
1688  {
1689  return 1; // for multiline, even a blank line counts as '1'
1690  }
1691 
1692  return pBuf->nEntries + 1; // include the last line (a blank one) in the line count
1693  }
1694  }
1695 
1696  return 0; // assume blank
1697 }
1698 static int __internal_get_cols(struct _text_object_ *pThis)
1699 {
1700 TEXT_BUFFER *pBuf;
1701 int i1;
1702 
1703  if(WBIsValidTextObject(pThis))
1704  {
1705  pBuf = (TEXT_BUFFER *)(pThis->pText);
1706 
1707  if(pBuf)
1708  {
1709  if(pBuf->nMinMaxCol > pBuf->nMaxCol || !pBuf->nMaxCol || !pBuf->nMinMaxCol)
1710  {
1711  // cache data not valid, so re-evaluate
1712  WBTextBufferRefreshCache(pBuf); // re-calculate
1713  }
1714 
1715  if(pBuf->nMaxCol)
1716  {
1717  if(pBuf->nMaxCol < pThis->iCol) // is my current column past the 'max' column?
1718  {
1719  i1 = pThis->iCol + DEFAULT_TAB_WIDTH; // use my current column position
1720  }
1721  else
1722  {
1723  i1 = pBuf->nMaxCol + DEFAULT_TAB_WIDTH; // use the 'max' column position
1724  }
1725 
1726  return i1 - (i1 % DEFAULT_TAB_WIDTH); // rounded off to 'DEFAULT_TAB_WIDTH'
1727  // so if DEFAULT_TAB_WIDTH is 8, then 1 becomes 8, 8 becomes 16, etc.
1728  }
1729  }
1730  else if(pThis->iCol > 0) // allow for 'scrolling without any actual data yet'
1731  {
1732  i1 = pThis->iCol + DEFAULT_TAB_WIDTH; // use my current column position
1733  return i1 - (i1 % DEFAULT_TAB_WIDTH); // rounded off to 'DEFAULT_TAB_WIDTH'
1734  }
1735 
1736  // all other conditions fall through and return 0
1737  }
1738 
1739  return 0; // assume blank
1740 }
1741 
1742 static int __internal_get_filetype(const struct _text_object_ *pThis)
1743 {
1744  if(WBIsValidTextObject(pThis))
1745  {
1746  return pThis->iFileType;
1747  }
1748  return 0; // for now
1749 }
1750 static void __internal_set_filetype(struct _text_object_ *pThis, int iFileType)
1751 {
1752  if(WBIsValidTextObject(pThis))
1753  {
1754  pThis->iFileType = iFileType;
1755  }
1756 }
1757 static int __internal_get_linefeed(const struct _text_object_ *pThis)
1758 {
1759  if(WBIsValidTextObject(pThis))
1760  {
1761  return (int) pThis->iLineFeed;
1762  }
1763  return 0; // for now
1764 }
1765 static void __internal_set_linefeed(struct _text_object_ *pThis, int iLineFeed)
1766 {
1767  if(WBIsValidTextObject(pThis))
1768  {
1769  pThis->iLineFeed = (enum _LineFeed_)iLineFeed;
1770  }
1771 }
1772 static int __internal_get_insmode(const struct _text_object_ *pThis)
1773 {
1774  if(WBIsValidTextObject(pThis))
1775  {
1776  return pThis->iInsMode;
1777  }
1778  return 0; // for now
1779 }
1780 static void __internal_set_insmode(struct _text_object_ *pThis, int iInsMode)
1781 {
1782  if(WBIsValidTextObject(pThis))
1783  {
1784  pThis->iInsMode = iInsMode;
1785  }
1786 }
1787 static int __internal_get_selmode(const struct _text_object_ *pThis)
1788 {
1789  if(WBIsValidTextObject(pThis))
1790  {
1791  return pThis->iSelMode;
1792  }
1793  return 0; // for now
1794 }
1795 static void __internal_set_selmode(struct _text_object_ *pThis, int iSelMode)
1796 {
1797  if(WBIsValidTextObject(pThis))
1798  {
1799  pThis->iSelMode = iSelMode;
1800  }
1801 }
1802 static int __internal_get_tab(const struct _text_object_ *pThis)
1803 {
1804  if(WBIsValidTextObject(pThis))
1805  {
1806  return pThis->iTab;
1807  }
1808 
1809  return 0; // for now
1810 }
1811 static void __internal_set_tab(struct _text_object_ *pThis, int iTab)
1812 {
1813  if(WBIsValidTextObject(pThis))
1814  {
1815  pThis->iTab = iTab;
1816  }
1817 }
1818 static int __internal_get_scrollmode(const struct _text_object_ *pThis)
1819 {
1820  if(WBIsValidTextObject(pThis))
1821  {
1822  return pThis->iScrollMode;
1823  }
1824  return 0; // for now
1825 }
1826 static void __internal_set_scrollmode(struct _text_object_ *pThis, int iScrollMode)
1827 {
1828  if(WBIsValidTextObject(pThis))
1829  {
1830  pThis->iScrollMode = iScrollMode;
1831  }
1832 }
1833 static void __internal_get_select(const struct _text_object_ *pThis, WB_RECT *pRct)
1834 {
1835  if(WBIsValidTextObject(pThis))
1836  {
1837  if(pRct)
1838  {
1839  if(SEL_RECT_ALL(pThis))
1840  {
1841  pRct->left = -1; // 'normalize' 'select all' to return {-1,-1,-1,-1}
1842  pRct->right = -1;
1843  pRct->top = -1;
1844  pRct->bottom = -1;
1845  }
1846  else
1847  {
1848  memcpy(pRct, &(pThis->rctSel), sizeof(*pRct));
1849 
1850  if(!SEL_RECT_EMPTY(pThis)) // if not empty
1851  {
1852  NORMALIZE_SEL_RECT(*pRct); // normalize the results
1853  }
1854  }
1855  }
1856  }
1857 }
1858 static void __internal_set_select(struct _text_object_ *pThis, const WB_RECT *pRct)
1859 {
1860  if(WBIsValidTextObject(pThis))
1861  {
1862  memcpy(&(pThis->rctSel), pRct, sizeof(*pRct));
1863  }
1864 }
1865 static int __internal_has_select(const struct _text_object_ *pThis)
1866 {
1867  if(WBIsValidTextObject(pThis))
1868  {
1869  return !SEL_RECT_EMPTY(pThis);
1870  }
1871  return 0;
1872 }
1873 static char* __internal_get_sel_text(const struct _text_object_ *pThis, const WB_RECT *pRct)
1874 {
1875 WB_RECT rctSel;
1876 
1877  if(WBIsValidTextObject(pThis))
1878  {
1879  if(SEL_RECT_EMPTY(pThis))
1880  {
1881  return NULL;
1882  }
1883  else if(SEL_RECT_ALL(pThis))
1884  {
1885  return __internal_get_selected_text(pThis, -1, -1, -1, -1);
1886  }
1887 
1888  memcpy(&rctSel, &(pThis->rctSel), sizeof(rctSel));
1889 
1890  NORMALIZE_SEL_RECT(rctSel);
1891 
1892  return __internal_get_selected_text(pThis, rctSel.top, rctSel.left,
1893  rctSel.bottom, rctSel.right);
1894  }
1895  return NULL; // for now
1896 }
1897 static int __internal_get_row(const struct _text_object_ *pThis)
1898 {
1899  if(WBIsValidTextObject(pThis))
1900  {
1901  return pThis->iRow;
1902  }
1903  return 0; // for now
1904 }
1905 static void __internal_set_row(struct _text_object_ *pThis, int iRow)
1906 {
1907  if(WBIsValidTextObject(pThis))
1908  {
1909  if(iRow < 0 || !pThis->pText)
1910  {
1911  iRow = 0;
1912  }
1913  else if(iRow > ((TEXT_BUFFER *)pThis->pText)->nEntries)
1914  {
1915  iRow = ((TEXT_BUFFER *)pThis->pText)->nEntries;
1916  }
1917 
1918  pThis->iRow = iRow;
1919  }
1920 }
1921 static int __internal_get_col(const struct _text_object_ *pThis)
1922 {
1923  if(WBIsValidTextObject(pThis))
1924  {
1925  return pThis->iCol;
1926  }
1927  return 0; // for now
1928 }
1929 static void __internal_set_col(struct _text_object_ *pThis, int iCol)
1930 {
1931 int iAutoScrollWidth = AUTO_HSCROLL_SIZE;
1932 
1933  if(WBIsValidTextObject(pThis))
1934  {
1935  pThis->iCol = iCol; // for now, assign "as-is"
1936 
1937  if(pThis->iLineFeed == LineFeed_NONE && // single-line, do auto-scrolling
1938  pThis->rctView.right > pThis->rctView.left) // in case the view area is empty
1939  {
1940  while(iAutoScrollWidth > 1 && iAutoScrollWidth >= pThis->rctView.right - pThis->rctView.left)
1941  {
1942  iAutoScrollWidth >>= 1;
1943  }
1944 
1945  // scroll left/right to expose the cursor
1946 
1947  while(pThis->rctView.left > pThis->iCol)
1948  {
1949  pThis->rctView.left -= iAutoScrollWidth;
1950  pThis->rctView.right -= iAutoScrollWidth;
1951  }
1952  while(pThis->rctView.right <= pThis->iCol)
1953  {
1954  pThis->rctView.left += iAutoScrollWidth;
1955  pThis->rctView.right += iAutoScrollWidth;
1956  }
1957  }
1958  }
1959 }
1960 static void __internal_del_select(struct _text_object_ *pThis)
1961 {
1962 char *pTemp, *pL;
1963 int iSelAll, iLen, i1, i2;
1964 TEXT_BUFFER *pBuf;
1965 WB_RECT rctSel;
1966 
1967 
1968  if(WBIsValidTextObject(pThis))
1969  {
1970  __internal_invalidate_cursor(pThis, 0);
1971  pThis->iBlinkState = 0; // this affects the cursor blink, basically resetting it whenever I edit something
1972 
1973  iSelAll = SEL_RECT_ALL(pThis); // identifies "select all"
1974 
1975  if(!iSelAll && SEL_RECT_EMPTY(pThis))
1976  {
1977  // no selection, do nothing
1978  return;
1979  }
1980 
1981  pBuf = (TEXT_BUFFER *)(pThis->pText);
1982 
1983  if(!pBuf || (pThis->rctSel.top >= pBuf->nEntries && pThis->rctSel.bottom >= pBuf->nEntries))
1984  {
1985  return; // NO buffer, or select area is outside of buffer area
1986  }
1987 
1988  // for the undo buffer I will need a copy of the original text.
1989  // this function only returns NULL on error
1990 
1991  memcpy(&rctSel, &(pThis->rctSel), sizeof(rctSel));
1992 
1993  if(iSelAll)
1994  {
1995  pTemp = __internal_get_selected_text(pThis, -1, -1, -1, -1);
1996  }
1997  else
1998  {
1999  NORMALIZE_SEL_RECT(rctSel);
2000 
2001  pTemp = __internal_get_selected_text(pThis, rctSel.top, rctSel.left,
2002  rctSel.bottom, rctSel.right);
2003  }
2004 
2005  if(iSelAll)
2006  {
2007  // delete all
2008  if(pThis->pText)
2009  {
2010  WBFreeTextBuffer(pThis->pText);
2011  pThis->pText = NULL;
2012  }
2013 
2014  if(pTemp)
2015  {
2016  __internal_add_undo(pThis, UNDO_DELETE, pThis->iSelMode,
2017  0, 0, &(pThis->rctSel), pTemp, -1,
2018  pBuf->nEntries, 0, NULL, NULL, 0);
2019  }
2020  }
2021  else if(pThis->iLineFeed == LineFeed_NONE ||
2022  rctSel.top == rctSel.bottom) // single-line selection (all methods behave the same)
2023  {
2024  // delete from rctSel.left to rctSel.right (exclusive)
2025 
2026  pL = pBuf->aLines[rctSel.top];
2027 
2028  if(pL)
2029  {
2030  iLen = WBGetMBLength(pL);
2031  if(iLen >= rctSel.left)
2032  {
2033  if(iLen <= rctSel.right)
2034  {
2035  char *pTempL = WBGetMBCharPtr(pL, rctSel.left, NULL);
2036  if(pTempL)
2037  {
2038  *pTempL = 0; // just truncate the string
2039  }
2040  }
2041  else
2042  {
2043  char *pTempL = WBGetMBCharPtr(pL, rctSel.left, NULL);
2044  char *pTempR = WBGetMBCharPtr(pL, rctSel.right, NULL);
2045 
2046  if(pTempL && pTempR)
2047  {
2048  strcpy(pTempL, pTempR); // thereby squeezing the string together and deleting the selection
2049  }
2050  }
2051  }
2052  }
2053 
2054  if(pTemp)
2055  {
2056  __internal_add_undo(pThis, UNDO_DELETE, pThis->iSelMode,
2057  rctSel.top, rctSel.left, &(pThis->rctSel),
2058  pTemp, -1,
2059  rctSel.bottom, rctSel.right, NULL, NULL, 0);
2060  }
2061 
2062  if(pThis->iLineFeed == LineFeed_NONE && // special case, auto-hscroll
2063  pThis->rctView.right > pThis->rctView.left)
2064  {
2065  int iAutoScrollWidth = AUTO_HSCROLL_SIZE;
2066 
2067  while(iAutoScrollWidth > 1 && iAutoScrollWidth >= pThis->rctView.right - pThis->rctView.left)
2068  {
2069  iAutoScrollWidth >>= 1;
2070  }
2071 
2072  // scroll left/right to expose the cursor
2073 
2074  while(pThis->rctView.left > pThis->iCol)
2075  {
2076  pThis->rctView.left -= iAutoScrollWidth;
2077  pThis->rctView.right -= iAutoScrollWidth;
2078  }
2079  while(pThis->rctView.right <= pThis->iCol)
2080  {
2081  pThis->rctView.left += iAutoScrollWidth;
2082  pThis->rctView.right += iAutoScrollWidth;
2083  }
2084  }
2085  }
2086  else if(pThis->iSelMode == SelectMode_BOX) // multiline delete BOX mode
2087  {
2088  }
2089  else if(pThis->iSelMode == SelectMode_LINE) // multiline delete LINE mode
2090  {
2091  }
2092  else // default 'char'
2093  {
2094  if(pBuf->aLines[rctSel.top] && rctSel.left > 0)
2095  {
2096  pL = pBuf->aLines[rctSel.top];
2097 
2098  if(rctSel.right > 0)
2099  {
2100  char *pJoin, *pNew;
2101 
2102  pJoin = WBGetMBCharPtr(pBuf->aLines[rctSel.bottom], rctSel.right, NULL);
2103  pNew = WBJoinMBLine(pL, rctSel.left, pJoin);
2104 
2105  if(!pNew) // error
2106  {
2107  WB_ERROR_PRINT("ERROR - %s - memory allocation error, errno=%d\n", __FUNCTION__, errno);
2108  return; // no undo buffer when there's a memory error
2109  }
2110  pBuf->aLines[rctSel.top] = pNew;
2111  if(pL) // just in case
2112  {
2113  WBFree(pL);
2114  }
2115 
2116  i2 = rctSel.bottom + 1; // start copying the NEXT line
2117  }
2118  else
2119  {
2120  char *pTemp = WBGetMBCharPtr(pL, rctSel.left, NULL);
2121  if(pTemp) // just in case
2122  {
2123  *pTemp = 0; // truncate line at this position
2124  }
2125 
2126  i2 = rctSel.bottom; // start copying THIS line (since I'm not joining them)
2127  }
2128 
2129  for(i1=pThis->rctSel.top + 1; i2 < pBuf->nEntries; i1++, i2++)
2130  {
2131  if(pBuf->aLines[i1])
2132  {
2133  WBFree(pBuf->aLines[i1]);
2134  }
2135  pBuf->aLines[i1] = pBuf->aLines[i2];
2136  pBuf->aLines[i2] = NULL; // for now, to prevent accidental pointer re-use later
2137  }
2138 
2139  pBuf->nEntries = i1;
2140  }
2141  else
2142  {
2143  if(rctSel.right == 0) // an even number of lines is being deleted
2144  {
2145  for(i1=rctSel.top, i2=rctSel.bottom; i2 < pBuf->nEntries; i1++, i2++)
2146  {
2147  if(pBuf->aLines[i1])
2148  {
2149  WBFree(pBuf->aLines[i1]);
2150  }
2151  pBuf->aLines[i1] = pBuf->aLines[i2];
2152  pBuf->aLines[i2] = NULL; // for now, to prevent accidental pointer re-use later
2153  }
2154 
2155  pBuf->nEntries = i1;
2156  }
2157  else // uneven lines
2158  {
2159  // top line will become partial bottom line
2160  pL = pBuf->aLines[rctSel.bottom];
2161 
2162  if(pL)
2163  {
2164  char *pTempR = WBGetMBCharPtr(pL, rctSel.right, NULL);
2165  if(pTempR)
2166  {
2167  strcpy(pL, pTempR);
2168  }
2169  else
2170  {
2171  *pL = 0; // for now, truncate the line if this happens
2172  }
2173  }
2174 
2175  if(pBuf->aLines[rctSel.top])
2176  {
2177  WBFree(pBuf->aLines[rctSel.top]);
2178  pBuf->aLines[rctSel.top] = NULL; // by convention (it's re-assigned in the next line though)
2179  }
2180 
2181  pBuf->aLines[rctSel.top] = pL;
2182  pBuf->aLines[rctSel.bottom] = NULL; // by convention, to prevent re-use of pointer
2183 
2184  for(i1=rctSel.top + 1, i2=rctSel.bottom + 1; i2 < pBuf->nEntries; i1++, i2++)
2185  {
2186  if(pBuf->aLines[i1])
2187  {
2188  WBFree(pBuf->aLines[i1]);
2189  pBuf->aLines[i1] = NULL; // by convention (re-assigned in next line)
2190  }
2191 
2192  pBuf->aLines[i1] = pBuf->aLines[i2];
2193  pBuf->aLines[i2] = NULL; // by convention, to prevent accidental pointer re-use later
2194  }
2195 
2196  pBuf->nEntries = i1;
2197  }
2198  }
2199 
2200  if(pTemp)
2201  {
2202  __internal_add_undo(pThis, UNDO_DELETE, pThis->iSelMode,
2203  rctSel.top, rctSel.left, &(pThis->rctSel),
2204  pTemp, -1,
2205  rctSel.bottom, rctSel.right, NULL, NULL, 0);
2206  }
2207  }
2208 
2209  if(pTemp)
2210  {
2211  WBFree(pTemp);
2212  }
2213 
2214  // once the selection has been deleted, select 'nothing' and re-paint all
2215  // and of course, iRow and iCol will be at the left/top of the selection
2216 
2217  // NOTE: this assumes that the data is consistent
2218 
2219  pThis->rctSel.left = pThis->rctSel.top = pThis->rctSel.right = pThis->rctSel.bottom = 0;
2220 
2221  if(iSelAll)
2222  {
2223  pThis->iRow = pThis->iCol = 0;
2224  }
2225  else
2226  {
2227  pThis->iRow = rctSel.top;
2228  pThis->iCol = rctSel.left;
2229  }
2230 
2231  __internal_invalidate_rect(pThis, NULL, 1); // TODO: optimize this
2232  }
2233 }
2234 static void __internal_replace_select(struct _text_object_ *pThis, const char *szText, unsigned long cbLen)
2235 {
2236 WB_RECT rctSel;
2237 int iSelAll;
2238 
2239  if(WBIsValidTextObject(pThis))
2240  {
2241  int iOldIns; //, iOldRow, iOldCol;
2242 
2243  if(pThis->iRow < 0 || pThis->iCol< 0)
2244  {
2245  pThis->iRow = 0;
2246  pThis->iCol = 0;
2247  }
2248 
2249 // NOTE: iOldRow and iOldCol not being used; commented out because of linux gcc warnings
2250 // iOldRow = pThis->iRow;
2251 // iOldCol = pThis->iCol;
2252  iOldIns = pThis->iInsMode;
2253 
2254  iSelAll = SEL_RECT_ALL(pThis);
2255 
2256  memcpy(&rctSel, &(pThis->rctSel), sizeof(rctSel));
2257 
2258  if(iSelAll)
2259  {
2260  pThis->iRow = 0;
2261  pThis->iCol = 0;
2262  }
2263  else if(!SEL_RECT_EMPTY(pThis))
2264  {
2265  NORMALIZE_SEL_RECT(rctSel);
2266 
2267  pThis->iRow = rctSel.top;
2268  pThis->iCol = rctSel.left;
2269  }
2270 
2271  pThis->iInsMode = InsertMode_INSERT;
2272 
2273  // TODO: if box mode, height/width may need to match. for now, don't care
2274 
2275  if(!iSelAll && SEL_RECT_EMPTY(pThis)) // not 'ALL'
2276  {
2277  __internal_ins_chars(pThis, szText, cbLen); // just insert the text (no selection)
2278 
2279  // NOTE: select rectangle remains empty
2280  }
2281  else
2282  {
2283  // for now delete the selection, then insert the new characters
2284  // this will take into consideration the select mode
2285  __internal_del_select(pThis); // this also clears the selection
2286 
2287  // new selection starts at rctSel.top, rctSel.left
2288 
2289  __internal_ins_chars(pThis, szText, cbLen); // just insert the text (no selection)
2290 
2291  // new selection ends at iRow, iCol
2292 
2293  rctSel.right = pThis->iRow;
2294  rctSel.bottom = pThis->iCol;
2295 
2296  // assign the new select rectangle to become the text I just added.
2297  memcpy(&(pThis->rctSel), &rctSel, sizeof(pThis->rctSel));
2298  }
2299 
2300 // pThis->iRow = iOldRow;
2301 // pThis->iCol = iOldCol;
2302  pThis->iInsMode = iOldIns;
2303 
2304  // todo: mark a NEW selection using the inserted text? this might mean replicating code for
2305  // __internal_del_select and __internal_ins_chars and processing undo here
2306 
2307 
2308  __internal_invalidate_rect(pThis, NULL, 1); // TODO: optimize this
2309  }
2310 }
2311 static void __internal_del_chars(struct _text_object_ *pThis, int nChar)
2312 {
2313 TEXT_BUFFER *pBuf;
2314 int i1, i2, iLen;
2315 char *pL, *pL2, *pL3;
2316 WB_RECT rctInvalid;
2317 
2318 
2319  if(!nChar)
2320  {
2321  return; // does nothing
2322  }
2323 
2324  // NOTE: nChar < 0 deletes to the left (like a backspace), >0 deletes to the right like 'Del' normally would
2325  // If the cursor is at the end of the line or the start of the line, an appropriate delete will
2326  // merge the previous (or next) line. Values of |nChar| greater than the number of characters remaining
2327  // in the line (from the cursor) _STOP_ at the beginning/end of the line. Only a delete with cursor
2328  // AT the beginning or end of the line will merge. Merge also considers the virtual cursor position.
2329 
2330  if(!WBIsValidTextObject(pThis))
2331  {
2332  return; // error
2333  }
2334 
2335  __internal_invalidate_cursor(pThis, 0);
2336  pThis->iBlinkState = 0; // this affects the cursor blink, basically resetting it whenever I edit something
2337 
2338  if(nChar > 0 || (pThis->iCol == 0 && pThis->iLineFeed == LineFeed_NONE))
2339  {
2340  __internal_calc_rect(pThis, &rctInvalid, pThis->iRow, pThis->iCol, pThis->iRow, pThis->iCol + 1);
2341  }
2342  else if(pThis->iCol > 0) // backspace, don't merge lines
2343  {
2344  __internal_calc_rect(pThis, &rctInvalid, pThis->iRow, pThis->iCol - 1, pThis->iRow, pThis->iCol);
2345  }
2346  else if(pThis->iLineFeed != LineFeed_NONE)
2347  {
2348  __internal_calc_rect(pThis, &rctInvalid, pThis->iRow - 1, 0, -1, -1);
2349  }
2350 
2351 
2352  pBuf = (TEXT_BUFFER *)(pThis->pText);
2353 
2354  if(!pBuf || !pBuf->nEntries || pThis->iRow > pBuf->nEntries ||
2355  (pThis->iRow == pBuf->nEntries && nChar > 0)) // ending row only allows a backspace from the 1st column
2356  {
2357  return; // do nothing
2358  }
2359 
2360  while(nChar) // while I have characters to delete
2361  {
2362  // if I hit a limit and 'nChar' is still non-zero, break out and return anyway
2363 
2364  if(pThis->iRow > pBuf->nEntries || pThis->iRow < 0) // allow row == nEntries
2365  {
2366  return; // empty
2367  }
2368 
2369  pL = pBuf->aLines[pThis->iRow];
2370 
2371  iLen = WBGetMBLength(pL);
2372 
2373  if(nChar < 0 && pThis->iCol == 0 && // backspace prior to beginning of line
2374  (pThis->iRow <= 0 || pThis->iLineFeed == LineFeed_NONE)) // single line and backspace past begin of column
2375  {
2376  WB_ERROR_PRINT("TEMPORARY: %s - backspace while I'm at start of file\n", __FUNCTION__);
2377  return; // exit
2378  }
2379 
2380  if(nChar > 0 && pThis->iCol >= iLen && // delete past end of line
2381  (pThis->iRow >= pBuf->nEntries ||
2382  pThis->iLineFeed == LineFeed_NONE)) // single line and backspace past begin of column
2383  {
2384  WB_ERROR_PRINT("TEMPORARY: %s - delete while I'm past end of file\n", __FUNCTION__);
2385 
2386  return; // exit
2387  }
2388 
2389  // if it's at an edge, merge lines
2390 
2391  if(pThis->iRow > 0 && nChar < 0 && pThis->iCol == 0) // backspace while at the start of a line
2392  {
2393  pL2 = pBuf->aLines[pThis->iRow - 1];
2394  if(!pL2)
2395  {
2396  pThis->iCol = 0; // a kind of 'fallback' - put column cursor at zero
2397 
2398  pBuf->aLines[pThis->iRow - 1] = pL; // move line to THIS position
2399  pBuf->aLines[pThis->iRow] = NULL; // pre-emptive, avoid sharing pointers
2400 
2401  pL = NULL;
2402  }
2403  else
2404  {
2405  if(pL) // can be NULL, which is a blank line
2406  {
2407  i2 = WBGetMBLength(pL2); // now THIS is the new column position
2408 
2409  pL2 = WBJoinMBLine(pL2, i2, pL);
2410 
2411  if(!pL2)
2412  {
2413  WB_ERROR_PRINT("ERROR: %s - not enough memory to join lines\n", __FUNCTION__);
2414 
2415  return; // do nothing (TODO: error message?)
2416  }
2417 
2418  pBuf->aLines[pThis->iRow - 1] = pL2; // the new pointer
2419  }
2420  else
2421  {
2422  i2 = 0;
2423  }
2424 
2425  pBuf->aLines[pThis->iRow] = NULL; // pre-emptive, avoid sharing pointers
2426 
2427  pThis->iCol = i2; // the new position (end of previous line)
2428 
2429  WBFree(pL);
2430  pL = NULL; // pre-emptive, avoid shared pointers
2431  }
2432 
2433  // if I hit backspace while on row==nEntries, just move the cursor to the end
2434  // of the previous line. I'm not really deleting anything.
2435  if(pThis->iRow >= pBuf->nEntries)
2436  {
2437  pThis->iRow--;
2438  if(pBuf->aLines[pThis->iRow])
2439  {
2440  pThis->iCol = WBGetMBLength(pBuf->aLines[pThis->iRow]);
2441  }
2442  }
2443  else
2444  {
2445  // at this point, 'iRow - 1' is the 'replaced' row, so I need to move rows such that
2446  // I start with 'iRow + 1' and assign to 'i1 - 1'. this works.
2447  for(i1=pThis->iRow + 1; i1 < pBuf->nEntries; i1++)
2448  {
2449  pBuf->aLines[i1 - 1] = pBuf->aLines[i1]; // pack them up by one line
2450  }
2451 
2452  pBuf->nEntries--;
2453  pBuf->aLines[pBuf->nEntries] = NULL; // so that pointers aren't accidentally re-used
2454 
2455  pThis->iRow--; // since I moved up
2456  }
2457 
2458  nChar++; // backspacing, so increment
2459 
2460  __internal_add_undo(pThis, UNDO_DELETE, pThis->iSelMode,
2461  pThis->iRow, pThis->iCol, &(pThis->rctSel),
2462  "\n", 1, // this is what I'm deleting - the newline
2463  pThis->iRow + 1, 0, NULL, NULL, 0);
2464 
2465  __internal_merge_rect(pThis, &rctInvalid, pThis->iRow, 0, -1, -1);
2466  }
2467  else if(pThis->iCol >= iLen && nChar > 0 && pThis->iRow < (pBuf->nEntries - 1))
2468  {
2469  pL2 = pBuf->aLines[pThis->iRow + 1];
2470 
2471  if(pL2)
2472  {
2473  if(*pL2)
2474  {
2475  WB_ERROR_PRINT("TEMPORARY: %s - joining \"%s\" at %d with \"%s\"\n", __FUNCTION__,
2476  pL, pThis->iCol, pL2);
2477 
2478  pL = WBJoinMBLine(pL, pThis->iCol, pL2);
2479 
2480  if(!pL)
2481  {
2482  WB_ERROR_PRINT("ERROR: %s - not enough memory to join\n", __FUNCTION__);
2483 
2484  return; // do nothing ELSE
2485  }
2486 
2487  pBuf->aLines[pThis->iRow] = pL; // the new pointer
2488  }
2489 
2490  WBFree(pL2);
2491  }
2492 
2493  pBuf->nEntries--;
2494 
2495  // merge UP the lines, starting with 'iRow + 1' getting 'iRow + 2'
2496  for(i1=pThis->iRow + 1; i1 < pBuf->nEntries; i1++)
2497  {
2498  pBuf->aLines[i1] = pBuf->aLines[i1 + 1];
2499  }
2500 
2501  pBuf->aLines[pBuf->nEntries] = NULL; // so that pointers aren't accidentally re-used
2502 
2503  nChar--; // deleting, so decrement
2504 
2505  __internal_add_undo(pThis, UNDO_DELETE, pThis->iSelMode,
2506  pThis->iRow, iLen, &(pThis->rctSel),
2507  "\n", 1,
2508  pThis->iRow + 1, 0, NULL, NULL, 0);
2509 
2510 // __internal_merge_rect(pThis, &rctInvalid, pThis->iRow, 0, -1, -1); // invalidate entire row and those that follow
2511 
2512  // bug workaround, mark everything 'invalid'
2513  __internal_merge_rect(pThis, &rctInvalid, 0, 0, -1, -1);
2514  }
2515  else
2516  {
2517  // OK now that _THAT_ is done, delete "up to the end of the string if needed"
2518  // on either end, depending upon which direction we must travel
2519 
2520  pL = pBuf->aLines[pThis->iRow];
2521 
2522 // if(!pL)
2523 // {
2524 // WB_ERROR_PRINT("UNEXPECTED: %s - NULL pointer at row %d\n", __FUNCTION__, pThis->iRow);
2525 //
2526 // return; // do nothing (TODO: error message?)
2527 // }
2528 
2529  // TODO: use WBDelMBChars
2530 
2531  if(nChar < 0 && pThis->iCol > 0)
2532  {
2533  // TODO: handle hard tab translation? For now "leave it"
2534 
2535  i2 = -nChar;
2536  if(i2 > pThis->iCol)
2537  {
2538  i2 = pThis->iCol;
2539  }
2540 
2541  if(pL) // could be NULL
2542  {
2543  // position to which I delete
2544  pL2 = WBGetMBCharPtr(pL, pThis->iCol - i2, NULL); // column to which I delete (backspace)
2545  pL3 = WBGetMBCharPtr(pL, pThis->iCol, NULL); // position FROM where I delete/backspace
2546 
2547 
2548  // create undo record first, before I actually delete things
2549  __internal_add_undo(pThis, UNDO_DELETE, pThis->iSelMode,
2550  pThis->iRow, pThis->iCol + nChar, &(pThis->rctSel),
2551  pL2, pL3 - pL2, // text pointer AND 'true length' in bytes
2552  pThis->iRow, pThis->iCol, NULL, NULL, 0);
2553 
2554  strcpy(pL2, pL3); // delete things down
2555  }
2556 
2557  pThis->iCol -= i2;
2558  nChar += i2; // # of characters actually deleted (added 'cause nChar is negative)
2559 
2560  __internal_merge_rect(pThis, &rctInvalid, pThis->iRow, pThis->iCol, pThis->iRow, -1); // invalidate remainder of row
2561  }
2562  else if(nChar > 0 && iLen > pThis->iCol)
2563  {
2564  // TODO: handle hard tab translation? For now "leave it"
2565 
2566  i2 = nChar;
2567 
2568  if(i2 > iLen - pThis->iCol)
2569  {
2570  i2 = iLen - pThis->iCol;
2571  }
2572 
2573  if(pL)
2574  {
2575  // position to which I delete
2576  pL2 = WBGetMBCharPtr(pL, pThis->iCol + i2, NULL); // column to which I delete
2577  pL3 = WBGetMBCharPtr(pL, pThis->iCol, NULL); // position FROM where I delete/backspace
2578 
2579  // create undo record first, before I actually delete things
2580  __internal_add_undo(pThis, UNDO_DELETE, pThis->iSelMode,
2581  pThis->iRow, pThis->iCol, &(pThis->rctSel),
2582  pL3, pL2 - pL3, // text pointer AND 'true length' in bytes
2583  pThis->iRow, pThis->iCol + nChar, NULL, NULL, 0);
2584 
2585  strcpy(pL3, pL2); // delete things down
2586  }
2587 
2588  nChar -= i2;
2589 
2590  // NOTE: column does not change
2591  __internal_merge_rect(pThis, &rctInvalid, pThis->iRow, pThis->iCol, pThis->iRow, -1); // invalidate remainder of row
2592  }
2593  else
2594  {
2595  // this is a condition that might infinite loop since it doesn't inc/dec nChar
2596  // it was SUPPOSED to be tested for, but wasn't.
2597 
2598  WB_ERROR_PRINT("UNEXPECTED: %s - nChar=%d, iCol=%d, iRow=%d nEntries=%d\n",
2599  __FUNCTION__, nChar, pThis->iCol, pThis->iRow, (int)pBuf->nEntries);
2600 
2601  break;
2602  }
2603  }
2604 
2605  // implement auto-hscroll while deleting
2606 
2607  if(pThis->rctView.right > pThis->rctView.left)
2608  {
2609  if(pThis->rctView.right <= pThis->iCol ||
2610  pThis->rctView.left > pThis->iCol)
2611  {
2612  int iAutoScrollWidth = AUTO_HSCROLL_SIZE;
2613 
2614  while(iAutoScrollWidth > 1 && iAutoScrollWidth >= pThis->rctView.right - pThis->rctView.left)
2615  {
2616  iAutoScrollWidth >>= 1;
2617  }
2618 
2619  // scroll left/right to expose the cursor
2620 
2621  while(pThis->rctView.left > pThis->iCol)
2622  {
2623  pThis->rctView.left -= iAutoScrollWidth;
2624  pThis->rctView.right -= iAutoScrollWidth;
2625  }
2626  while(pThis->rctView.right <= pThis->iCol)
2627  {
2628  pThis->rctView.left += iAutoScrollWidth;
2629  pThis->rctView.right += iAutoScrollWidth;
2630  }
2631 
2632  __internal_invalidate_rect(pThis, NULL, 1); // scrolling, so invalidate everything
2633  }
2634  else
2635  {
2636  // for now, always do this
2637  __internal_invalidate_rect(pThis, NULL, 1); // scrolling, so invalidate everything
2638 // __internal_invalidate_rect(pThis, &rctInvalid, 1); // invalidate bounding rectangle
2639  }
2640  }
2641  else
2642  {
2643  // for now, always do this
2644  __internal_invalidate_rect(pThis, NULL, 1); // scrolling, so invalidate everything
2645 
2646 // __internal_invalidate_rect(pThis, &rctInvalid, 1); // invalidate bounding rectangle
2647  }
2648  }
2649 }
2650 static void __internal_ins_chars(struct _text_object_ *pThis, const char *pChar, int nChar)
2651 {
2652 TEXT_BUFFER *pBuf;
2653 const char *p1, *p2;
2654 char *pL, *pTemp;
2655 int i1, iLen=0, iMultiLine = 0;
2656 WB_RECT rctInvalid;
2657 
2658 
2659  if(!pChar || !nChar)
2660  {
2661  return;
2662  }
2663 
2664  if(nChar < 0)
2665  {
2666  nChar = strlen(pChar);
2667  }
2668 
2669  p1 = pChar;
2670  p2 = pChar + nChar;
2671 
2672 
2673  // FIRST, see if the insert is single-line or multi-line and act accordingly
2674 
2675  while(p1 < p2)
2676  {
2677  if(*p1 == '\n' || *p1 == '\r')
2678  {
2679  iMultiLine = 1;
2680  break; // this will be used in single-line mode to mark "stop here"
2681  }
2682 
2683  p1++;
2684  }
2685 
2686  __internal_invalidate_cursor(pThis, 0); // always do this
2687 
2688  if(WBIsValidTextObject(pThis))
2689  {
2690  if(pThis->iRow < 0 || pThis->iCol< 0)
2691  {
2692  pThis->iRow = 0;
2693  pThis->iCol = 0;
2694  }
2695 
2696  __internal_invalidate_cursor(pThis, 0);
2697  pThis->iBlinkState = 0; // this affects the cursor blink, basically resetting it whenever I edit something
2698 
2699  __internal_calc_rect(pThis, &rctInvalid, pThis->iRow, pThis->iCol, pThis->iRow, -1); // always refresh entire line from 'iCol' forward
2700 
2701  pBuf = (TEXT_BUFFER *)(pThis->pText);
2702 
2703  if(pThis->iLineFeed == LineFeed_NONE) // single line
2704  {
2705  if(iMultiLine)
2706  {
2707  iMultiLine = 0;
2708  p2 = p1; // either end of string, or the position of the newline
2709  }
2710 
2711  p1 = pChar;
2712 
2713  // insert at 'iCol'
2714 
2715  if(!pBuf) // no buffer, so add text now
2716  {
2717  pBuf = pThis->pText = WBAllocTextBuffer(p1, p2 - p1); // this copies the data, too
2718 
2719  pThis->iRow = 0; // always
2720  pThis->iCol = WBGetMBColIndex(p1, p2); // the col index of 'p2' within 'p1' ('MB' length of 'p1 through p2')
2721  // new col will be 'end of string'. by using this value, it's effectively, like pressing 'end' after inserting
2722 
2723  __internal_add_undo(pThis, UNDO_INSERT, pThis->iSelMode,
2724  pThis->iRow, 0, &(pThis->rctSel), NULL, 0, // old col was 0, always
2725  pThis->iRow, pThis->iCol, NULL, // new col already assigned
2726  p1, p2 - p1); // the new text
2727  }
2728  else if(p2 > p1) // adding text to existing buffer
2729  {
2730  if(pThis->iRow != 0)
2731  {
2732  pThis->iRow = 0; // force it
2733  }
2734 
2735  iLen = 0; // pre-assign for later (so it won't be "unassigned" by accident on error)
2736 
2737  //------------------------------------------------
2738  // calculating buffer size, allocating line buffer
2739  //------------------------------------------------
2740 
2741  if(pBuf->nEntries <= 0 || !pBuf->aLines[0])
2742  {
2743  if(pBuf->nEntries <= 0)
2744  {
2745  pBuf->nEntries = 1; // single-line, always this, but not verifying for now
2746  }
2747 
2748  pL = pBuf->aLines[0] = WBAlloc(pThis->iCol + p2 - p1 + 2);
2749 
2750  if(pL)
2751  {
2752  if(pThis->iCol > 0) // this works because a space is always 1 byte long
2753  {
2754  memset(pL, ' ', pThis->iCol);
2755  pL[pThis->iCol] = 0;
2756  }
2757 
2758  iLen = pThis->iCol;
2759  }
2760  }
2761  else
2762  {
2763  pTemp = pBuf->aLines[0];
2764 
2765  i1 = strlen(pTemp); // the actual length
2766 
2767  iLen = WBGetMBLength(pTemp); // the # of columns
2768 
2769  if(iLen < pThis->iCol)
2770  {
2771  i1 += pThis->iCol - iLen; // add the alloc length for white space following 'iCol'
2772  }
2773 
2774  if(pThis->iInsMode == InsertMode_OVERWRITE &&
2775  iLen >= pThis->iCol) // only if I actually overwrite something
2776  {
2777  if(iLen < pThis->iCol + WBGetMBColIndex(p1, p2)) // will overwrite extend the string??
2778  {
2779  char *pTempC = WBGetMBCharPtr(pTemp, pThis->iCol, NULL);
2780 
2781  if(pTempC)
2782  {
2783  i1 = (pTempC - pTemp) + (p2 - p1); // extent of teh text I'm adding when I overwrite
2784  }
2785  else
2786  {
2787  i1 += p2 - p1; // fallback in case of problems
2788  }
2789  }
2790  }
2791  else
2792  {
2793  i1 += p2 - p1; // I'm extending the string by 'that much'
2794  }
2795 
2796  pL = WBReAlloc(pTemp, i1 + 2);
2797 
2798  if(pL)
2799  {
2800  pBuf->aLines[0] = pL;
2801  }
2802  else
2803  {
2804  WB_ERROR_PRINT("ERROR: %s - not enough memory\n", __FUNCTION__);
2805  }
2806  }
2807 
2808  //-------------------------------------------
2809  // copying data into buffer at column 'iCol'
2810  //-------------------------------------------
2811 
2812  if(pL)
2813  {
2814  char *pTempL;
2815  if(iLen < pThis->iCol)
2816  {
2817  pTempL = pL + strlen(pL); // will always be this
2818 
2819  memset(pTempL, ' ', pThis->iCol - iLen); // pad with spaces
2820 
2821  pTempL += pThis->iCol - iLen; // advance the pointer to where 'iCol' is
2822  *pTempL = 0; // make sure it ends in a zero byte
2823  }
2824  else if(iLen > pThis->iCol && // insert 'in the middle'
2825  pThis->iInsMode != InsertMode_OVERWRITE) // NOT overwriting
2826  {
2827  pTempL = WBGetMBCharPtr(pL, pThis->iCol, NULL);
2828 
2829  // make room for text
2830  if(WB_LIKELY((iLen - pThis->iCol + 1) > 0)) // probably always true
2831  {
2832  memmove(pTempL + (p2 - p1), pTempL, strlen(pTempL) + 1); // note that this includes the zero byte at the end
2833  }
2834  }
2835  else if((p2 - p1) + pThis->iCol >= iLen) // overwriting AND it extends the buffer?
2836  {
2837  pTempL = WBGetMBCharPtr(pL, pThis->iCol, NULL);
2838 
2839  pTempL[p2 - p1] = 0; // I need a terminating zero byte immediately after where the text will go
2840  }
2841 
2842  __internal_add_undo(pThis, UNDO_INSERT, pThis->iSelMode,
2843  pThis->iRow, pThis->iCol, &(pThis->rctSel),
2844  (pThis->iInsMode == InsertMode_OVERWRITE && pThis->iCol < iLen ?
2845  pL + pThis->iCol : NULL), // for overwrite, it's the original text
2846  (pThis->iInsMode == InsertMode_OVERWRITE && pThis->iCol < iLen ?
2847  p2 - p1 : 0), // length of old text for overwrite
2848  pThis->iRow, pThis->iCol + (p2 - p1), NULL,
2849  p1, p2 - p1); // the new text
2850 
2851  memcpy(pL + pThis->iCol, p1, p2 - p1); // insert the data (but not the terminating zero byte)
2852 
2853  pThis->iCol += WBGetMBColIndex(p1, p2); // always advance the cursor to this point (overwrite OR insert)
2854  }
2855  }
2856  }
2857  else // multi-line text
2858  {
2859 
2860  // TODO: auto-vscroll while inserting text, make sure cursor position is visible
2861 
2862  // insert at 'iCol'
2863 
2864  if(!pBuf) // no buffer, so add text now
2865  {
2866  p1 = pChar;
2867  p2 = pChar + nChar;
2868 
2869  pBuf = pThis->pText = WBAllocTextBuffer(pChar, nChar);
2870 
2871  pThis->iRow = 0; // always
2872  pThis->iCol = nChar; // effectively, like pressing 'end' after inserting
2873 
2874  __internal_add_undo(pThis, UNDO_INSERT, pThis->iSelMode,
2875  pThis->iRow, 0, &(pThis->rctSel), NULL, 0, // old col was 0, always
2876  pThis->iRow, pThis->iCol, NULL, // new col already assigned
2877  pChar, nChar); // the new text
2878  }
2879  else // add to existing buffer
2880  {
2881  // NOW, parse into 'lines' and add text as needed
2882  const char *p3 = pChar + nChar; // p3 is 'end of text' marker now
2883  p2 = pChar; // also marks 'end of line' for insertion
2884 
2885 
2886  while(p2 < p3)
2887  {
2888  int nTabs = 0;
2889  p1 = p2; // new 'start of line' pointer (for insertion)
2890 
2891  while(p2 < p3 && *p2 != '\n' && *p2 != '\r')
2892  {
2893  if(*p2 == '\t') // hard tab insertion?
2894  {
2895  nTabs++; // count them (for now); this is for allocation purposes
2896 
2897  // NOTE: if hard tabs are supported, I will be storing the white space
2898  // as a special character
2899  }
2900 
2901  p2++;
2902  }
2903 
2904  // p2 now points just past the end of the 'line' I am inserting, and points to
2905  // either a '\r' or a '\n' (which will be inserted)
2906 
2907  // there are 3 scenarious:
2908  // a) inserting a line feed (no text)
2909  // b) inserting text without a line feed
2910  // c) inserting text WITH a line feed (or more than one line feed)
2911  //
2912  // and the insert condition: insert OR overwrite
2913  // a) if overwrite exceeds the length of the line, it appends to the same line.
2914  // b) if overwrite 'overwrites' with a line feed, it acts like 'insert' at that point (gedit does this too)
2915  // c) if insert or overwrite adds a line feed, a new line is created with the remaining text from the line
2916  // and subsequent lines are moved down by 1 to make space for it
2917 
2918  // I'll deal with 'inserting text' first, then the line feed separately
2919 
2920  // TODO: use WBSplitMBLine() if there's a linefeed
2921  // otherwise, WBInsertMBChars()
2922 
2923  if(pBuf->nEntries <= 0 || !pBuf->aLines[0])
2924  {
2925  if(pBuf->nEntries <= 0)
2926  {
2927  pBuf->nEntries = 1; // single-line, always this, but not verifying for now
2928  }
2929 
2930  pL = pBuf->aLines[0] = WBAlloc(pThis->iCol + (p2 - p1)
2931  + nTabs * pThis->iTab // extra space for tabs
2932  + 2); // 2 additional spaces
2933 
2934  if(pL)
2935  {
2936  if(pThis->iCol > 0) // prepend with spaces
2937  {
2938  memset(pL, ' ', pThis->iCol);
2939  }
2940 
2941  pL[pThis->iCol] = 0; // mark end of string
2942 
2943  iLen = pThis->iCol; // in this case it will be
2944  }
2945  }
2946  else
2947  {
2948  pTemp = pBuf->aLines[pThis->iRow];
2949 
2950  i1 = strlen(pTemp); // the actual length
2951  iLen = WBGetMBLength(pTemp); // # of columns
2952 
2953  if(iLen < pThis->iCol)
2954  {
2955  i1 += pThis->iCol - iLen; // the necessary alloc length
2956  }
2957 
2958  if(pThis->iInsMode == InsertMode_OVERWRITE
2959  && iLen > pThis->iCol)
2960  {
2961  if(iLen < pThis->iCol + (p2 - p1))
2962  {
2963  i1 += (p2 - p1) - (iLen - pThis->iCol); // estimated extent of the text I'm adding
2964  }
2965  }
2966  else
2967  {
2968  i1 += p2 - p1; // extending string length
2969  }
2970 
2971  pL = WBReAlloc(pTemp, i1
2972  + nTabs * pThis->iTab // extra space for tabs
2973  + 2); // re-allocate to fit (plus 2 extra chars)
2974 
2975  if(pL)
2976  {
2977  pBuf->aLines[pThis->iRow] = pL;
2978  }
2979  else
2980  {
2981  WB_ERROR_PRINT("ERROR: %s - not enough memory\n", __FUNCTION__);
2982  }
2983  }
2984 
2985  if(pL)
2986  {
2987  if(iLen < pThis->iCol)
2988  {
2989  pTemp = WBGetMBCharPtr(pL, iLen, NULL);
2990 
2991  memset(pTemp, ' ', pThis->iCol - iLen); // pad with spaces
2992  pTemp[pThis->iCol - iLen] = 0; // I need a terminating zero byte
2993 
2994  iLen = pThis->iCol;
2995  }
2996  else if(iLen > pThis->iCol && // insert 'in the middle'
2997  pThis->iInsMode != InsertMode_OVERWRITE) // NOT overwriting
2998  {
2999  // make room for text
3000  if(WB_LIKELY((iLen - pThis->iCol + 1) > 0)) // probably always true
3001  {
3002  memmove(pL + pThis->iCol + (p2 - p1), pL + pThis->iCol, iLen - pThis->iCol + 1);
3003  // note that this includes teh zero byte.
3004  }
3005  }
3006  else if((p2 - p1) + pThis->iCol >= iLen)
3007  {
3008  pL[pThis->iCol + p2 - p1] = 0; // I need a terminating zero byte
3009  }
3010 
3011  __internal_add_undo(pThis, UNDO_INSERT, pThis->iSelMode,
3012  pThis->iRow, pThis->iCol, &(pThis->rctSel),
3013  (pThis->iInsMode == InsertMode_OVERWRITE && pThis->iCol < iLen ?
3014  pL + pThis->iCol : NULL), // for overwrite, it's the original text
3015  (pThis->iInsMode == InsertMode_OVERWRITE && pThis->iCol < iLen ?
3016  p2 - p1 : 0), // length of old text for overwrite
3017  pThis->iRow, pThis->iCol + (p2 - p1), NULL,
3018  p1, p2 - p1); // the new text
3019 
3020  memcpy(pL + pThis->iCol, p1, p2 - p1); // insert the data
3021 
3022  // the end of the loop
3023 
3024  if(p2 > p1)
3025  {
3026  pThis->iCol += p2 - p1; // always advance the cursor to this point (overwrite OR insert)
3027  }
3028 
3029  // if last char is a newline, split the line at 'iCol'
3030 
3031  if(p2 < p3 && (*p2 == '\r' || *p2 == '\n')) // a newline
3032  {
3033  char c1 = *p2;
3034 
3035  p2++; // advance past the newline
3036 
3037  if(p2 < p3 && *p2 != c1 &&
3038  (*p2 == '\r' || *p2 == '\n')) // CRLF or LFCR
3039  {
3040  p2++; // skip this one too
3041  }
3042 
3043  // insert a new row after this one. But first, does pBuf have enough space?
3044 
3045  if(WBCheckReAllocTextBuffer(&pBuf, 1) || !pBuf)
3046  {
3047  WB_ERROR_PRINT("ERROR: %s - not enough memory to add line\n", __FUNCTION__);
3048 
3049  break; // bust out of loop - cannot insert any more
3050  }
3051  else
3052  {
3053  pThis->pText = pBuf; // re-assign in case there's a new ptr
3054 
3055  WB_ERROR_PRINT("TEMPORARY: %s - split line, %d, %d, %d\n", __FUNCTION__,
3056  pThis->iRow, pThis->iCol, (int)pBuf->nEntries);
3057 
3058  for(i1=pBuf->nEntries; i1 > (pThis->iRow + 1); i1--)
3059  {
3060  pBuf->aLines[i1] = pBuf->aLines[i1 - 1]; // make room for new line
3061  }
3062 
3063  pBuf->nEntries++;
3064 
3065  // TODO: if insert blank line, do I just leave it 'NULL' and not make a copy?
3066 
3067  pTemp = WBGetMBCharPtr(pL, pThis->iCol, NULL); // point at which I insert the newline
3068 
3069  pBuf->aLines[pThis->iRow + 1] = WBCopyString(pTemp); // make a copy of previous at "that point"
3070  *pTemp = 0; // terminate line at "that point" (TODO: trim right?)
3071 
3072  WB_ERROR_PRINT("TEMPORARY: %s - split line into \"%s\", \"%s\"\n", __FUNCTION__,
3073  pBuf->aLines[pThis->iRow],
3074  pBuf->aLines[pThis->iRow + 1]);
3075 
3076 
3077  pThis->iRow++; // advance row position
3078  pThis->iCol = 0; // newline forces column 0, always
3079 
3080  }
3081  }
3082  }
3083  }
3084  }
3085 
3086  __internal_invalidate_rect(pThis, NULL, 1); // so invalidate everything (for NOW)
3087  }
3088 
3089  // auto-hscroll while inserting text, make sure cursor position is visible
3090 
3091  if(pThis->rctView.right > pThis->rctView.left)
3092  {
3093  if(iMultiLine ||
3094  pThis->rctView.right <= pThis->iCol ||
3095  pThis->rctView.left > pThis->iCol)
3096  {
3097  int iAutoScrollWidth = AUTO_HSCROLL_SIZE;
3098 
3099  while(iAutoScrollWidth > 1 && iAutoScrollWidth >= pThis->rctView.right - pThis->rctView.left)
3100  {
3101  iAutoScrollWidth >>= 1;
3102  }
3103 
3104  // scroll left/right to expose the cursor
3105 
3106  while(pThis->rctView.left > pThis->iCol)
3107  {
3108  pThis->rctView.left -= iAutoScrollWidth;
3109  pThis->rctView.right -= iAutoScrollWidth;
3110  }
3111  while(pThis->rctView.right <= pThis->iCol)
3112  {
3113  pThis->rctView.left += iAutoScrollWidth;
3114  pThis->rctView.right += iAutoScrollWidth;
3115  }
3116 
3117  __internal_invalidate_rect(pThis, NULL, 1); // scrolling, so invalidate everything
3118  }
3119  else
3120  {
3121  __internal_invalidate_rect(pThis, &rctInvalid, 1); // invalidate bounding rectangle
3122  }
3123  }
3124  else
3125  {
3126  __internal_invalidate_rect(pThis, NULL, 1); // invalidate everything (confusion handler)
3127  }
3128  }
3129 }
3130 static void __internal_indent(struct _text_object_ *pThis, int nCol)
3131 {
3132  if(WBIsValidTextObject(pThis))
3133  {
3134  WB_ERROR_PRINT("TODO: %s - implement 'indent'\n", __FUNCTION__);
3135  }
3136 }
3137 static int __internal_can_undo(struct _text_object_ *pThis)
3138 {
3139  if(WBIsValidTextObject(pThis))
3140  {
3141  }
3142  return 0; // for now
3143 }
3144 static void __internal_undo(struct _text_object_ *pThis)
3145 {
3146  if(WBIsValidTextObject(pThis))
3147  {
3148  WB_ERROR_PRINT("TODO: %s - implement 'undo'\n", __FUNCTION__);
3149  }
3150 }
3151 static int __internal_can_redo(struct _text_object_ *pThis)
3152 {
3153  if(WBIsValidTextObject(pThis))
3154  {
3155  }
3156  return 0; // for now
3157 }
3158 static void __internal_redo(struct _text_object_ *pThis)
3159 {
3160  if(WBIsValidTextObject(pThis))
3161  {
3162  WB_ERROR_PRINT("TODO: %s - implement 'redo'\n", __FUNCTION__);
3163  }
3164 }
3165 static void __internal_get_view(const struct _text_object_ *pThis, WB_RECT *pRct)
3166 {
3167  if(WBIsValidTextObject(pThis))
3168  {
3169  }
3170 }
3171 static void __internal_set_view(struct _text_object_ *pThis, const WB_RECT *pRct)
3172 {
3173  if(WBIsValidTextObject(pThis))
3174  {
3175  }
3176 }
3177 static void __internal_begin_highlight(struct _text_object_ *pThis)
3178 {
3179  if(WBIsValidTextObject(pThis))
3180  {
3181  pThis->iDragState = DragState_CURSOR;
3182  }
3183 }
3184 static void __internal_end_highlight(struct _text_object_ *pThis)
3185 {
3186  if(WBIsValidTextObject(pThis))
3187  {
3188  pThis->iDragState = DragState_NONE;
3189  }
3190 }
3191 static void __internal_mouse_click(struct _text_object_ *pThis, int iMouseXDelta, int iMouseYDelta, int iType, int iACS)
3192 {
3193 TEXT_BUFFER *pBuf;
3194 int iRow = -1, iCol = -1; // pre-assign error returns
3195 int iFontHeight;
3196 WB_RECT rctSel;
3197 
3198  // NOTE: if this has never been painted, I can't respond to this request
3199 
3200  if(WBIsValidTextObject(pThis))
3201  {
3202  pBuf = (TEXT_BUFFER *)(pThis->pText);
3203 
3204  memset(&rctSel, 0, sizeof(rctSel));
3205 
3206  if(!(pThis->iDragState & DragState_MOUSE)) // not mouse-dragging at the moment
3207  {
3208  // clear the selection if there is one
3209  if(!SEL_RECT_EMPTY(pThis))
3210  {
3211  __internal_invalidate_rect(pThis, &(pThis->rctHighLight), 0); // invalidate the highlight rectangle
3212  memset(&(pThis->rctSel), 0, sizeof(pThis->rctSel));
3213  memset(&(pThis->rctHighLight), 0, sizeof(pThis->rctHighLight));
3214  }
3215  }
3216 
3217  iFontHeight = pThis->iAsc + pThis->iDesc;
3218 
3219  if(iFontHeight && pThis->iFontWidth &&
3220  pThis->rctWinView.left < pThis->rctWinView.right &&
3221  pThis->rctWinView.top < pThis->rctWinView.bottom)
3222  {
3223  if(pThis->iLineFeed == LineFeed_NONE) // i.e. "single line"
3224  {
3225  iRow = 0; // always
3226  }
3227  else
3228  {
3229  iFontHeight = WBTextObjectCalculateLineHeight(pThis->iAsc, pThis->iDesc);
3230 
3231  iRow = pThis->rctView.top
3232  + (iMouseYDelta + iFontHeight / 4 - pThis->rctWinView.top) / iFontHeight;
3233 
3234  if(iRow < 0 || !pBuf)
3235  {
3236  iRow = 0;
3237  }
3238  else if(iRow > pBuf->nEntries)
3239  {
3240  iRow = pBuf->nEntries;
3241  }
3242  }
3243 
3244  // column calculation is no different between single-line and multi-line
3245  iCol = pThis->rctView.left
3246  + (iMouseXDelta + pThis->iFontWidth / 4 - pThis->rctWinView.left) / pThis->iFontWidth;
3247 
3248  if(iCol < 0 || !pBuf || iRow >= pBuf->nEntries || !pBuf->aLines[iRow])
3249  {
3250  iCol = 0; // for now also force column 0 if there's no buffer or a NULL entry for it or at end of document
3251  }
3252  else if(pThis->iLineFeed == LineFeed_NONE &&
3253  iCol > WBGetMBLength(pBuf->aLines[iRow]))
3254  {
3255  iCol = WBGetMBLength(pBuf->aLines[iRow]); // don't select pos past end of line for single-line edit
3256  }
3257  }
3258 
3259  // if I am doing a mouse-drag, and the selection is empty, start a new selection.
3260  // if I am doing a cursor-drag, disable it.
3261 
3262  if(pThis->iDragState & DragState_CURSOR)
3263  {
3264  pThis->iDragState = DragState_NONE; // turn it off now.
3265 
3266  if(iType == WB_POINTER_BUTTON1 && iACS == 0) // only if left-click for now
3267  {
3268  pThis->iCol = iCol;
3269  pThis->iRow = iRow;
3270  }
3271  }
3272  else if(!(pThis->iDragState & DragState_MOUSE))
3273  {
3274  if(iType == WB_POINTER_BUTTON1 && iACS == 0) // only if left-click for now
3275  {
3276  pThis->iCol = iCol;
3277  pThis->iRow = iRow;
3278  }
3279  else
3280  {
3281  pThis->iDragState = DragState_NONE; // turn dragging off
3282 
3283  // TODO: check for wheel buttons WB_POINTER_BUTTON4 and WB_POINTER_BUTTON5 ?
3284  }
3285  }
3286  else if(iType == 0 && iACS == 0) // a mouse motion message while dragging
3287  {
3288  // see if the select rectangle is empty. If it is, then I
3289  // am just starting the drag and I need to anchor the cursor
3290 
3291  // NOTE: cursor anchor is always (left,top) even if it's a negative distance
3292 
3293  if(SEL_RECT_EMPTY(pThis))
3294  {
3295  pThis->rctSel.top = pThis->iRow; // old row
3296  pThis->rctSel.left = pThis->iCol; // previous column
3297  }
3298 
3299  pThis->rctSel.bottom = iRow; // new row
3300  pThis->rctSel.right = iCol; // new column
3301 
3302  pThis->iCol = iCol;
3303  pThis->iRow = iRow;
3304  }
3305  else
3306  {
3307  // TODO: cancel dragging? or not... [maybe check for right-click or scroll]
3308 
3309  // TODO: check for wheel buttons WB_POINTER_BUTTON4 and WB_POINTER_BUTTON5 ?
3310  // for a mouse drag I may enable this, but probably should just shut off the drag
3311  // maybe shift+wheel would select...? testing it shows that it scrolls the viewport
3312 
3313  pThis->iDragState = DragState_NONE; // turn dragging off, for now
3314  }
3315 
3316  // for now mouse-click invalidates the entire rectangle. Later I fix this.
3317 
3318  __internal_invalidate_rect(pThis, NULL, 1); // POOBAH
3319  }
3320 }
3321 static void __internal_begin_mouse_drag(struct _text_object_ *pThis)
3322 {
3323  if(WBIsValidTextObject(pThis))
3324  {
3325 // WB_ERROR_PRINT("TEMPORARY - %s\n", __FUNCTION__);
3326  pThis->iDragState = DragState_MOUSE;
3327  }
3328 }
3329 static void __internal_end_mouse_drag(struct _text_object_ *pThis)
3330 {
3331  if(WBIsValidTextObject(pThis))
3332  {
3333 // WB_ERROR_PRINT("TEMPORARY - %s\n", __FUNCTION__);
3334  pThis->iDragState = DragState_NONE;
3335  }
3336 }
3337 static void __internal_cursor_up(struct _text_object_ *pThis)
3338 {
3339 TEXT_BUFFER *pBuf;
3340 int iPageHeight;
3341 
3342 
3343  if(!WBIsValidTextObject(pThis))
3344  {
3345  WB_ERROR_PRINT("ERROR: %p - invalid text object %p\n", __FUNCTION__, pThis);
3346  }
3347  else
3348  {
3349  int iOldRow = pThis->iRow;
3350 
3351  pThis->iBlinkState = 0; // this affects the cursor blink, basically resetting it whenever I edit something
3352 
3353  if(pThis->iLineFeed == LineFeed_NONE) // single line
3354  {
3355  return; // do nothing
3356  }
3357 
3358  WB_ERROR_PRINT("TEMPORARY: %s - here I am at line %d\n", __FUNCTION__, __LINE__);
3359 
3360  __internal_invalidate_cursor(pThis, 0); // invalidate current cursor rectangle
3361 
3362  pBuf = (TEXT_BUFFER *)(pThis->pText);
3363 
3364  if(!pBuf)
3365  {
3366  pThis->iRow = 0;
3367  pThis->iCol = 0;
3368  }
3369  else if(pBuf->nEntries < pThis->iRow)
3370  {
3371  pThis->iRow = pBuf->nEntries;
3372  pThis->iCol = 0;
3373  }
3374  else if(pThis->iRow > 0)
3375  {
3376  pThis->iRow--;
3377  }
3378  else
3379  {
3380  pThis->iRow = 0;
3381  }
3382 
3383  if(pThis->iDragState & DragState_CURSOR)
3384  {
3385  if(SEL_RECT_EMPTY(pThis))
3386  {
3387  pThis->rctSel.left = pThis->iCol;
3388  pThis->rctSel.top = iOldRow;
3389  }
3390 
3391  pThis->rctSel.right = pThis->iCol;
3392  pThis->rctSel.bottom = pThis->iRow;
3393  }
3394  else
3395  {
3396  // clear the selection if there is one
3397  if(!SEL_RECT_EMPTY(pThis))
3398  {
3399  __internal_invalidate_rect(pThis, &(pThis->rctHighLight), 0); // invalidate the highlight rectangle
3400  memset(&(pThis->rctSel), 0, sizeof(pThis->rctSel));
3401  memset(&(pThis->rctHighLight), 0, sizeof(pThis->rctHighLight));
3402  }
3403  }
3404 
3405  if(pThis->rctView.top > pThis->iRow ||
3406  pThis->rctView.bottom <= pThis->iRow)
3407  {
3408  __internal_invalidate_rect(pThis, NULL, 1); // scrolling invalidates all
3409 
3410 
3411  iPageHeight = pThis->rctView.bottom - pThis->rctView.top - 1;
3412 
3413  // scroll up/down to expose the cursor
3414 
3415  if(iPageHeight > 0)
3416  {
3417  while(pThis->rctView.top > pThis->iRow)
3418  {
3419  pThis->rctView.top -= iPageHeight;
3420  pThis->rctView.bottom -= iPageHeight;
3421  }
3422 
3423  while(pThis->rctView.bottom <= pThis->iRow)
3424  {
3425  pThis->rctView.top += iPageHeight;
3426  pThis->rctView.bottom += iPageHeight;
3427  }
3428  }
3429  }
3430  else // re-calculate cursor metrics
3431  {
3432  pThis->iCursorY += WBTextObjectCalculateLineHeight(pThis->iAsc, pThis->iDesc)
3433  * (pThis->iRow - iOldRow); // will effectively subtract
3434 
3435 // (pThis->iAsc + pThis->iDesc
3436 // + pThis->iDesc > MIN_LINE_SPACING * 2 ?
3437 // pThis->iDesc / 2 : MIN_LINE_SPACING)
3438 // * (pThis->iRow - iOldRow);
3439 
3440  __internal_invalidate_cursor(pThis, 1); // invalidate the NEW cursor rectangle
3441  }
3442  }
3443 }
3444 static void __internal_cursor_down(struct _text_object_ *pThis)
3445 {
3446 TEXT_BUFFER *pBuf;
3447 int iPageHeight;
3448 
3449 
3450  if(!WBIsValidTextObject(pThis))
3451  {
3452  WB_ERROR_PRINT("ERROR: %p - invalid text object %p\n", __FUNCTION__, pThis);
3453  }
3454  else
3455  {
3456  int iOldRow = pThis->iRow;
3457 
3458  pThis->iBlinkState = 0; // this affects the cursor blink, basically resetting it whenever I edit something
3459 
3460  if(pThis->iLineFeed == LineFeed_NONE) // single line
3461  {
3462  return; // do nothing
3463  }
3464 
3465  WB_ERROR_PRINT("TEMPORARY: %s - here I am at line %d\n", __FUNCTION__, __LINE__);
3466 
3467  __internal_invalidate_cursor(pThis, 0); // invalidate current cursor rectangle
3468 
3469  pBuf = (TEXT_BUFFER *)(pThis->pText);
3470 
3471  if(!pBuf)
3472  {
3473  pThis->iRow = 0;
3474  pThis->iCol = 0;
3475  }
3476  else if(pThis->iRow >= (pBuf->nEntries - 1))
3477  {
3478  pThis->iRow = pBuf->nEntries;
3479  pThis->iCol = 0;
3480  }
3481  else
3482  {
3483  pThis->iRow++;
3484  }
3485 
3486  if(pThis->iDragState & DragState_CURSOR)
3487  {
3488  if(SEL_RECT_EMPTY(pThis))
3489  {
3490  pThis->rctSel.left = pThis->iCol;
3491  pThis->rctSel.top = iOldRow;
3492  }
3493 
3494  pThis->rctSel.right = pThis->iCol;
3495  pThis->rctSel.bottom = pThis->iRow;
3496  }
3497  else
3498  {
3499  // clear the selection if there is one
3500  if(!SEL_RECT_EMPTY(pThis))
3501  {
3502  __internal_invalidate_rect(pThis, &(pThis->rctHighLight), 0); // invalidate the highlight rectangle
3503  memset(&(pThis->rctSel), 0, sizeof(pThis->rctSel));
3504  memset(&(pThis->rctHighLight), 0, sizeof(pThis->rctHighLight));
3505  }
3506  }
3507 
3508  if(pThis->rctView.top > pThis->iRow ||
3509  pThis->rctView.bottom <= pThis->iRow)
3510  {
3511  __internal_invalidate_rect(pThis, NULL, 1); // scrolling invalidates all
3512 
3513 
3514  iPageHeight = pThis->rctView.bottom - pThis->rctView.top - 1;
3515 
3516  // scroll up/down to expose the cursor
3517 
3518  if(iPageHeight > 0)
3519  {
3520  while(pThis->rctView.top > pThis->iRow)
3521  {
3522  pThis->rctView.top -= iPageHeight;
3523  pThis->rctView.bottom -= iPageHeight;
3524  }
3525 
3526  while(pThis->rctView.bottom <= pThis->iRow)
3527  {
3528  pThis->rctView.top += iPageHeight;
3529  pThis->rctView.bottom += iPageHeight;
3530  }
3531  }
3532  }
3533  else // re-calculate cursor metrics
3534  {
3535  pThis->iCursorY += WBTextObjectCalculateLineHeight(pThis->iAsc, pThis->iDesc)
3536  * (pThis->iRow - iOldRow);
3537 
3538 // (pThis->iAsc + pThis->iDesc
3539 // + pThis->iDesc > MIN_LINE_SPACING * 2 ?
3540 // pThis->iDesc / 2 : MIN_LINE_SPACING)
3541 // * (pThis->iRow - iOldRow);
3542 
3543  __internal_invalidate_cursor(pThis, 1); // invalidate the NEW cursor rectangle
3544  }
3545  }
3546 }
3547 static void __internal_cursor_left(struct _text_object_ *pThis)
3548 {
3549 TEXT_BUFFER *pBuf;
3550 
3551 
3552  if(!WBIsValidTextObject(pThis))
3553  {
3554  WB_ERROR_PRINT("ERROR: %p - invalid text object %p\n", __FUNCTION__, pThis);
3555  }
3556  else
3557  {
3558  int iOldCol = pThis->iCol;
3559 
3560  pThis->iBlinkState = 0; // this affects the cursor blink, basically resetting it whenever I edit something
3561 
3562  __internal_invalidate_cursor(pThis, 0); // invalidate current cursor rectangle
3563 
3564  pBuf = (TEXT_BUFFER *)(pThis->pText);
3565 
3566  if(!pBuf)
3567  {
3568  pThis->iRow = 0;
3569  pThis->iCol = 0;
3570  }
3571  else if(pThis->iCol > 0)
3572  {
3573  pThis->iCol --;
3574  }
3575  else
3576  {
3577  pThis->iCol = 0;
3578  }
3579 
3580  if(pThis->iDragState & DragState_CURSOR)
3581  {
3582  if(SEL_RECT_EMPTY(pThis))
3583  {
3584  pThis->rctSel.left = iOldCol;
3585  pThis->rctSel.top = pThis->iRow;
3586  }
3587 
3588  pThis->rctSel.right = pThis->iCol;
3589  pThis->rctSel.bottom = pThis->iRow;
3590  }
3591  else
3592  {
3593  // clear the selection if there is one
3594  if(!SEL_RECT_EMPTY(pThis))
3595  {
3596  __internal_invalidate_rect(pThis, &(pThis->rctHighLight), 0); // invalidate the highlight rectangle
3597  memset(&(pThis->rctSel), 0, sizeof(pThis->rctSel));
3598  memset(&(pThis->rctHighLight), 0, sizeof(pThis->rctHighLight));
3599  }
3600  }
3601 
3602  if(pThis->rctView.right > pThis->rctView.left)
3603  {
3604  if(pThis->rctView.right <= pThis->iCol ||
3605  pThis->rctView.left > pThis->iCol)
3606  {
3607  int iAutoScrollWidth = AUTO_HSCROLL_SIZE;
3608 
3609  while(iAutoScrollWidth > 1 && iAutoScrollWidth >= pThis->rctView.right - pThis->rctView.left)
3610  {
3611  iAutoScrollWidth >>= 1;
3612  }
3613 
3614  // scroll left/right to expose the cursor
3615 
3616  while(pThis->rctView.left > pThis->iCol)
3617  {
3618  pThis->rctView.left -= iAutoScrollWidth;
3619  pThis->rctView.right -= iAutoScrollWidth;
3620  }
3621  while(pThis->rctView.right <= pThis->iCol)
3622  {
3623  pThis->rctView.left += iAutoScrollWidth;
3624  pThis->rctView.right += iAutoScrollWidth;
3625  }
3626 
3627  __internal_invalidate_rect(pThis, NULL, 1); // invalidate entire screen if I'm here
3628  }
3629  else // re-calculate cursor metrics
3630  {
3631  pThis->iCursorX += (pThis->iCol - iOldCol) * pThis->iFontWidth; // will effectively subtract
3632 
3633  __internal_invalidate_cursor(pThis, 1); // invalidate the NEW cursor rectangle
3634  }
3635  }
3636  else
3637  {
3638  __internal_invalidate_rect(pThis, NULL, 1); // invalidate entire screen if I'm here (confusion point)
3639  }
3640  }
3641 }
3642 static void __internal_cursor_right(struct _text_object_ *pThis)
3643 {
3644 TEXT_BUFFER *pBuf;
3645 
3646 
3647  if(!WBIsValidTextObject(pThis))
3648  {
3649  WB_ERROR_PRINT("ERROR: %p - invalid text object %p\n", __FUNCTION__, pThis);
3650  }
3651  else
3652  {
3653  int iOldCol = pThis->iCol;
3654 
3655  pThis->iBlinkState = 0; // this affects the cursor blink, basically resetting it whenever I edit something
3656 
3657  __internal_invalidate_cursor(pThis, 0); // invalidate current cursor rectangle
3658 
3659  pBuf = (TEXT_BUFFER *)(pThis->pText);
3660 
3661  if(!pBuf)
3662  {
3663  pThis->iRow = 0;
3664  pThis->iCol = 0;
3665  }
3666  else if(pThis->iCol < 0)
3667  {
3668  pThis->iCol = 0;
3669  }
3670  else if(pThis->iCol < INT_MAX)
3671  {
3672  if(pThis->iLineFeed == LineFeed_NONE) // single line
3673  {
3674  if(pBuf->nEntries <= 0 || !pBuf->aLines[0])
3675  {
3676  pThis->iCol = 0;
3677  }
3678  else
3679  {
3680  int iLen = WBGetMBLength(pBuf->aLines[0]);
3681 
3682  if(pThis->iRow != 0)
3683  {
3684  pThis->iRow = 0; // force it
3685  }
3686 
3687  // no 'virtual space' for single-line but trailing white space OK
3688 
3689  if(pThis->iCol >= iLen)
3690  {
3691  pThis->iCol = iLen;
3692  }
3693  else
3694  {
3695  pThis->iCol++;
3696  }
3697  }
3698  }
3699  else
3700  {
3701  pThis->iCol ++;
3702  }
3703  }
3704 
3705  if(pThis->iDragState & DragState_CURSOR)
3706  {
3707  if(SEL_RECT_EMPTY(pThis))
3708  {
3709  pThis->rctSel.left = iOldCol;
3710  pThis->rctSel.top = pThis->iRow;
3711  }
3712 
3713  pThis->rctSel.right = pThis->iCol;
3714  pThis->rctSel.bottom = pThis->iRow;
3715  }
3716  else
3717  {
3718  // clear the selection if there is one
3719  if(!SEL_RECT_EMPTY(pThis))
3720  {
3721  __internal_invalidate_rect(pThis, &(pThis->rctHighLight), 0); // invalidate the highlight rectangle
3722  memset(&(pThis->rctSel), 0, sizeof(pThis->rctSel));
3723  memset(&(pThis->rctHighLight), 0, sizeof(pThis->rctHighLight));
3724  }
3725  }
3726 
3727  if(pThis->rctView.right > pThis->rctView.left)
3728  {
3729  if(pThis->rctView.right <= pThis->iCol ||
3730  pThis->rctView.left > pThis->iCol)
3731  {
3732  int iAutoScrollWidth = AUTO_HSCROLL_SIZE;
3733 
3734  while(iAutoScrollWidth > 1 && iAutoScrollWidth >= pThis->rctView.right - pThis->rctView.left)
3735  {
3736  iAutoScrollWidth >>= 1;
3737  }
3738 
3739  // scroll left/right to expose the cursor
3740 
3741  while(pThis->rctView.left > pThis->iCol)
3742  {
3743  pThis->rctView.left -= iAutoScrollWidth;
3744  pThis->rctView.right -= iAutoScrollWidth;
3745  }
3746  while(pThis->rctView.right <= pThis->iCol)
3747  {
3748  pThis->rctView.left += iAutoScrollWidth;
3749  pThis->rctView.right += iAutoScrollWidth;
3750  }
3751 
3752  __internal_invalidate_rect(pThis, NULL, 1); // invalidate entire screen if I'm here
3753  }
3754  else // re-calculate cursor metrics
3755  {
3756  pThis->iCursorX += (pThis->iCol - iOldCol) * pThis->iFontWidth;
3757 
3758  __internal_invalidate_cursor(pThis, 1); // invalidate the NEW cursor rectangle
3759  }
3760  }
3761  else
3762  {
3763  __internal_invalidate_rect(pThis, NULL, 1); // invalidate entire screen if I'm here (confusion point)
3764  }
3765  }
3766 }
3767 static void __internal_page_up(struct _text_object_ *pThis)
3768 {
3769  if(!WBIsValidTextObject(pThis))
3770  {
3771  WB_ERROR_PRINT("ERROR: %p - invalid text object %p\n", __FUNCTION__, pThis);
3772  }
3773  else
3774  {
3775  pThis->iBlinkState = 0; // this affects the cursor blink, basically resetting it whenever I edit something
3776 
3777 
3778  __internal_invalidate_rect(pThis, NULL, 1); // invalidate entire screen if I'm here
3779  }
3780 }
3781 static void __internal_page_down(struct _text_object_ *pThis)
3782 {
3783  if(!WBIsValidTextObject(pThis))
3784  {
3785  WB_ERROR_PRINT("ERROR: %p - invalid text object %p\n", __FUNCTION__, pThis);
3786  }
3787  else
3788  {
3789  pThis->iBlinkState = 0; // this affects the cursor blink, basically resetting it whenever I edit something
3790 
3791 
3792  __internal_invalidate_rect(pThis, NULL, 1); // invalidate entire screen if I'm here
3793  }
3794 }
3795 static void __internal_page_left(struct _text_object_ *pThis)
3796 {
3797  if(!WBIsValidTextObject(pThis))
3798  {
3799  WB_ERROR_PRINT("ERROR: %p - invalid text object %p\n", __FUNCTION__, pThis);
3800  }
3801  else
3802  {
3803  pThis->iBlinkState = 0; // this affects the cursor blink, basically resetting it whenever I edit something
3804 
3805 
3806  __internal_invalidate_rect(pThis, NULL, 1); // invalidate entire screen if I'm here
3807  }
3808 }
3809 static void __internal_page_right(struct _text_object_ *pThis)
3810 {
3811  if(!WBIsValidTextObject(pThis))
3812  {
3813  WB_ERROR_PRINT("ERROR: %p - invalid text object %p\n", __FUNCTION__, pThis);
3814  }
3815  else
3816  {
3817  pThis->iBlinkState = 0; // this affects the cursor blink, basically resetting it whenever I edit something
3818 
3819 
3820  __internal_invalidate_rect(pThis, NULL, 1); // invalidate entire screen if I'm here
3821  }
3822 }
3823 
3824 static void __internal_cursor_home(struct _text_object_ *pThis)
3825 {
3826 const char *pL, *p2;
3827 TEXT_BUFFER *pBuf;
3828 
3829 
3830  if(!WBIsValidTextObject(pThis))
3831  {
3832  WB_ERROR_PRINT("ERROR: %p - invalid text object %p\n", __FUNCTION__, pThis);
3833  }
3834  else
3835  {
3836  int iOldCol = pThis->iCol;
3837 
3838  pThis->iBlinkState = 0; // this affects the cursor blink, basically resetting it whenever I edit something
3839 
3840  __internal_invalidate_cursor(pThis, 0); // invalidate current cursor rectangle
3841 
3842  pBuf = (TEXT_BUFFER *)(pThis->pText);
3843 
3844  if(!pBuf || pThis->iRow >= pBuf->nEntries || pThis->iCol > 0)
3845  {
3846  pThis->iCol = 0;
3847  }
3848  else
3849  {
3850  // find first non-white-space character and assign to THAT
3851  // TODO: handle hard tab translation? For now "leave it"
3852 
3853  pL = pBuf->aLines[pThis->iRow];
3854 
3855  if(pL)
3856  {
3857  p2 = pL;
3858  while(*p2 && *p2 <= ' ') // TODO: adjust for 'hard tab' char which might be 'U+00A0'...
3859  {
3860  p2++;
3861  }
3862 
3863  if(*p2)
3864  {
3865  pThis->iCol = (int)(p2 - pL); // toggling 'beginning of line' and column 0
3866  }
3867  }
3868  }
3869 
3870  if(pThis->iDragState & DragState_CURSOR)
3871  {
3872  if(SEL_RECT_EMPTY(pThis))
3873  {
3874  pThis->rctSel.left = iOldCol;
3875  pThis->rctSel.top = pThis->iRow;
3876  }
3877 
3878  pThis->rctSel.right = pThis->iCol;
3879  pThis->rctSel.bottom = pThis->iRow;
3880  }
3881  else
3882  {
3883  // clear the selection if there is one
3884  if(!SEL_RECT_EMPTY(pThis))
3885  {
3886  __internal_invalidate_rect(pThis, &(pThis->rctHighLight), 0); // invalidate the highlight rectangle
3887  memset(&(pThis->rctSel), 0, sizeof(pThis->rctSel));
3888  memset(&(pThis->rctHighLight), 0, sizeof(pThis->rctHighLight));
3889  }
3890  }
3891 
3892  if(pThis->rctView.right > pThis->rctView.left)
3893  {
3894  if(pThis->rctView.right <= pThis->iCol ||
3895  pThis->rctView.left > pThis->iCol)
3896  {
3897  int iAutoScrollWidth = AUTO_HSCROLL_SIZE;
3898 
3899  while(iAutoScrollWidth > 1 && iAutoScrollWidth >= pThis->rctView.right - pThis->rctView.left)
3900  {
3901  iAutoScrollWidth >>= 1;
3902  }
3903 
3904  // scroll left/right to expose the cursor
3905 
3906  while(pThis->rctView.left > pThis->iCol)
3907  {
3908  pThis->rctView.left -= iAutoScrollWidth;
3909  pThis->rctView.right -= iAutoScrollWidth;
3910  }
3911  while(pThis->rctView.right <= pThis->iCol)
3912  {
3913  pThis->rctView.left += iAutoScrollWidth;
3914  pThis->rctView.right += iAutoScrollWidth;
3915  }
3916 
3917  __internal_invalidate_rect(pThis, NULL, 1); // invalidate entire screen if I'm here
3918  }
3919  else // re-calculate cursor metrics
3920  {
3921  pThis->iCursorX += (pThis->iCol - iOldCol) * pThis->iFontWidth;
3922 
3923  __internal_invalidate_cursor(pThis, 1); // invalidate the NEW cursor rectangle
3924  }
3925  }
3926  else
3927  {
3928  __internal_invalidate_rect(pThis, NULL, 1); // invalidate entire screen if I'm here (confusion point)
3929  }
3930  }
3931 }
3932 
3933 static void __internal_cursor_end(struct _text_object_ *pThis)
3934 {
3935 const char *pL;
3936 TEXT_BUFFER *pBuf;
3937 
3938 
3939  if(!WBIsValidTextObject(pThis))
3940  {
3941  WB_ERROR_PRINT("ERROR: %p - invalid text object %p\n", __FUNCTION__, pThis);
3942  }
3943  else
3944  {
3945  int iOldCol = pThis->iCol;
3946 
3947  pThis->iBlinkState = 0; // this affects the cursor blink, basically resetting it whenever I edit something
3948 
3949  __internal_invalidate_cursor(pThis, 0); // invalidate current cursor rectangle
3950 
3951  pBuf = (TEXT_BUFFER *)(pThis->pText);
3952 
3953  if(!pBuf || pThis->iRow >= pBuf->nEntries)
3954  {
3955  pThis->iCol = 0;
3956  }
3957  else
3958  {
3959  pL = pBuf->aLines[pThis->iRow];
3960 
3961  if(!pL)
3962  {
3963  pThis->iCol = 0;
3964  }
3965  else
3966  {
3967  pThis->iCol = WBGetMBLength(pL);
3968  }
3969  }
3970 
3971  if(pThis->iDragState & DragState_CURSOR)
3972  {
3973  if(SEL_RECT_EMPTY(pThis))
3974  {
3975  pThis->rctSel.left = iOldCol;
3976  pThis->rctSel.top = pThis->iRow;
3977  }
3978 
3979  pThis->rctSel.right = pThis->iCol;
3980  pThis->rctSel.bottom = pThis->iRow;
3981  }
3982  else
3983  {
3984  // clear the selection if there is one
3985  if(!SEL_RECT_EMPTY(pThis))
3986  {
3987  __internal_invalidate_rect(pThis, &(pThis->rctHighLight), 0); // invalidate the highlight rectangle
3988  memset(&(pThis->rctSel), 0, sizeof(pThis->rctSel));
3989  memset(&(pThis->rctHighLight), 0, sizeof(pThis->rctHighLight));
3990  }
3991  }
3992 
3993  if(pThis->rctView.right > pThis->rctView.left)
3994  {
3995  if(pThis->rctView.right <= pThis->iCol ||
3996  pThis->rctView.left > pThis->iCol)
3997  {
3998  int iAutoScrollWidth = AUTO_HSCROLL_SIZE;
3999 
4000  while(iAutoScrollWidth > 1 && iAutoScrollWidth >= pThis->rctView.right - pThis->rctView.left)
4001  {
4002  iAutoScrollWidth >>= 1;
4003  }
4004 
4005  // scroll left/right to expose the cursor
4006 
4007  while(pThis->rctView.left > pThis->iCol)
4008  {
4009  pThis->rctView.left -= iAutoScrollWidth;
4010  pThis->rctView.right -= iAutoScrollWidth;
4011  }
4012  while(pThis->rctView.right <= pThis->iCol)
4013  {
4014  pThis->rctView.left += iAutoScrollWidth;
4015  pThis->rctView.right += iAutoScrollWidth;
4016  }
4017 
4018  __internal_invalidate_rect(pThis, NULL, 1); // invalidate entire screen if I'm here
4019  }
4020  else // re-calculate cursor metrics
4021  {
4022  pThis->iCursorX += (pThis->iCol - iOldCol) * pThis->iFontWidth;
4023 
4024  __internal_invalidate_cursor(pThis, 1); // invalidate the NEW cursor rectangle
4025  }
4026  }
4027  else
4028  {
4029  __internal_invalidate_rect(pThis, NULL, 1); // invalidate entire screen if I'm here (confusion point)
4030  }
4031  }
4032 }
4033 
4034 static void __internal_cursor_top(struct _text_object_ *pThis)
4035 {
4036 int iPageHeight;
4037 
4038  if(!WBIsValidTextObject(pThis))
4039  {
4040  WB_ERROR_PRINT("ERROR: %p - invalid text object %p\n", __FUNCTION__, pThis);
4041  }
4042  else
4043  {
4044  int iOldRow = pThis->iRow;
4045 
4046  pThis->iBlinkState = 0; // this affects the cursor blink, basically resetting it whenever I edit something
4047 
4048  pThis->iRow = 0;
4049  pThis->iCol = 0;
4050 
4051  iPageHeight = pThis->rctView.bottom - pThis->rctView.top - 1;
4052 
4053  if(pThis->iDragState & DragState_CURSOR)
4054  {
4055  if(SEL_RECT_EMPTY(pThis))
4056  {
4057  pThis->rctSel.left = pThis->iCol;
4058  pThis->rctSel.top = iOldRow;
4059  }
4060 
4061  pThis->rctSel.right = pThis->iCol;
4062  pThis->rctSel.bottom = pThis->iRow;
4063  }
4064  else
4065  {
4066  // clear the selection if there is one
4067  if(!SEL_RECT_EMPTY(pThis))
4068  {
4069  __internal_invalidate_rect(pThis, &(pThis->rctHighLight), 0); // invalidate the highlight rectangle
4070  memset(&(pThis->rctSel), 0, sizeof(pThis->rctSel));
4071  memset(&(pThis->rctHighLight), 0, sizeof(pThis->rctHighLight));
4072  }
4073  }
4074 
4075  // scroll up/down to expose the cursor
4076 
4077  if(iPageHeight > 0)
4078  {
4079  while(pThis->rctView.top > pThis->iRow)
4080  {
4081  pThis->rctView.top -= iPageHeight;
4082  pThis->rctView.bottom -= iPageHeight;
4083  }
4084 
4085  while(pThis->rctView.bottom <= pThis->iRow)
4086  {
4087  pThis->rctView.top += iPageHeight;
4088  pThis->rctView.bottom += iPageHeight;
4089  }
4090  }
4091  }
4092 }
4093 
4094 static void __internal_cursor_bottom(struct _text_object_ *pThis)
4095 {
4096 TEXT_BUFFER *pBuf;
4097 int iPageHeight;
4098 
4099  if(!WBIsValidTextObject(pThis))
4100  {
4101  WB_ERROR_PRINT("ERROR: %p - invalid text object %p\n", __FUNCTION__, pThis);
4102  }
4103  else
4104  {
4105  int iOldRow = pThis->iRow;
4106 
4107  pThis->iBlinkState = 0; // this affects the cursor blink, basically resetting it whenever I edit something
4108 
4109  pBuf = (TEXT_BUFFER *)(pThis->pText);
4110 
4111  if(pBuf)
4112  {
4113  pThis->iRow = pBuf->nEntries; // last line, which has no text
4114  pThis->iCol = 0;
4115  }
4116  else
4117  {
4118  pThis->iRow = 0;
4119  pThis->iCol = 0;
4120  }
4121 
4122  if(pThis->iDragState & DragState_CURSOR)
4123  {
4124  if(SEL_RECT_EMPTY(pThis))
4125  {
4126  pThis->rctSel.left = pThis->iCol;
4127  pThis->rctSel.top = iOldRow;
4128  }
4129 
4130  pThis->rctSel.right = pThis->iCol;
4131  pThis->rctSel.bottom = pThis->iRow;
4132  }
4133  else
4134  {
4135  // clear the selection if there is one
4136  if(!SEL_RECT_EMPTY(pThis))
4137  {
4138  __internal_invalidate_rect(pThis, &(pThis->rctHighLight), 0); // invalidate the highlight rectangle
4139  memset(&(pThis->rctSel), 0, sizeof(pThis->rctSel));
4140  memset(&(pThis->rctHighLight), 0, sizeof(pThis->rctHighLight));
4141  }
4142  }
4143 
4144  iPageHeight = pThis->rctView.bottom - pThis->rctView.top - 1;
4145 
4146  // scroll up/down to expose the cursor
4147 
4148  if(iPageHeight > 0)
4149  {
4150  while(pThis->rctView.top > pThis->iRow)
4151  {
4152  pThis->rctView.top -= iPageHeight;
4153  pThis->rctView.bottom -= iPageHeight;
4154  }
4155 
4156  while(pThis->rctView.bottom <= pThis->iRow)
4157  {
4158  pThis->rctView.top += iPageHeight;
4159  pThis->rctView.bottom += iPageHeight;
4160  }
4161  }
4162  }
4163 }
4164 
4165 static void __internal_do_expose(struct _text_object_ *pThis, Display *pDisplay, Window wID,
4166  GC gc, const WB_GEOM *pPaintGeom, const WB_GEOM *pViewGeom,
4167  XFontSet rFontSet)
4168 {
4169 TEXT_BUFFER *pBuf;
4170 WB_GEOM geomV, geomP, geomC;
4171 XFontStruct *pFont = NULL, **ppFonts = NULL;
4172 char **ppNames, *pL;
4173 int i1, iLen, iFontHeight, iFontWidth, nFonts, iAsc, iDesc, iX, iY, iPX, iPY;
4174 int iXDelta = 0, iYDelta = 0;
4175 XFontSet fSet;
4176 Pixmap pxTemp;
4177 unsigned long clrFG, clrBG, clrHFG, clrHBG;
4178 GC gc2 = None;
4179 WB_RECT rctSel; // the NORMALIZED selection rectangle (calculated)
4180 
4181 
4182  if(pViewGeom)
4183  {
4184  memcpy(&geomV, pViewGeom, sizeof(geomV));
4185  }
4186  else
4187  {
4188  WBGetWindowGeom(wID, &geomV);
4189 
4190  geomV.x = geomV.y = 0; // ALWAYS (make sure) - it's client coords now
4191 
4192  geomV.y += MIN_BORDER_SPACING; // need a minimum top/bottom border as well
4193  geomV.height -= MIN_BORDER_SPACING * 2;
4194  }
4195 
4196  // in all cases, make room on right/left edges for the cursor
4197 
4198  geomV.x += MIN_BORDER_SPACING; // required for cursor
4199  geomV.width -= MIN_BORDER_SPACING * 2;
4200 
4201  // TODO: make use of geomV's "border" parameter??
4202 
4203  if(geomV.width <= MIN_BORDER_SPACING * 2 || // absolute minimum width
4204  geomV.height <= MIN_BORDER_SPACING * 2) // absolute minimum height
4205  {
4206  return; // can't do window that's way too small
4207  }
4208 
4209  if(pPaintGeom)
4210  {
4211  memcpy(&geomP, pPaintGeom, sizeof(geomP));
4212  }
4213  else
4214  {
4215  memcpy(&geomP, &geomV, sizeof(geomV));
4216  }
4217 
4218  if(rFontSet == None) /* NULL? */
4219  {
4220  pFont = WBGetGCFont(pDisplay, gc);
4221 
4222  if(!pFont)
4223  {
4224  WB_ERROR_PRINT("%s - ERROR: WBGetGCFont returns NULL\n", __FUNCTION__);
4225 
4226  return; // bad
4227  }
4228 
4229  fSet = WBFontSetFromFont(pDisplay, pFont); // convert font to font set
4230 
4231  XFreeFont(pDisplay, pFont); // free it now (important to do so)
4232  pFont = NULL;
4233 
4234  if(fSet == None)
4235  {
4236  WB_ERROR_PRINT("%s - ERROR: WBFontSetFromFont returns None\n", __FUNCTION__);
4237 
4238  return; // bad
4239  }
4240  }
4241  else
4242  {
4243  fSet = rFontSet; // I must check for this before I free fSet later
4244  }
4245 
4246  // at this point I must use 'fSet' to do all of the font metrics + drawing
4247 
4248  // NOTE: doing the call directly instead of calling WBFontSetAscent() etc. so only loop once
4249 
4250  nFonts = XFontsOfFontSet(fSet, &ppFonts, &ppNames); // returns # of items (do NOT free resources! They are owned by the font set)
4251 
4252  // TODO: XFontStruct::direction indicates LTR RTL TTB or BTT for painting - for now _ONLY_ LTR_TTB will apply
4253 
4254  if(nFonts <= 0 || !ppFonts) // no fonts?
4255  {
4256  WB_ERROR_PRINT("%s - ERROR: XFontsOfFontSet returns %d\n", __FUNCTION__, nFonts);
4257 
4258  goto the_end;
4259  }
4260 
4261  // to find the floor of the font, I'll need to determine max iAsc and iDesc
4262 
4263  for(i1=0, iAsc=0, iDesc=0; i1 < nFonts; i1++)
4264  {
4265  if(iAsc < ppFonts[i1]->ascent)
4266  {
4267  iAsc = ppFonts[i1]->ascent;
4268  }
4269  if(iDesc < ppFonts[i1]->descent)
4270  {
4271  iDesc = ppFonts[i1]->descent;
4272  }
4273  }
4274 
4275  // NOW get the font width for a space (TODO: average char width instead?)
4276 
4277  iFontWidth = WB_TEXT_ESCAPEMENT(fSet, " ", 1);
4278 
4279  // get FG/BG color information
4280 
4281  clrFG = WBGetGCFGColor(pDisplay, gc);
4282  clrBG = WBGetGCBGColor(pDisplay, gc);
4283 
4284  // TODO: compare clrHFG and clrHBG to a "NULL" XColor?
4285  if(!memcmp(&pThis->clrHFG, &pThis->clrHBG, sizeof(XColor))) // not assigned (they should never match)
4286  {
4287  clrHBG = clrFG;
4288  clrHFG = clrBG;
4289  }
4290  else
4291  {
4292  clrHBG = pThis->clrHBG.pixel;
4293  clrHFG = pThis->clrHFG.pixel;
4294  }
4295 
4296  // make a copy of the GC since I'm probably going to mess with it
4297 
4298  gc2 = XCreateGC(pDisplay, wID, 0, NULL);
4299 
4300  if(gc2 != None)
4301  {
4302  // NOTE: docs wrong for XCopyGC, header correct - see WBBeginPaint()
4303  if(!XCopyGC(pDisplay, gc, GCAll, gc2))
4304  {
4305  XFreeGC(pDisplay, gc2);
4306  gc2 = None;
4307  }
4308  }
4309 
4310  // font metrics complete. Now THAT wasn't so bad, was it? OK it was but only need to do it here
4311 
4312  if(WBIsValidTextObject(pThis))
4313  {
4314  // cache the window ID
4315  pThis->wIDOwner = wID;
4316 
4317  // cache the font metrics
4318  pThis->iAsc = iAsc;
4319  pThis->iDesc = iDesc;
4320  pThis->iFontWidth = iFontWidth;
4321 
4322  iFontHeight = iAsc + iDesc; // does not include interline spacing (that comes later for multi-line only)
4323 
4324 
4325  // keep track of the total viewport in window coordinates.
4326  // this will be used later for mouse translation
4327 
4328  pThis->rctWinView.left = geomV.x;
4329  pThis->rctWinView.top = geomV.y;
4330  pThis->rctWinView.right = pThis->rctWinView.left + geomV.width;
4331  pThis->rctWinView.bottom = pThis->rctWinView.top + geomV.height;
4332 
4333  // cache the pointer to the TEXT BUFFER
4334  pBuf = (TEXT_BUFFER *)(pThis->pText);
4335 
4336 
4337  // ------------------------------------------
4338  // CALCULATING THE CORRECT VIEWPORT (rctView)
4339  // ------------------------------------------
4340  //
4341  // NOTE: if the owning window has reset the viewport, the entire window SHOULD
4342  // be invalid. If not, it won't paint properly.
4343 
4344  if(pThis->iLineFeed == LineFeed_NONE || // SINGLE LINE
4345  !pBuf || pBuf->nEntries <= 0) // also test for this condition and treat it as single-line also (temporary)
4346  {
4347  // AUTO-ASSIGN the viewport whenever right <= left (i.e. viewport is 'NULL' or 'empty')
4348  // or whenever the viewport is not properly assigned (window re-size re-paint)
4349 
4350  if(pThis->rctView.top || pThis->rctView.bottom ||
4351  pThis->rctView.right != pThis->rctView.left + geomV.width / iFontWidth) // not PROPERLY assigned
4352  {
4353  int iAutoScrollWidth = AUTO_HSCROLL_SIZE;
4354 
4355  if(pThis->rctView.left < 0) // not already assigned
4356  {
4357  pThis->rctView.left = 0;
4358  }
4359 
4360  pThis->rctView.top = pThis->rctView.bottom = 0; // single line forces this (always)
4361 
4362  pThis->rctView.right = pThis->rctView.left + geomV.width / iFontWidth;
4363 
4364  if(pThis->rctView.right > pThis->rctView.left)
4365  {
4366  while(iAutoScrollWidth > 1 && iAutoScrollWidth >= pThis->rctView.right - pThis->rctView.left)
4367  {
4368  iAutoScrollWidth >>= 1;
4369  }
4370 
4371  // scroll right to expose the cursor
4372 
4373  while(pThis->rctView.right <= pThis->iCol)
4374  {
4375  pThis->rctView.left += iAutoScrollWidth;
4376  pThis->rctView.right += iAutoScrollWidth;
4377  }
4378  }
4379  else
4380  {
4381  pThis->rctView.right = pThis->rctView.left = pThis->iCol; // desperate to have something there
4382  }
4383  }
4384  }
4385  else // MULTI-LINE
4386  {
4387  int iWindowHeightInLines;
4388 
4389  // adjust font height to include line spacing (I'll use this to position the lines)
4390 
4391  iFontHeight = WBTextObjectCalculateLineHeight(pThis->iAsc, pThis->iDesc);
4392 
4393  iWindowHeightInLines = geomV.height / iFontHeight; // window height (in lines)
4394  if(!iWindowHeightInLines)
4395  {
4396  iWindowHeightInLines = 1; // just in case so I don't divide by zero
4397  }
4398 
4399  // AUTO-ASSIGN the viewport whenever right <= left (i.e. viewport is 'NULL' or 'empty')
4400  // or whenever the viewport is not properly assigned (window re-size re-paint)
4401 
4402  // NOTE: in this section, pBuf cannot be NULL
4403 
4404  if(pThis->rctView.right - pThis->rctView.left
4405  != geomV.width / iFontWidth // viewport needs re-calculation
4406  || pThis->rctView.bottom - pThis->rctView.top
4407  != iWindowHeightInLines // viewport needs re-calculation
4408  || pThis->rctView.top > pBuf->nEntries) // top exceeds total # of lines
4409  {
4410  int iAutoScrollWidth = AUTO_HSCROLL_SIZE;
4411 
4412  if(pThis->rctView.left < 0) // not already assigned
4413  {
4414  pThis->rctView.left = 0;
4415  }
4416 
4417  if(pThis->rctView.top < 0 || pThis->rctView.top > pBuf->nEntries
4418  || (pThis->rctView.top && pBuf->nEntries < iWindowHeightInLines))
4419  {
4420  pThis->rctView.top = 0; // set viewport to top
4421  }
4422 
4423  if(pThis->rctView.bottom - pThis->rctView.top
4424  != iWindowHeightInLines)
4425  {
4426  pThis->rctView.bottom = pThis->rctView.top + iWindowHeightInLines;
4427 
4428  // always scroll row into view
4429 
4430  while(pThis->rctView.top > pThis->iRow)
4431  {
4432  pThis->rctView.top -= iWindowHeightInLines - 1;
4433  pThis->rctView.bottom -= iWindowHeightInLines - 1;
4434  }
4435  while(pThis->rctView.bottom <= pThis->iRow)
4436  {
4437  pThis->rctView.top += iWindowHeightInLines - 1;
4438  pThis->rctView.bottom += iWindowHeightInLines - 1;
4439  }
4440  }
4441 
4442  if(pThis->rctView.right - pThis->rctView.left != geomV.width / iFontWidth)
4443  {
4444  pThis->rctView.right = pThis->rctView.left + geomV.width / iFontWidth;
4445 
4446  if(pThis->rctView.right > pThis->rctView.left) // just in case, test it
4447  {
4448  while(iAutoScrollWidth > 1 && iAutoScrollWidth >= pThis->rctView.right - pThis->rctView.left)
4449  {
4450  iAutoScrollWidth >>= 1;
4451  }
4452 
4453  // scroll right to expose the cursor
4454 
4455  while(pThis->rctView.right <= pThis->iCol)
4456  {
4457  pThis->rctView.left += iAutoScrollWidth;
4458  pThis->rctView.right += iAutoScrollWidth;
4459  }
4460  }
4461  else
4462  {
4463  pThis->rctView.right = pThis->rctView.left = pThis->iCol; // desperate to have something there
4464  }
4465  }
4466  }
4467  }
4468 
4469 
4470  //--------------------------------------------------------------------------
4471  // convert the highlight rectangle into screen coordinates (fills in rctSel)
4472  //--------------------------------------------------------------------------
4473 
4474  if(!SEL_RECT_EMPTY(pThis))
4475  {
4476  if(SEL_RECT_ALL(pThis))
4477  {
4478  memcpy(&rctSel, &(pThis->rctView), sizeof(rctSel)); // use entire viewport for hightlight rect
4479  // TODO: single-line, only select to end of string?
4480  }
4481  else
4482  {
4483  memcpy(&rctSel, &(pThis->rctSel), sizeof(rctSel));
4484  NORMALIZE_SEL_RECT(rctSel);
4485  }
4486 
4487  pThis->rctHighLight.left = pThis->rctWinView.left
4488  + (rctSel.left - pThis->rctView.left) * iFontWidth;
4489 
4490  pThis->rctHighLight.top = pThis->rctWinView.top
4491  + (rctSel.top - pThis->rctView.top) * iFontHeight;
4492 
4493  if(pThis->iLineFeed == LineFeed_NONE)
4494  {
4495  pThis->rctHighLight.top += (geomV.height - iAsc - iDesc) / 2; // since single-line text is centered
4496  }
4497 
4498  pThis->rctHighLight.right = pThis->rctHighLight.left
4499  + (rctSel.right - rctSel.left) * iFontWidth;
4500 
4501  pThis->rctHighLight.bottom = pThis->rctHighLight.top // NOTE add 1 to # of lines, always
4502  + (rctSel.bottom - rctSel.top + 1) * iFontHeight;
4503 
4504  // NOTE: this is independent of the selection method. In the special case
4505  // that a single line or an entire line is selected, I may have
4506  // to handle this differently
4507  }
4508  else
4509  {
4510  bzero(&rctSel, sizeof(rctSel)); // zero out selection rectangle
4511  bzero(&(pThis->rctHighLight), sizeof(pThis->rctHighLight));
4512  }
4513 
4514 
4515  //--------------------------------------------
4516  // CREATE PIXMAP (for speeding up the process)
4517  //--------------------------------------------
4518 
4519  iPX = pThis->rctWinView.right - pThis->rctWinView.left + 2 * MIN_BORDER_SPACING;
4520  iPY = pThis->rctWinView.bottom - pThis->rctWinView.top + 2 * MIN_BORDER_SPACING;
4521 
4522 // WB_ERROR_PRINT("TEMPORARY: %s window %u (%08xH) pixmap dimensions %d,%d\n"
4523 // " row/col %d,%d\n",
4524 // __FUNCTION__, (int)wID, (int)wID, iPX, iPY,
4525 // pThis->iRow, pThis->iCol);
4526 
4527  if(gc2 != None && iPX > 0 && iPY > 0)
4528  {
4529  pxTemp = XCreatePixmap(pDisplay, wID, iPX, iPY,
4530  DefaultDepth(pDisplay, DefaultScreen(pDisplay)));
4531  }
4532  else
4533  {
4534  pxTemp = None;
4535  }
4536 
4537  //-----------------------
4538  // FILL/ERASE BACKGROUND
4539  //-----------------------
4540 
4541  if(pxTemp != None)
4542  {
4543  iXDelta = geomV.x - MIN_BORDER_SPACING;
4544  iYDelta = geomV.y - MIN_BORDER_SPACING;
4545 
4546  // sometimes the clipping origin, when filling the background, might matter, especially if patterns are involved
4547  XSetClipOrigin(pDisplay, gc2, -iXDelta, -iYDelta); // so that it matches the display's clipping in gc
4548 
4549  XSetForeground(pDisplay, gc2, clrBG);
4550  XFillRectangle(pDisplay, pxTemp, gc2, 0, 0, iPX, iPY);
4551 
4552  XSetForeground(pDisplay, gc2, clrFG);
4553  }
4554  else // FALLBACK, if no pixmap, go to window directly
4555  {
4556  iXDelta = iYDelta = 0; // make sure, as they're used in a few places
4557 
4558  XSetForeground(pDisplay, gc, clrBG);
4559  XFillRectangle(pDisplay, wID, gc,
4560  pThis->rctWinView.left,
4561  pThis->rctWinView.top,
4562  pThis->rctWinView.right - pThis->rctWinView.left,
4563  pThis->rctWinView.bottom - pThis->rctWinView.top);
4564 
4565  XSetForeground(pDisplay, gc, clrFG);
4566  }
4567 
4568  // At this point, if pBuf is NULL or the # of entries zero, the background is *STILL* erased
4569 
4570  if(!pBuf || pBuf->nEntries <= 0)
4571  {
4572 // WB_ERROR_PRINT("TEMPORARY: %s - no text\n", __FUNCTION__);
4573  goto almost_the_end; // I use the goto so I can put cleanup code there - it's safer
4574  }
4575 
4576 
4577  // if it's a single-line object, center it within the window and display only ONE line
4578 
4579  if(pThis->iLineFeed == LineFeed_NONE) // i.e. SINGLE LINE
4580  {
4582  // SINGLE LINE PAINTING
4584 
4585  iY = geomV.y + (geomV.height - iAsc - iDesc) / 2 + iAsc; // iY is now "the baseline" for the font
4586  iX = geomV.x;
4587 
4588  pL = pBuf->aLines[0];
4589  if(!pL)
4590  {
4591  goto the_end;
4592  }
4593 
4594 
4595  //-----------------------------
4596  // DRAWING HIGHLIGHT RECTANGLE
4597  //-----------------------------
4598 
4599  if(pThis->rctHighLight.right > pThis->rctHighLight.left &&
4600  WBRectOverlapped(pThis->rctHighLight, pThis->rctWinView))
4601  {
4602  if(pxTemp != None)
4603  {
4604  // NOTE: iXDelta and iYDelta were calculated before, when background was erased
4605  // and the clip origin was also assigned for gc2
4606 // XSetClipOrigin(pDisplay, gc2, -iXDelta, -iYDelta); // so that it matches the display's clipping in gc
4607 
4608  XSetForeground(pDisplay, gc2, clrHBG); // highlight background color
4609 
4610  XFillRectangle(pDisplay, pxTemp, gc2,
4611  pThis->rctHighLight.left - iXDelta,
4612  pThis->rctHighLight.top - iYDelta,
4613  pThis->rctHighLight.right - pThis->rctHighLight.left,
4614  pThis->rctHighLight.bottom - pThis->rctHighLight.top);
4615 
4616  XSetForeground(pDisplay, gc2, clrFG);
4617  }
4618  else // FALLBACK, if no pixmap, go to window directly
4619  {
4620  XSetForeground(pDisplay, gc, clrHBG); // highlight background color
4621 
4622  XFillRectangle(pDisplay, wID, gc,
4623  pThis->rctHighLight.left,
4624  pThis->rctHighLight.top,
4625  pThis->rctHighLight.right - pThis->rctHighLight.left,
4626  pThis->rctHighLight.bottom - pThis->rctHighLight.top);
4627 
4628  XSetForeground(pDisplay, gc, clrFG);
4629  }
4630  }
4631 
4632  //---------------------------------------
4633  // DRAW the text and the vertical cursor
4634  //---------------------------------------
4635 
4636  iLen = WBGetMBLength(pL);
4637 
4638  pThis->iCursorX = pThis->iCursorY = pThis->iCursorHeight = 0; // to indicate "not drawn"
4639 
4640  for(i1=pThis->rctView.left; i1 < pThis->rctView.right; i1++, iX += iFontWidth)
4641  {
4642  WB_RECT rctChar, rctCursor;
4643 
4644  if(i1 == pThis->iCol) // display the cursor (NOTE: row ALWAYS matches)
4645  {
4646  pThis->iCursorX = iX - 1;
4647 
4648  if(pThis->iInsMode == InsertMode_OVERWRITE)
4649  {
4650  pThis->iCursorY = iY + iDesc + 1;
4651  pThis->iCursorHeight = 1;
4652 
4653  rctCursor.left = pThis->iCursorX - iXDelta;
4654  rctCursor.top = pThis->iCursorY - iYDelta;
4655  rctCursor.right = pThis->iCursorX + iFontWidth - iXDelta;
4656  rctCursor.bottom = pThis->iCursorY - iYDelta;
4657  }
4658  else // INSERT mode cursor
4659  {
4660  pThis->iCursorY = iY - iAsc - 1;
4661  pThis->iCursorHeight = iY + iDesc + 1 - pThis->iCursorY;
4662 
4663  rctCursor.left = pThis->iCursorX - iXDelta;
4664  rctCursor.top = pThis->iCursorY - iYDelta;
4665  rctCursor.right = pThis->iCursorX - iXDelta;
4666  rctCursor.bottom = pThis->iCursorY + pThis->iCursorHeight - iYDelta;
4667  }
4668 
4669 
4670  if(pThis->iBlinkState != 0) // horizontal cursor for overwrite
4671  {
4672  if(WBRectOverlapped(rctCursor, pThis->rctHighLight))
4673  {
4674  XSetForeground(pDisplay, gc2 != None ? gc2 : gc, clrHFG);
4675  XSetBackground(pDisplay, gc2 != None ? gc2 : gc, clrHBG);
4676  }
4677  else
4678  {
4679  XSetForeground(pDisplay, gc2 != None ? gc2 : gc, clrFG);
4680  XSetBackground(pDisplay, gc2 != None ? gc2 : gc, clrBG);
4681  }
4682 
4683  XDrawLine(pDisplay, pxTemp != None ? pxTemp : wID,
4684  gc2 != None ? gc2 : gc,
4685  rctCursor.left, rctCursor.top, rctCursor.right, rctCursor.bottom);
4686  }
4687  }
4688 
4689  rctChar.top = iY - iAsc;
4690  rctChar.bottom = iY + iDesc;
4691  rctChar.left = iX + 1;
4692  rctChar.right = iX + iFontWidth - 1; // since I'm checking overlap, make it a bit 'skinnier'
4693 
4694  if(WBRectOverlapped(rctChar, pThis->rctHighLight))
4695  {
4696  XSetForeground(pDisplay, gc2 != None ? gc2 : gc, clrHFG);
4697  XSetBackground(pDisplay, gc2 != None ? gc2 : gc, clrHBG);
4698  }
4699  else
4700  {
4701  XSetForeground(pDisplay, gc2 != None ? gc2 : gc, clrFG);
4702  XSetBackground(pDisplay, gc2 != None ? gc2 : gc, clrBG);
4703  }
4704 
4705  // for NOW, draw one character at a time. But make sure it's within
4706  // the invalid region first...
4707 
4708  geomC.x = iX;
4709  geomC.y = iY; // TODO: for single line, move this and the next 2 outside the loop?
4710  geomC.width = iFontWidth;
4711  geomC.height = iFontHeight;
4712 
4713  if(i1 < iLen)
4714  {
4715  if(WBGeomOverlapped(geomC, geomP)) // only if the character's geometry overlaps the paint geometry
4716  {
4717  int iLen2;
4718  const char *p1 = WBGetMBCharPtr(pL, i1, &iLen2);
4719 
4720  if(p1 && iLen2 > 0)
4721  {
4722 // if(iLen2 > 1)
4723 // {
4724 // WB_ERROR_PRINT("TEMPORARY: %s - iLen2=%d, %d, %d, p1 = %02xH,%02xH\n", __FUNCTION__,
4725 // iLen2, iX - iXDelta, iY - iYDelta, (unsigned char)p1[0], (unsigned char)p1[1]);
4726 // }
4727 
4728  WB_DRAW_STRING(pDisplay, pxTemp ? pxTemp : wID, fSet,
4729  gc2 != None ? gc2 : gc,
4730  iX - iXDelta, iY - iYDelta, p1, iLen2);
4731  }
4732  }
4733  }
4734  else if(i1 > pThis->iCol)
4735  {
4736  break; // slight optimization
4737  }
4738  }
4739  }
4740  else // MULTI-LINE
4741  {
4742  int iCurRow; // my counter
4743 
4745  // MULTI LINE PAINTING
4747 
4748  // 3 different important conditions that affect painting:
4749  // 1. the cursor is (or is not) on this row
4750  // 2. the row has highlighting in it
4751  // 3. if 2, the row is either fully or partially highlighted
4752 
4753  // if 1 and 2 are FALSE, paint the string 'as-is' with WB_DRAW_STRING
4754  // if 2 is TRUE, but 3 is false, paint the entire line "highlighted".
4755  // otherwise, duplicate (for this row) what single-line painting does,
4756  // dealing with cursor and highlight rectangles as needed, 1 char at a time
4757 
4758  pThis->iCursorX = pThis->iCursorY = pThis->iCursorHeight = 0; // to indicate "not drawn"
4759 
4760  for(iCurRow=pThis->rctView.top; iCurRow <= pBuf->nEntries && iCurRow <= pThis->rctView.bottom; iCurRow++)
4761  {
4762  int bHighlight = 0;
4763 
4764  // NOTE: geomV already has 'border spacing' taken into account
4765 
4766  iY = geomV.y + iFontHeight * (iCurRow - pThis->rctView.top)
4767  + iAsc; // add the ascent to the top of the text to get "the baseline" for the font
4768 
4769  iX = geomV.x;
4770 
4771  if(iCurRow < pBuf->nEntries)
4772  {
4773  pL = pBuf->aLines[iCurRow];
4774  }
4775  else
4776  {
4777  pL = NULL;
4778  }
4779 
4780  if(pL)
4781  {
4782  iLen = WBGetMBLength(pL); // length in "characters"
4783  }
4784  else
4785  {
4786  iLen = 0;
4787  }
4788 
4789  // if the row might be highlighted, it will be within 'rctSel', which can
4790  // span multiple lines. It identifies the starting and ending row/col combo, but in pixels.
4791 
4792  if((rctSel.left != rctSel.right || rctSel.top != rctSel.bottom) && // has a selection
4793  iCurRow >= pThis->rctSel.top && // row is in range below or equal to 'top'
4794  (iCurRow < pThis->rctSel.bottom || // above 'last' row
4795  (iCurRow == pThis->rctSel.bottom && pThis->rctSel.right > 0))) // last row but has column > 0
4796  {
4797  bHighlight = 1; // flag for later
4798  }
4799 
4800  if(iCurRow == pThis->iRow || // current row is cursor row
4801  (bHighlight && // I am highlighting this row AND
4802  (iCurRow == pThis->rctSel.top || // it's the starting row for highlight
4803  iCurRow == pThis->rctSel.bottom))) // it's the ending row for highlight
4804  {
4805  for(i1=pThis->rctView.left; i1 <= pThis->rctView.right; i1++, iX += iFontWidth)
4806  {
4807  WB_RECT rctCursor;
4808  int iLen2;
4809  const char *p1;
4810 
4811  if(pL && i1 < iLen)
4812  {
4813  p1 = WBGetMBCharPtr(pL, i1, &iLen2);
4814  }
4815  else
4816  {
4817  p1 = NULL; // past end of string
4818  }
4819 
4820  if(bHighlight &&
4821  ((iCurRow > pThis->rctSel.top &&
4822  iCurRow < pThis->rctSel.bottom) ||
4823  (iCurRow == pThis->rctSel.top &&
4824  i1 >= pThis->rctSel.left &&
4825  (i1 < pThis->rctSel.right || iCurRow != pThis->rctSel.bottom)) ||
4826  (iCurRow == pThis->rctSel.bottom && i1 < pThis->rctSel.right)))
4827  {
4828  int iY0 = iY - iAsc; // NOTE: iY is the BASE of the font, so I need to font ascent to get top of rect
4829 
4830  // fill the rectangle for this character with the correct background color.
4831 
4832  if(pxTemp != None)
4833  {
4834  XSetForeground(pDisplay, gc2, clrHBG); // highlight background color
4835  XSetBackground(pDisplay, gc2, clrHBG);
4836 
4837  XFillRectangle(pDisplay, pxTemp, gc2,
4838  iX - iXDelta, iY0 - iYDelta,
4839  iFontWidth, iFontHeight);
4840 
4841  XSetForeground(pDisplay, gc2, clrHFG);
4842  }
4843  else // FALLBACK, if no pixmap, go to window directly
4844  {
4845  XSetForeground(pDisplay, gc, clrHBG); // highlight background color
4846  XSetBackground(pDisplay, gc, clrHBG);
4847 
4848  XFillRectangle(pDisplay, wID, gc,
4849  iX, iY0,
4850  iFontWidth, iFontHeight);
4851 
4852  XSetForeground(pDisplay, gc, clrHFG);
4853  }
4854  }
4855  else // non-highlighted character
4856  {
4857  XSetForeground(pDisplay, gc2 != None ? gc2 : gc, clrFG);
4858  XSetBackground(pDisplay, gc2 != None ? gc2 : gc, clrBG);
4859  }
4860 
4861 
4862  //------------
4863  // draw cursor
4864  //------------
4865 
4866  if(iCurRow == pThis->iRow && i1 == pThis->iCol) // display the cursor (matching row/col)
4867  {
4868  pThis->iCursorX = iX - 1;
4869 
4870  if(pThis->iInsMode == InsertMode_OVERWRITE) // NOTE: horizontal cursor for overwrite
4871  {
4872  pThis->iCursorY = iY + iDesc + 1;
4873  pThis->iCursorHeight = 1;
4874 
4875  rctCursor.left = pThis->iCursorX - iXDelta;
4876  rctCursor.top = pThis->iCursorY - iYDelta;
4877  rctCursor.right = pThis->iCursorX + iFontWidth - iXDelta;
4878  rctCursor.bottom = pThis->iCursorY - iYDelta;
4879  }
4880  else // INSERT mode cursor
4881  {
4882  pThis->iCursorY = iY - iAsc - 1;
4883  pThis->iCursorHeight = iY + iDesc + 1 - pThis->iCursorY;
4884 
4885  rctCursor.left = pThis->iCursorX - iXDelta;
4886  rctCursor.top = pThis->iCursorY - iYDelta;
4887  rctCursor.right = pThis->iCursorX - iXDelta;
4888  rctCursor.bottom = pThis->iCursorY + pThis->iCursorHeight - iYDelta;
4889  }
4890 
4891  if(pThis->iBlinkState != 0)
4892  {
4893  XDrawLine(pDisplay, pxTemp != None ? pxTemp : wID,
4894  gc2 != None ? gc2 : gc,
4895  rctCursor.left, rctCursor.top, rctCursor.right, rctCursor.bottom);
4896  }
4897 
4898 // WB_ERROR_PRINT("TEMPORARY: %s - cursor x,y = %d,%d\n", __FUNCTION__, pThis->iCursorX, pThis->iCursorY);
4899  }
4900 // else
4901 // {
4902 // WB_ERROR_PRINT("TEMPORARY: %s - pThis->iRow=%d,iCol=%d,iCurRow=%d,i1=%d\n",
4903 // __FUNCTION__, pThis->iRow, pThis->iCol, iCurRow, i1);
4904 // }
4905 
4906 
4907  if(p1 && iLen2 > 0)
4908  {
4909 // if(iLen2 > 1)
4910 // {
4911 // WB_ERROR_PRINT("TEMPORARY: %s - iLen2=%d, %d, %d, p1 = %02xH,%02xH\n", __FUNCTION__,
4912 // iLen2, iX - iXDelta, iY - iYDelta, (unsigned char)p1[0], (unsigned char)p1[1]);
4913 // }
4914 
4915  WB_DRAW_STRING(pDisplay, pxTemp ? pxTemp : wID, fSet,
4916  gc2 != None ? gc2 : gc,
4917  iX - iXDelta, iY - iYDelta, p1, iLen2);
4918  }
4919  }
4920  }
4921  else if(pL) // current row is NOT cursor row (and line is not blank)
4922  {
4923  const char *p1 = WBGetMBCharPtr(pL, pThis->rctView.left, NULL);
4924  const char *p2 = WBGetMBCharPtr(pL, pThis->rctView.right, NULL);
4925 
4926  if(bHighlight)
4927  {
4928  int iY0 = iY - iAsc; // NOTE: iY is the BASE of the font, so I need to font ascent to get top of rect
4929 
4930  if(pxTemp != None)
4931  {
4932  XSetForeground(pDisplay, gc2, clrHBG); // highlight background color
4933  XSetBackground(pDisplay, gc2, clrHBG);
4934 
4935  XFillRectangle(pDisplay, pxTemp, gc2,
4936  iX - iXDelta, iY0 - iYDelta,
4937  geomV.width, iFontHeight); // entire line
4938 
4939  XSetForeground(pDisplay, gc2, clrHFG);
4940  }
4941  else // FALLBACK, if no pixmap, go to window directly
4942  {
4943  XSetForeground(pDisplay, gc, clrHBG); // highlight background color
4944  XSetBackground(pDisplay, gc, clrHBG);
4945 
4946  XFillRectangle(pDisplay, wID, gc,
4947  iX, iY0,
4948  geomV.width, iFontHeight); // entire line
4949 
4950  XSetForeground(pDisplay, gc, clrHFG);
4951  }
4952  }
4953  else
4954  {
4955  XSetForeground(pDisplay, gc2 != None ? gc2 : gc, clrFG);
4956  XSetBackground(pDisplay, gc2 != None ? gc2 : gc, clrBG);
4957  }
4958 
4959  if(p1 && p1 && p2 > p1)
4960  {
4961  WB_DRAW_STRING(pDisplay, pxTemp ? pxTemp : wID, fSet,
4962  gc2 != None ? gc2 : gc,
4963  iX - iXDelta, iY - iYDelta,
4964  p1, p2 - p1);
4965  }
4966 // else (this happens when lines are blank)
4967 // {
4968 // WB_ERROR_PRINT("TEMPORARY: %s - NOT drawing \"%s\"\n p1=%p p2=%p delta=%d\n",
4969 // __FUNCTION__, pL, p1, p2, (int)(p2 - p1));
4970 // }
4971  }
4972  }
4973  }
4974 
4975  //------------------------------------------
4976  // PIXMAP OPTIMIZATION - TRANSFER TO WINDOW
4977  //------------------------------------------
4978 
4979 almost_the_end:
4980 
4981  if(pxTemp != None) // OPTIMIZATION (using pixmap)
4982  {
4983  int iX0=0, iY0=0, iW0=iPX, iH0=iPY;
4984 
4985  iX = pThis->rctWinView.left - MIN_BORDER_SPACING;
4986  iY = pThis->rctWinView.top - MIN_BORDER_SPACING;
4987 
4988  // only copy the invalid area (this should save some time)
4989 
4990  if(geomP.x > iX)
4991  {
4992  iX0 = geomP.x - iX;
4993  iW0 -= iX0;
4994  iX = geomP.x;
4995  }
4996 
4997  if(geomP.x + geomP.width < iX + iW0)
4998  {
4999  iW0 = geomP.x + geomP.width - iX;
5000  }
5001 
5002  if(geomP.y > iY)
5003  {
5004  iY0 = geomP.y - iY;
5005  iH0 -= iY0;
5006  iY = geomP.y;
5007  }
5008 
5009  if(geomP.y + geomP.height < iY + iH0)
5010  {
5011  iH0 = geomP.y + geomP.height - iY;
5012  }
5013 
5014  if(iH0 > 0 && iW0 > 0)
5015  {
5016  XCopyArea(pDisplay, pxTemp, wID, gc, // this time use GC to do the copy
5017  iX0, iY0, iW0, iH0, iX, iY);
5018  }
5019 
5020  XFreePixmap(pDisplay, pxTemp);
5021  }
5022  }
5023 
5024 the_end:
5025 
5026  // mandatory resource free-up goes here, hence the 'goto' label
5027 
5028  if(gc2 != None)
5029  {
5030  XFreeGC(pDisplay, gc2);
5031  }
5032 
5033  if(rFontSet == None) // that is, I created one
5034  {
5035  XFreeFontSet(pDisplay, fSet);
5036  }
5037 }
5038 
5039 static void __internal_cursor_blink(struct _text_object_ *pThis, int bHasFocus)
5040 {
5041  if(WBIsValidTextObject(pThis))
5042  {
5043 // WB_RECT rctCursor;
5044 
5045  if(!bHasFocus)
5046  {
5047  if(pThis->iBlinkState)
5048  {
5049  pThis->iBlinkState = 0; // no cursor
5050 
5051  __internal_invalidate_cursor(pThis, 1);
5052  }
5053  }
5054  else
5055  {
5056  pThis->iBlinkState = (pThis->iBlinkState + 1) % 3;
5057 
5058  __internal_invalidate_cursor(pThis, pThis->iBlinkState == 0 || pThis->iBlinkState == 1);
5059  }
5060  }
5061 }
5062 
5063 
5064 // ----------------------------
5065 // MBCS UTF-8 UTILITY FUNCTIONS
5066 // ----------------------------
5067 
5068 // see RFC3629 and https://en.wikipedia.org/wiki/UTF-8#Codepage_layout
5069 
5070 static int internal_IsMBCharValid(const char *pChar, int *piLen)
5071 {
5072 const unsigned char *p1;
5073 int iRval;
5074 
5075 
5076  if((unsigned char)*pChar < 0x80) // normal ASCII (including '0' byte)
5077  {
5078  if(piLen)
5079  {
5080  if(*pChar)
5081  {
5082  *piLen = 1;
5083  }
5084  else
5085  {
5086  *piLen = 0;
5087  }
5088  }
5089 
5090  return 1; // valid char
5091  }
5092 
5093  p1 = (const unsigned char *)pChar;
5094  iRval = 0; // initially, call it 'invalid'
5095 
5096  // 80-BF - values 0-3F 'continuation' bytes for mbcs [sequence will not start with this]
5097  // C0-DF - 2-byte character
5098  // (C0=0, C1=40H, C2=80H, up to 7C0H for DF; values C0 and C1 are actually 'not valid' as it's pointless)
5099  // E0-EF - 3-byte character
5100  // (E0 adds 800H; E1 adds 1000H; E2 2000H; etc. up to F000H for EF. not all E0 sequences are valid)
5101  // F0-FF - 4+byte character
5102  // (F0 is 10000H; F1 40000H; F2 80000H; F3 C0000H; F4 100000H; values F5 and above are 'not valid'
5103  // Not all F0's are valid, either)
5104  //
5105  // For invalid sequences, this function will attempt to work around it; however RFC3629 requires them to be error conditions
5106  // to avoid security-related problems caused by invalid UTF8 sequences.
5107 
5108  if(*p1 < 0xc2 || // a continuation character? an 'invalid' sequence?
5109  *p1 >= 0xf5) // assume value cannot be > 0xff, right?
5110  {
5111  p1++; // invalid; length is 1 (like ASCII)
5112  }
5113  else if(*p1 < 0xe0) // 2-byte sequence
5114  {
5115  if((unsigned char)p1[1] >= 0x80 && (unsigned char)p1[1] <= 0xbf) // valid 2-byte sequence
5116  {
5117  p1 += 2;
5118  iRval = 1;
5119  }
5120  else
5121  {
5122  p1++; // invalid; length is 1 (like ASCII)
5123  }
5124  }
5125  else if(*p1 < 0xf0) // 3-byte sequence
5126  {
5127  if(p1[1] >= 0x80 && p1[1] <= 0xbf && // valid 3-byte sequence
5128  p1[2] >= 0x80 && p1[2] <= 0xbf)
5129  {
5130  // TODO: validate E0 sequences
5131 
5132  p1 += 3;
5133  iRval = 1;
5134  }
5135  else // now it gets trickier...
5136  {
5137  // by convention, treat it like 8-bit ASCII with a length of 1
5138  p1++;
5139  }
5140  }
5141  else // if(*p1 < 0xf5)
5142  {
5143  if(p1[1] >= 0x80 && p1[1] <= 0xbf && // valid 4-byte sequence
5144  p1[2] >= 0x80 && p1[2] <= 0xbf &&
5145  p1[3] >= 0x80 && p1[3] <= 0xbf)
5146  {
5147  // TODO: validate F0 sequences
5148 
5149  p1 += 4;
5150  iRval = 1;
5151  }
5152  else // now it gets trickier...
5153  {
5154  // by convention, treat it like 8-bit ASCII with a length of 1
5155  p1++;
5156  }
5157  }
5158 
5159  if(piLen)
5160  {
5161  *piLen = (const char *)p1 - pChar; // this simplified length indicator helps with copy/pasta [if I need it]
5162  }
5163 
5164 #ifndef NO_DEBUG
5165  if(!iRval || ((const char *)p1 - pChar) > 1)
5166  {
5167  int iLen = ((const char *)p1 - pChar);
5168 
5169  WBDebugPrint("TEMPORARY: %s - return=%d char=%02xH", __FUNCTION__, iRval, (unsigned char)*pChar);
5170  while(iLen > 1)
5171  {
5172  iLen--;
5173  pChar++;
5174 
5175  WBDebugPrint(",%02xH", (unsigned char)*pChar);
5176  }
5177  WBDebugPrint("\n");
5178  }
5179 #endif // NO_DEBUG
5180 
5181  return iRval;
5182 }
5183 
5184 static int internal_MBstrlen(const char *pString)
5185 {
5186 const char *p1 = pString;
5187 int iLen, iRval;
5188 
5189 
5190  iRval = 0;
5191 
5192  while(*p1)
5193  {
5194  // step through 1 character at a time until I reach the end of the string, or iCol reaches zero
5195 
5196  if(!internal_IsMBCharValid(p1, &iLen))
5197  {
5198  p1++; // treat like 8-bit ASCII if it's an invalid UTF-8 sequence
5199 
5200  // NOTE: may change or render as EF BF BD i.e. U+FFFD (a diamond with question mark)
5201  }
5202  else
5203  {
5204  p1 += iLen;
5205  }
5206 
5207  iRval++; // we only count blue cars (and multi-byte chars as "1")
5208  }
5209 
5210  return iRval;
5211 }
5212 
5213 // return the 'character' index for the 'column' specified by 'iCol' for MBCS
5214 
5215 static int internal_MBColIndex(const char *pString, int iCol)
5216 {
5217 const char *p1 = pString;
5218 int iLen;
5219 
5220 
5221  while(iCol > 0 && *p1)
5222  {
5223  // step through 1 character at a time until I reach the end of the string, or iCol reaches zero
5224 
5225  if(!internal_IsMBCharValid(p1, &iLen))
5226  {
5227  p1++; // treat like 8-bit ASCII if it's an invalid UTF-8 sequence
5228 
5229  // NOTE: may change or render as EF BF BD i.e. U+FFFD (a diamond with question mark)
5230  }
5231  else
5232  {
5233  p1 += iLen;
5234  }
5235 
5236  iCol--;
5237  }
5238 
5239  return (p1 - pString); // NOTE: need to verify length before calling or this could be WRONG
5240 }
5241 
5242 
5243 char * WBInsertMBChars(char *pString, int iCol, const char *pszMBString, int cbString,
5244  int fTab, int fOverwrite, int *piNewCol, char **ppInserted)
5245 {
5246  if(ppInserted)
5247  {
5248  *ppInserted = NULL; // initial value
5249  }
5250 
5251  if(piNewCol)
5252  {
5253  *piNewCol = iCol; // initial value
5254  }
5255 
5256 
5257 // POOBAH
5258 
5259 
5260  return NULL;
5261 }
5262 
5263 char * WBSplitMBLine(char *pString, int iCol)
5264 {
5265 char *p1, *pRval;
5266 int iLen, i1;
5267 
5268 
5269  iLen = internal_MBstrlen(pString);
5270 
5271  if(iCol >= iLen) // past end of string?
5272  {
5273  pRval = WBCopyString(""); // allocated pointer that's a copy of an empty string - it's what I'll start with
5274  }
5275  else
5276  {
5277  i1 = internal_MBColIndex(pString, iCol);
5278 
5279  pRval = WBCopyString(pString + i1);
5280 
5281  if(pRval)
5282  {
5283  pString[i1] = 0; // terminate it here (my 'split')
5284  }
5285  }
5286 
5287  if(pRval) // if I have a valid return, right-trim the original line
5288  {
5289  p1 = pString + strlen(pString);
5290 
5291  while(p1 > pString && *(p1 - 1) <= ' ') // right-trim it
5292  {
5293  *(--p1) = 0; // right-trim the string - make ALL white space GO AWAY
5294  }
5295  }
5296 
5297  return(pRval);
5298 }
5299 
5300 char * WBJoinMBLine(char *pString, int iCol, const char *pJoin)
5301 {
5302 int iLen, iLenNew, iJoin;
5303 char *pRval, *p1;
5304 
5305 
5306  iLen = internal_MBstrlen(pString);
5307  if(iLen < iCol)
5308  {
5309  iLenNew = iCol;
5310  }
5311  else
5312  {
5313  iLenNew = iLen;
5314  }
5315 
5316  iJoin = internal_MBstrlen(pJoin);
5317 
5318  pRval = WBReAlloc(pString, iLenNew + iJoin + 2);
5319  if(!pRval)
5320  {
5321  return NULL;
5322  }
5323 
5324  p1 = pRval + strlen(pRval); // regardless of actual length, this works
5325 
5326  while(iLen < iLenNew)
5327  {
5328  *(p1++) = ' '; // add white space up to 'iCol'
5329  iLen++;
5330  }
5331 
5332  strcpy(p1, pJoin);
5333 
5334  return pRval;
5335 }
5336 
5337 int WBDelMBChars(char *pString, int iCol, int nDel, int *piNewCol, char **ppDeleted)
5338 {
5339 int iLen, iIndex, i1, iRval = nDel;
5340 char *pDelChar, *p1;
5341 
5342 
5343  if(ppDeleted)
5344  {
5345  *ppDeleted = NULL; // initial value
5346  }
5347 
5348  if(piNewCol)
5349  {
5350  *piNewCol = iCol; // initial value
5351  }
5352 
5353  if(!pString || !nDel || !*pString)
5354  {
5355  if(piNewCol)
5356  {
5357  *piNewCol = iCol; // for now, just do this, even if string is blank
5358  }
5359 
5360  return nDel; // parameter validation
5361  }
5362 
5363  iLen = internal_MBstrlen(pString);
5364 
5365  if(iRval > 0)
5366  {
5367  if(ppDeleted && iLen > iCol)
5368  {
5369  pDelChar = WBAlloc(4 // max size of MB char
5370  * (iLen - iCol + 1) // 1 more than # of chars to delete
5371  + 1); // additional storage for zero byte
5372 
5373  *ppDeleted = pDelChar; // NOTE: caller checks for NULL to determine error state
5374 
5375  if(pDelChar)
5376  {
5377  *pDelChar = 0; // make sure
5378  }
5379  }
5380  else
5381  {
5382  pDelChar = NULL;
5383  }
5384 
5385  while(iLen > iCol && iRval > 0)
5386  {
5387  iIndex = internal_MBColIndex(pString, iCol);
5388 
5389  p1 = pString + iIndex; // always
5390 
5391  if(!*p1)
5392  {
5393  break; // I'm at the end of the string (sanity test)
5394  }
5395 
5396  if(!internal_IsMBCharValid(p1, &i1))
5397  {
5398  // invalid character - truncate the line here!
5399 
5400  *p1 = 0;
5401  iRval = 0; // assume 'end of line' and terminate deleting
5402 
5403  break;
5404  }
5405 
5406  if(pDelChar && i1 > 0)
5407  {
5408  memcpy(pDelChar, p1, i1);
5409  pDelChar += i1;
5410 
5411  *pDelChar = 0; // mark the end of the string
5412  }
5413 
5414  iRval --; // deleting one character (so remove a 'delete')
5415  iLen--; // update the new length
5416 
5417  // i1 is the length of the next character. slide 'em down! [this is less efficient, but more 'stable']
5418  strcpy(p1, p1 + i1); // simplest method
5419  }
5420  }
5421  else
5422  {
5423  if(ppDeleted && iCol > 0)
5424  {
5425  pDelChar = WBAlloc(4 // max size of MB char
5426  * (iCol + 1) // 1 more than # of chars to delete
5427  + 1); // additional storage for zero byte
5428 
5429  *ppDeleted = pDelChar; // NOTE: caller checks for NULL to determine error state
5430 
5431  if(pDelChar)
5432  {
5433  *pDelChar = 0; // make sure
5434  }
5435  }
5436  else
5437  {
5438  pDelChar = NULL;
5439  }
5440 
5441  // NOW we do backspacing, which preserves the character at 'iCol'. this is a little trickier.
5442  // when we end up at column zero, we return the 'iRval' count.
5443 
5444  while(iCol > 0 && iRval < 0)
5445  {
5446  if(iCol <= iLen) // I'm NOT past the end
5447  {
5448  iIndex = internal_MBColIndex(pString, iCol - 1); // PREVIOUS column
5449 
5450  p1 = pString + iIndex; // always
5451 
5452  if(!*p1)
5453  {
5454  break; // I'm at the end of the string (sanity test, unlikely to happen)
5455  }
5456 
5457  if(!internal_IsMBCharValid(p1, &i1))
5458  {
5459  // invalid character - truncate the line here!
5460 
5461  *p1 = 0;
5462  iRval = 0; // assume 'end of line' at this point, and terminate deleting
5463 
5464  break;
5465  }
5466 
5467  if(pDelChar && i1 > 0)
5468  {
5469  // insert this character into the beginning of the 'undo' info string
5470  // there are probably BETTER ways to do this, but this is what I'll do.
5471 
5472  memmove(pDelChar + i1, pDelChar, strlen(pDelChar) + 1); // open up space. make sure ending zero byte is included.
5473  memcpy(pDelChar, p1, i1);
5474  }
5475 
5476  // i1 is the length of the prev character, so slide 'em down! [this is less efficient, but more 'stable']
5477  strcpy(p1, p1 + i1); // simplest method (I could also get the pointer to the 'iCol'th char, but this is more consistent)
5478  }
5479  else
5480  {
5481  // since I'm at the end, just delete the last char in the line (effectively) by decrementing the column
5482  // so nothing else to do here other than the end of the loop
5483  }
5484 
5485  iCol--;
5486  iRval++; // because it's NEGATIVE, so we increment it
5487  }
5488  }
5489 
5490  // it seems in both cases (above) this section remains exactly the same
5491  // how amazing that works out!
5492 
5493  if(piNewCol)
5494  {
5495  *piNewCol = iCol; // 'iCol' will be the NEW column for the cursor
5496  }
5497 
5498  return iRval;
5499 }
5500 
5501 int WBGetMBLength(const char *pString)
5502 {
5503  if(!pString)
5504  {
5505  return 0;
5506  }
5507 
5508  return internal_MBstrlen(pString);
5509 }
5510 
5511 char * WBGetMBCharPtr(char *pString, int iCol, int *pcbLen)
5512 {
5513 int iIndex;
5514 
5515 
5516  if(!pString)
5517  {
5518  return NULL;
5519  }
5520 
5521  iIndex = internal_MBColIndex(pString, iCol);
5522 
5523  if(pString[iIndex]) // it's not a 0-byte
5524  {
5525  if(!internal_IsMBCharValid(pString + iIndex, pcbLen))
5526  {
5527  return NULL; // character is not valid
5528  }
5529  }
5530  else
5531  {
5532  if(pcbLen)
5533  {
5534  *pcbLen = 0;
5535  }
5536 
5537  // TODO: anything else?
5538  }
5539 
5540  return pString + iIndex;
5541 }
5542 
5543 int WBGetMBColIndex(const char *pString, const char *pChar)
5544 {
5545 const char *p1 = pString;
5546 int iLen, iRval;
5547 
5548 
5549  if(pChar <= pString)
5550  {
5551  return 0;
5552  }
5553 
5554  iRval = 0;
5555 
5556  while(*p1 && p1 < pChar)
5557  {
5558  // step through 1 character at a time until I reach the end of the string, or iCol reaches zero
5559 
5560  if(!internal_IsMBCharValid(p1, &iLen))
5561  {
5562  p1++; // treat like 8-bit ASCII if it's an invalid UTF-8 sequence
5563 
5564  // NOTE: may change or render as EF BF BD i.e. U+FFFD (a diamond with question mark)
5565  }
5566  else
5567  {
5568  p1 += iLen;
5569  }
5570 
5571  iRval++; // we only count blue cars (and multi-byte chars as "1")
5572  }
5573 
5574  if(p1 != pChar)
5575  {
5576  return 0; // this is an error condition
5577  }
5578 
5579  return iRval; // returns the actual column index for 'pChar' within the string pointed by 'pString'
5580 }
5581 
5582