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