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