1 /*
2  *  Yudit Unicode Editor Source File
3  *
4  *  GNU Copyright (C) 1997-2006  Gaspar Sinai <gaspar@yudit.org>
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License, version 2,
8  *  dated June 1991. See file COPYYING for details.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program; if not, write to the Free Software
17  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18  */
19 
20 #include "swidget/STextView.h"
21 #include "stoolkit/SCluster.h"
22 
23 #define DEBUG_SPEED 0
24 
25 
26 /* I could debug speed of redrawing. Most time (98%) is taken in
27   i += font.draw (c, p, dm, &g.array()[i], g.size()-i); */
28 
29 #if DEBUG_SPEED
30 #ifndef USE_WINAPI
31 #include <sys/time.h>
32 #else
33 #include <winsock.h>
34 #include <time.h>
35 #endif
36 
37 
38 static struct timeval thatTime;
39 
40 static void
timerStart()41 timerStart()
42 {
43   gettimeofday (&thatTime, 0);
44 }
45 
46 static void
timerStop()47 timerStop()
48 {
49   struct timeval thisTime;
50   gettimeofday (&thisTime, 0);
51   if (thisTime.tv_usec < thatTime.tv_usec)
52   {
53     thisTime.tv_sec--;
54     thisTime.tv_usec+=1000000;
55   }
56   thisTime.tv_sec -= thatTime.tv_sec;
57   thisTime.tv_usec -= thatTime.tv_usec;
58   int msec = (int) thisTime.tv_sec * 1000 + thisTime.tv_usec/1000;
59   fprintf (stderr, "Elapsed time: %d msecs\n", msec);
60 }
61 #endif
62 
63 
64 static unsigned int sane_index (const SV_UINT& array, unsigned int index);
65 
66 /**
67  * The text data is mine. I'll delete it.
68  */
STextView(void)69 STextView::STextView (void)
70   : lrpen (SColor (0.0, 0.0, 0.0, 1.0)),
71   rlpen (SColor (0.0, 0.0, 1.0, 1.0)),
72   underlineColor("red")
73 {
74   isHidingText = false;
75   printerPageSize = 0;
76   highlightMode = "";
77   isWordWrapOn = false;
78   isEditable = false;
79   clipx = clipy = 0;
80   clipw = cliph = 0;
81   fontSize = 16.0;
82   lineend = true;
83   multiline = true;
84   syntax.setTextData (&textData);
85   syntax.addTextDataListener (this);
86   textData.addTextDataListener (this);
87   textData.addLineTracker (&syntax);
88 }
89 
90 /**
91  * Make a text-view from external text
92  * @param utf8 is the utf8 ancoded text
93  */
STextView(const SString & utf8)94 STextView::STextView (const SString& utf8)
95   : textData (utf8), lrpen (SColor (0.0, 0.0, 0.0, 1.0)),
96   rlpen (SColor (0.0, 0.0, 1.0, 1.0)),
97   underlineColor("red")
98 {
99   isHidingText = false;
100   printerPageSize = 0;
101   highlightMode = "";
102   isWordWrapOn = false;
103   isEditable = false;
104   clipx = clipy = 0;
105   clipw = cliph = 0;
106   fontSize = 16.0;
107   lineend = true;
108   multiline = true;
109   wrapAndPosition ();
110   syntax.setTextData (&textData);
111   syntax.addTextDataListener (this);
112   textData.addTextDataListener (this);
113   textData.addLineTracker (&syntax);
114   textData.clearEvent();
115 }
116 
117 /**
118  * Set the text and
119  * Do the reordering, expanding for the whole text.
120  * Not very efficient with large text.
121  */
122 void
setText(const SString & text)123 STextView::setText(const SString& text)
124 {
125   textData.clear();
126   textData.fireEvent();
127   textData.insert(text);
128 
129   /* HACK FOR LABELS - they neeed to know their exact size */
130   for (unsigned int i=0; i<textData.size(); i++)
131   {
132     textData.setVisible(i);
133     textData.setReordered(i);
134   }
135   wrapAndPosition();
136   textData.fireEvent();
137 }
138 
~STextView()139 STextView::~STextView ()
140 {
141 }
142 
143 void
setClippingArea(int _x,int _y,unsigned int _width,unsigned int _height)144 STextView::setClippingArea (int _x, int _y,
145           unsigned int _width, unsigned int _height)
146 {
147   clipx = _x;
148   clipy = _y;
149   clipw = _width;
150   cliph = _height;
151 }
152 
153 /**
154  * Set the font and recalculate sizes.
155  * @param _font is the new font.
156  */
157 void
setFont(const SString & _font,double _fontSize)158 STextView::setFont (const SString& _font, double _fontSize)
159 {
160   if (_fontSize > 0.0) fontSize = _fontSize;
161   font = SFont(_font, fontSize);
162   setPen ();
163   setReordered ();
164 
165   lineHeight = (unsigned int ) (font.ascent() + font.descent() + font.gap());
166   lineAscent = (unsigned int) font.ascent();
167   preferredSize.height = (textData.size()==0) ? lineHeight
168       : textData.size() *  lineHeight;
169 }
170 
171 /**
172  * Set the size of the font.
173  * @param size is the size of the font.
174  */
175 void
setFontSize(double newsize)176 STextView::setFontSize (double newsize)
177 {
178   font.setSize(newsize);
179   setPen ();
180   setReordered ();
181 
182   lineHeight = (unsigned int ) (font.ascent() + font.descent() + font.gap());
183   lineAscent = (unsigned int) font.ascent();
184   preferredSize.height = (textData.size()==0) ? lineHeight
185       : textData.size() *  lineHeight;
186 }
187 
188 /**
189  * Set the pen size according to font size.
190  */
191 void
setPen()192 STextView::setPen ()
193 {
194   double pensize = 1.0;
195   double pointsize = font.getSize();
196   if (pointsize <= 16)
197   {
198    pensize = 0.125;
199   }
200   else if (pointsize <=24)
201   {
202    pensize = 0.25;
203   }
204   else if (pointsize <=80)
205   {
206    pensize = 0.5;
207   }
208   else if (pointsize <=100)
209   {
210    pensize = 0.75;
211   }
212   lrpen.setLineWidth (pensize);
213   rlpen.setLineWidth (pensize);
214 }
215 
216 /**
217  * Set the text alignment align is true if it is right aligned.
218  */
219 void
setAlignment(bool align)220 STextView::setAlignment (bool align)
221 {
222   alignment = align;
223 }
224 /**
225  */
226 void
setMultiline(bool _multiline)227 STextView::setMultiline (bool _multiline)
228 {
229   multiline = _multiline;
230   setReordered ();
231 }
232 /**
233  */
234 bool
isMultiline() const235 STextView::isMultiline () const
236 {
237   return multiline;
238 }
239 /**
240  * Set the viewport. This is the location that casts to {0;0}
241  * @param _viewPort is the new viewport.
242  */
243 void
setViewPort(const SLocation & _viewPort)244 STextView::setViewPort (const SLocation& _viewPort)
245 {
246   viewPort = _viewPort;
247 }
248 
249 const SLocation&
getViewPort()250 STextView::getViewPort()
251 {
252   return viewPort;
253 }
254 
255 /**
256  * Resize the component and redraw.
257  */
258 void
resize(const SDimension & _dimension)259 STextView::resize (const SDimension& _dimension)
260 {
261   SComponent::resize (_dimension);
262   setReordered ();
263 }
264 
265 unsigned int
getLineIndex(int locy)266 STextView::getLineIndex (int locy)
267 {
268   /* binary search linespan */
269   unsigned int    top;
270   unsigned int    bottom;
271   unsigned int    mid;
272   top = lineSpan.size();
273   bottom = 0;
274   int y = locy - (location.y + viewPort.y) + 1;
275   while (top > bottom)
276   {
277     mid = (top+bottom)/2;
278     unsigned int sindex = lineHeight * sane_index (lineSpan, mid+1);
279     if (y == (int) (sindex))
280     {
281       top = mid;
282       break;
283     }
284     if (y < (int) (sindex))
285     {
286       top = mid;
287       continue;
288     }
289     bottom = mid + 1;
290   }
291   return top;
292 }
293 
294 /**
295  * @param l is the location on the canvas.
296  */
297 SCursorIndex
getCursorIndex(const SLocation & l)298 STextView::getCursorIndex (const SLocation& l)
299 {
300   unsigned int line = getLineIndex (l.y);
301 
302   if (line >= textData.size())
303   {
304     if (line > 0 && !textData.isProperLine (line-1))
305     {
306       return SCursorIndex (line-1, textData.size(line-1));
307     }
308     return SCursorIndex (line, 0);
309   }
310 
311 
312   SV_UINT brk;
313   if (line < textData.size())
314   {
315     setVisible (line);
316     brk = breaks[line];
317   }
318 
319   /* Linear search. I don't expect long lines. */
320   unsigned int offset = lineHeight * sane_index (lineSpan, line);
321 
322   SLocation lm (0, viewPort.y-location.y+(int)offset);
323   bool lr  = textData.isLR (line);
324 
325   /* Transpose to LR */
326   lm.x = l.x-location.x+viewPort.x;
327 
328   /* convert as if it were lr */
329   if (!lr) lm.x =  (int)size.width - (int)lm.x;
330 
331   /* find where the glyph starts */
332   unsigned int i;
333   unsigned int currExt=0;
334   for (i=0; i<brk.size(); i++)
335   {
336     lm.y += lineHeight;
337     if (lm.y > l.y) break;
338     currExt = brk[i];
339   }
340   unsigned int si = textData.size(line);
341   unsigned int nextExt = si;
342   if (i<brk.size()) nextExt = brk[i];
343   // SGC bool endsline = false;
344 
345   /* find the x position witihn the line */
346   SCursorIndex cindex (line, currExt);
347   bool llr = textData.isLR(line);
348   bool before = true;
349   for (i=currExt; i<nextExt && i<si; i++)
350   {
351      unsigned int begpos = posBefore[line][i];
352      unsigned int endpos = posAfter[line][i];
353      unsigned int midpos = (begpos+endpos) / 2;
354 
355      const SGlyph& g = textData.glyphAt (STextIndex (line, i));
356      bool glr = g.isLR();
357      before = ((glr && llr) | (!glr && !llr));
358 
359      if (g.isEOP())
360      {
361        // SGC endsline = true;
362      }
363      else if (lm.x >= (int)begpos && lm.x < (int)midpos)
364      {
365        cindex = SCursorIndex(line, i, before); /* logical before */
366        break;
367      }
368      else if (lm.x >= (int)midpos && lm.x < (int)endpos)
369      {
370        cindex = SCursorIndex(line, i, !before); /* logical after */
371        break;
372      }
373   }
374   if (i==si && lm.x > 0 && si > 0)
375   {
376     /* rl is initialized already */
377     cindex = SCursorIndex(line, i, before);
378   }
379   /* beginning or found */
380   return SCursorIndex(cindex);
381 }
382 
383 SLocation
getCursorLocation(const SCursorIndex & cursorIndex)384 STextView::getCursorLocation (const SCursorIndex& cursorIndex)
385 {
386   return SLocation(getTextLocation (cursorIndex.textIndex, cursorIndex.before));
387 }
388 
389 /**
390  * Convert a line and index to a location on the screen
391  * This is the top left(lr) or right(rl) corner of the glyph.
392  * @param line is the line number.
393  * @param index is the index.
394  * @param before is true if we need the index before the glyph -
395  *  logical meaning.
396  * Here is an LR glyph:
397  *   Paragraph LR  A                        A Paragrph RL
398  *                ^  ^                     ^ ^
399  *                |  |                     | |
400  *     Before ----+  +--- After  Before ---+ +---- After
401  */
402 SLocation
getTextLocation(const STextIndex & textIndex,bool before)403 STextView::getTextLocation (const STextIndex& textIndex, bool before)
404 {
405   unsigned int line = textIndex.line;
406   unsigned int index = textIndex.index;
407   unsigned int ll = sane_index (lineSpan, line);
408   SLocation ret;
409   if (line >= textData.size())
410   {
411     ret  = SLocation (location.x+viewPort.x,
412           location.y+viewPort.y + lineHeight * ll);
413   }
414   else
415   {
416     setVisible (line);
417     SV_UINT brk =  breaks[line];
418     /* It may be second or third line. */
419     unsigned int i = 0;
420     int yoffset = 0;
421     unsigned int posafter = 0;
422     for (i=0; i<brk.size()-1; i++)
423     {
424       if (index < brk[i]) break;
425       posafter = brk[i];
426       yoffset++;
427     }
428 
429     int si = 0;
430     if (index < textData.size(line))
431     {
432        /* convert logical 'before' to phisical */
433        bool glr = textData.isLR(STextIndex(line, index));
434        bool llr = textData.isLR(line);
435 
436        /*  paragraph directionality is different from glyph directionality */
437        bool swap = ((llr && !glr) || (!llr && glr));
438 
439        si = ((before && !swap) || (!before && swap))
440              ? (int) posBefore[line][index]
441              : (int) posAfter[line][index];
442 
443     }
444     else if (index > textData.size(line) && textData.isProperLine(line))
445     {
446       /* last one */
447       si = 0;
448       yoffset++;
449     }
450     else /* Streched beyond the last one. */
451     {
452       SV_UINT v = posAfter[line];
453       unsigned int max = 0;
454       unsigned int maxi = 0;
455       for (unsigned int i=posafter; i<v.size(); i++)
456       {
457         if (v[i] > max)
458         {
459           max = v[i];
460           maxi  = i;
461         }
462       }
463       si = posAfter[line][maxi];
464     }
465     ret = SLocation (location.x+si+viewPort.x,
466         location.y+viewPort.y + lineHeight * (ll+yoffset));
467   }
468   bool lr  = textData.isLR (line);
469   int nloc = (lr) ? ret.x
470      : 2 * location.x + (int)size.width - (int)ret.x - 2 * viewPort.x;
471   return SLocation(nloc, ret.y);
472 }
473 
474 /**
475  * This is coming from the SWindowListener
476  * @param c is the canvas to draw on.
477  * @param x is the upper lect corner.
478  * @param y is the upper lect corner.
479  * @param width is the width of this event.
480  * @param height is the height of this event.
481  */
482 void
redraw(SCanvas * c,int x,int y,unsigned int width,unsigned int height)483 STextView::redraw (SCanvas *c, int x, int y,
484         unsigned int width, unsigned int height)
485 {
486   internalRedraw (c, x, y, width, height);
487 }
488 
489 /**
490  * This is coming from the SWindowListener
491  * @param c is the canvas to draw on.
492  * @param x is the upper lect corner.
493  * @param y is the upper lect corner.
494  * @param width is the width of this event.
495  * @param height is the height of this event.
496  */
497 void
internalRedraw(SCanvas * c,int x,int y,unsigned int width,unsigned int height)498 STextView::internalRedraw (SCanvas *c, int x, int y,
499         unsigned int width, unsigned int height)
500 {
501 #if DEBUG_SPEED
502   x = getLocation ().x;
503   y = getLocation ().y;
504   width = getSize().width;
505   height = getSize().height;
506   timerStart();
507 #endif
508   SLocation lb (x, y);
509   SLocation le (x+width, y+height);
510 
511   unsigned int line = getLineIndex (lb.y);
512 
513   bool islr  = textData.isLR (line);
514   SLocation lstart = lb;
515   if (!islr)
516   {
517     lstart.x = le.x;
518   }
519 
520   SLocation lleft(location.x+viewPort.x,
521        location.y+viewPort.y + (int) lineHeight
522        * (int) sane_index (lineSpan, line));
523   SLocation lright(location.x+viewPort.x+(int)size.width, lleft.y);
524 
525   unsigned int i;
526 
527   // expand +/- SD_PRE_EXPAND  lines by calling textData.size(i).
528   unsigned int start = (line > SD_PRE_EXPAND) ? line-SD_PRE_EXPAND : 0;
529   for (i=start; i<textData.size(); i++)
530   {
531     // dummy is never negative.
532     textData.size(i);
533     if (i>line+SD_PRE_EXPAND) break;
534   }
535 
536   for (i=line; i<textData.size(); i++)
537   {
538     textData.size(i);
539     setVisible (i);
540     islr  = textData.isLR (i);
541     unsigned int cs = islr
542       ?  drawParagraph (c, islr, i, lleft, lb, le, false)
543       :  drawParagraph (c, islr, i, lright, lb, le, false);
544     lleft.y = lleft.y + lineHeight * cs;
545     lright.y = lleft.y;
546     if (lleft.y  > location.y + (int) size.height ) break;
547     if (lleft.y  > y + (int)height) break;
548   }
549 
550 #if DEBUG_SPEED
551   timerStop();
552 #endif
553 }
554 
555 /**
556  * Draw a whole line of glyphs.
557  * @param c is the canvas to draw to
558  * @param islr is true if we draw from left to right.
559  * @param line is the line index to draw.
560  * @param l is the beginning upper corner location
561  * @param lb is the beginning exposure
562  * @param le is the end exposure
563  * @param iswindow is true if we want to set the clipping area
564  *   this is only if you want to experiment - we dont want to do that.
565  * @return the number of lines drawn.
566  */
567 unsigned int
drawParagraph(SCanvas * c,bool islr,unsigned int line,const SLocation & l,const SLocation & lb,const SLocation & le,bool iswindow)568 STextView::drawParagraph (SCanvas* c, bool islr, unsigned int line,
569   const SLocation& l, const SLocation& lb, const SLocation& le, bool iswindow)
570 {
571   SV_UINT br;
572   if (line < breaks.size()) br = breaks[line];
573   unsigned int currExt = 0;
574   SLocation lm = l;
575   unsigned int ls = textData.size(line);
576   unsigned int mycliph = 0;
577 
578   /**
579    *  set clip to line so that we won't overflow...
580    */
581   if (iswindow && clipw != 0 && cliph != 0)
582   {
583     int myclipy0 = (clipy > lm.y) ? clipy : lm.y;
584     int myclipy1 = myclipy0 + lineHeight;
585     if (myclipy1 > clipy + (int) cliph)
586     {
587       myclipy1 = clipy + (int) cliph;
588     }
589     mycliph = (myclipy1 > myclipy0) ? myclipy1-myclipy0 : 0;
590    //fprintf (stderr, "clip=%d,%d wh=%u,%u\n", clipx, myclipy0, clipw, mycliph);
591     if (mycliph)
592     {
593       ((SWindow*)c)->setClippingArea (clipx, myclipy0, clipw, mycliph);
594     }
595   }
596   for (unsigned int i=0; i<ls; i++)
597   {
598      /* move the clipping area */
599      if (br[currExt] == i)
600      {
601        // printer can have same extent
602        while (br[currExt] == i)
603        {
604          currExt++;
605          if (currExt >= br.size()) break;
606        }
607 
608        lm.y = lineHeight * currExt + l.y;
609        if (iswindow && clipw != 0 && cliph != 0)
610        {
611          int myclipy0 = (clipy > lm.y) ? clipy : lm.y;
612          int myclipy1 = myclipy0 + lineHeight;
613          if (myclipy1 > clipy + (int) cliph)
614          {
615            myclipy1 = clipy + (int) cliph;
616          }
617          mycliph = (myclipy1 > myclipy0) ? myclipy1-myclipy0 : 0;
618          if (mycliph)
619          {
620            ((SWindow*)c)->setClippingArea (clipx, myclipy0, clipw, mycliph);
621          }
622        }
623      }
624      lm.x = islr
625         ? l.x + (int) posBefore[line][i]
626         : l.x-1-(int) posAfter[line][i];
627 
628      unsigned int e = posAfter[line][i] - posBefore[line][i];
629 
630      /* is it drawable ? */
631      if (lm.x < le.x  && lb.x < lm.x + (int) e
632         && lm.y <  le.y && lb.y < lm.y + (int) lineHeight)
633      {
634        if (!iswindow || mycliph) drawGlyph (c, lm, e, STextIndex (line,i));
635      }
636   }
637   if (iswindow && clipw!=0 && cliph !=0)
638   {
639        ((SWindow*)c)->setClippingArea (clipx, clipy, clipw, cliph);
640   }
641   return currExt+1;
642 }
643 
644 /**
645  * Set Syntax Hilight Mode
646  */
647 void
setSyntax(const SString & hlm)648 STextView::setSyntax (const SString& hlm)
649 {
650   highlightMode = hlm;
651   // these are kept for backward compatibility, hard coded.
652   if (hlm == "simple" || hlm == "simple-dark" || hlm == "none")
653   {
654     syntax.setSyntax ("");
655   }
656   else
657   {
658     syntax.setSyntax (hlm);
659   }
660 }
661 
662 /**
663  * Get Syntax Hilight Mode
664  */
665 const SString&
getSyntax() const666 STextView::getSyntax () const
667 {
668   return highlightMode;
669 }
670 
671 void
setSyntaxColors(const SSyntaxColors & attr)672 STextView::setSyntaxColors (const SSyntaxColors& attr)
673 {
674   syntaxColors = attr;
675 }
676 
677 const SSyntaxColors&
getSyntaxColors() const678 STextView::getSyntaxColors () const
679 {
680   return syntaxColors;
681 }
682 
683 /**
684  * Set WordWrap  Mode
685  * @param pbm is true if line break is on
686  */
687 void
setWordWrap(bool lbm)688 STextView::setWordWrap (bool lbm)
689 {
690   isWordWrapOn = lbm;
691   setReordered ();
692 }
693 
694 /**
695  * Some stuff displays differently if this is editable.
696  */
697 void
setEditable(bool editable)698 STextView::setEditable (bool editable)
699 {
700   isEditable = editable;
701   setReordered ();
702 }
703 
704 /**
705  * Get WordWrap Mode
706  * @return true if line break is on.
707  */
708 bool
getWordWrap() const709 STextView::getWordWrap () const
710 {
711   return isWordWrapOn;
712 }
713 
714 /**
715  * Syntax Highlighting  system.
716  * Added by Maarten van Gompel <proycon@anaproy.homeip.net>
717  * Note:  this is a dumb system and merely colors single characters.
718  * For error highlighting, set forground to NONE and error to true.
719  */
720 void
syntaxHighlight(STextIndex index,SPen * pen,bool * isError)721 STextView::syntaxHighlight(STextIndex index, SPen* pen, bool *isError)
722 {
723   *isError = false;
724   if (highlightMode == "simple")
725   {
726     const SGlyph& g = textData.glyphAt (index);
727     if (g.isLetter ())
728     {
729     }
730     else if (g.isNumber())
731     {
732       pen->setForeground(SColor("orange"));
733     }
734     // emoji
735     else if (g.getType() == SD_CC_So)
736     {
737       pen->setForeground(SColor("gray90"));
738     }
739     else // not a letter, nor a number
740     {
741       pen->setForeground(SColor("CornflowerBlue"));
742     }
743     if (getLigatureScriptCode (g.getChar()) == SD_AS_LITERAL) *isError = true;
744   }
745   else if (highlightMode == "simple-dark")
746   {
747     const SGlyph& g = textData.glyphAt (index);
748     if (g.isLetter ())
749     {
750     }
751     else if (g.isNumber())
752     {
753       pen->setForeground(SColor("orange4"));
754     }
755     // emoji
756     else if (g.getType() == SD_CC_So)
757     {
758       pen->setForeground(SColor("gray20"));
759     }
760     else // not a letter, nor a number
761     {
762       pen->setForeground(SColor("DeepSkyBlue4"));
763     }
764     if (getLigatureScriptCode (g.getChar()) == SD_AS_LITERAL) *isError = true;
765   }
766   else if (highlightMode != "none" && highlightMode != "")
767   {
768 
769     SSyntax::SS_Tag tag = syntax.getTagByTDI (index);
770     const SGlyph& g = textData.glyphAt (index);
771     if (getLigatureScriptCode (g.getChar()) == SD_AS_LITERAL)
772     {
773       *isError = true;
774       tag = SSyntax::SD_CONTROL;
775       SColor c = syntaxColors.colors[(unsigned int) tag];
776       pen->setForeground(c);
777     }
778     // SD_ERROR and SD_NONE is preserving old color
779     else if (tag == SSyntax::SD_ERROR)
780     {
781       // SColor c = syntaxColors.colors[(unsigned int) SSyntax::SD_NONE];
782       // pen->setForeground(c);
783       *isError = true;
784     }
785     else if (tag == SSyntax::SD_NONE)
786     {
787       // preserve original color
788     }
789     else
790     {
791       SColor c = syntaxColors.colors[(unsigned int) tag];
792       pen->setForeground(c);
793     }
794   }
795   return;
796 }
797 
798 /**
799  * Draw one signle glyph on the screen.
800  * @param c is the canvas to draw to
801  * @param l is the location of the glyph.
802  * @return the length of the text drawn
803  */
804 void
drawGlyph(SCanvas * c,SLocation & l,unsigned int ext,STextIndex index)805 STextView::drawGlyph (SCanvas* c,
806   SLocation& l, unsigned int ext, STextIndex index)
807 {
808   const SGlyph& g = textData.glyphAt (index);
809 
810   SS_Matrix2D dm;
811   dm.y1 = -dm.y1; /* updown */
812 
813 
814   dm.translate (0, font.ascent ());
815   dm.translate ((double)l.x, (double)l.y);
816 
817   if (isHidingText){
818     SColor fg = lrpen.getForeground();
819     c->bitfill (fg, l.x, l.y, ext, lineHeight);
820     return;
821   }
822 
823   SPen p (lrpen);
824   if (!g.isLR())
825   {
826     p = rlpen;
827   }
828 
829   unsigned int explevel = g.getExplicitLevel();
830   bool isError = false;
831   if (g.selected)
832   {
833      //SColor fg = p.getForeground();
834      //SColor bg = p.getBackground();
835      SColor fg = SColor("DeepSkyBlue4");
836      SColor bg = SColor("white");
837      p.setForeground (bg);
838      p.setBackground (fg);
839      c->bitfill (fg, l.x, l.y, ext, lineHeight);
840   }
841    /* fade background according to embed level */
842   else if (isEditable && explevel!=0)
843   {
844      SColor bg = p.getBackground();
845      if (explevel > 5) explevel = 5; /* 5 shades max*/
846      /* This funny linear curve is the result of experiments */
847      double alpha = 1.0/2.0 +  (1.0/2.0) * 0.9 * ((double)explevel)/5.0;
848      /* we fade gray in with an alpha */
849      double cg = 0.5;
850      SColor grey(cg,cg,cg, alpha);
851      bg.blend (grey);
852      p.setBackground (bg);
853      c->bitfill (bg, l.x, l.y, ext, lineHeight);
854   }
855   if (!g.selected)
856   {
857    /*
858     * Syntax highlighting, an addition
859     * by Maarten van Gompel <proycon@anaproy.homeip.net>
860     */
861     syntaxHighlight(index, &p, &isError); /* change pen color if necessary */
862   }
863 
864   if (!lineend && g.isEOP()) return;
865   SS_UCS4 fc = g.getFirstChar();
866   /* I would just check for SD_CC_Mn also */
867   if (!isEditable && (g.isEOL() || fc == SD_CD_LRM || fc == SD_CD_RLM
868       || fc == SD_CD_ZWNJ || fc == SD_CD_ZWJ))
869   {
870     return;
871   }
872   /* Zero width space */
873   if (fc == SD_CD_ZWSP) return;
874 
875   if (!g.isTab()) font.draw (c, p, dm, g);
876 
877   if (g.underlined)
878   {
879     unsigned int w = ext;
880     unsigned int h = lineHeight/24+1;
881     unsigned int base =  (lineAscent + h >= lineHeight) ?
882          lineHeight -1 : lineAscent + h;
883     /* construct a square */
884     // changed in 2.8.2
885     //c->bitfill (underlineColor, l.x, l.y + (int) base - h, w, h);
886     c->bitfill (underlineColor, l.x, l.y + (int) base, w, h);
887   }
888   else if(isError)
889   {
890     SColor errColor = syntaxColors.colors[(unsigned int) SSyntax::SD_ERROR];
891     unsigned int w = ext;
892     unsigned int h = 3; // height occupies 3 pixels
893     unsigned int base =  (lineAscent + h >= lineHeight) ?
894          lineHeight - 1 : lineAscent + h;
895     int screenBase = l.y + (int) base;
896     SBinVector<int> x;
897     SBinVector<int> y;
898     for (unsigned i=0; i<w; i++)
899     {
900       x.append (l.x + i);
901       int state = ((l.x+i+clipx) % 6);
902       switch (state)
903       {
904       case 0: y.append (screenBase); break;
905       case 1: y.append (screenBase-1); break;
906       case 2: y.append (screenBase-1); break;
907       case 3: y.append (screenBase); break;
908       case 4: y.append (screenBase+1); break;
909       case 5: y.append (screenBase+1); break;
910       default: y.append (screenBase);
911       }
912     }
913     if (x.size() > 0)
914     {
915       c->bitpoints (errColor, x.array(), y.array(), x.size());
916     }
917   }
918 }
919 
920 /**
921  * This can be called by the STextData and SSyntax.
922  */
923 void
textChanged(void * src,const STextDataEvent & unparsedEvent)924 STextView::textChanged (void* src, const STextDataEvent& unparsedEvent)
925 {
926   if (src == &syntax && unparsedEvent.attribute)
927   {
928     STextIndex tb = unparsedEvent.start;
929     STextIndex te = unparsedEvent.remaining;
930     // convert te back to text data coords
931     te.line = te.line >= textData.size() ? 0 : textData.size() - te.line;
932     // Filter out visible range
933     unsigned int firstVisible = getLineIndex (0);
934     int height = (int) getSize().height;
935     int width = (int) getSize().width;
936     unsigned int lastVisible = getLineIndex (height);
937     // filter out nont visible portion
938     if (te.line < firstVisible) return;
939     if (tb.line > lastVisible+1) return;
940 
941     if (tb.line < firstVisible) tb.line = firstVisible;
942     tb.index = 0;
943     // non-inclusive
944     if (te.line > lastVisible) te.line = lastVisible+1;
945     te.index = 0;
946     // calculate the screen index, and do a redraw
947     if (te.line > textData.size()) te.line = textData.size();
948     if (tb.line > textData.size()) tb.line = textData.size();
949     int bcoord = (int) lineHeight * sane_index (lineSpan, tb.line);
950     int ecoord = (int) lineHeight * sane_index (lineSpan, te.line);
951     // as we scroll down viewPort.y becomes negative.
952     // y coord of top of starting line.
953     bcoord += (location.y + viewPort.y) - 1;
954     // y coord of top of ending line.
955     ecoord += (location.y + viewPort.y) - 1;
956     SWindow* w = getWindow ();
957     if (w && ecoord > bcoord && bcoord < height && ecoord > 0)
958     {
959       /* request a redraw and clear the whole area + overdraw */
960       w->redraw (true, 0, bcoord, width, ecoord-bcoord + 2);
961 //fprintf (stderr, "redraw 0,%d %u,%u\n", bcoord, getSize().width, ecoord-bcoord);
962 //fprintf (stderr, "tb=%u te=%u\n", tb.line, te.line);
963     }
964   }
965   else
966   {
967     textChangedInternal (src, unparsedEvent);
968   }
969 }
970 
971 void
textChangedInternal(void * src,const STextDataEvent & event)972 STextView::textChangedInternal (void* src, const STextDataEvent& event)
973 {
974   /* The whole text has been cleared */
975   if (textData.size()==0)
976   {
977      wrapAndPosition();
978      SWindow* w = getWindow ();
979      if (w)
980      {
981        /* request a redraw and clear the whole area */
982        w->redraw (true, location.x, location.y, size.width, size.height);
983      }
984      return;
985   }
986   /* overdraw */
987   int odw = (int) lineHeight / 3 + 1;
988   STextIndex tb = textData.getMinTextIndex (event);
989   STextIndex te = textData.getMaxTextIndex (event);
990 
991 
992   unsigned int oldsize = lineSpan.size();
993   unsigned int oldspan = sane_index (lineSpan, oldsize);
994   bool oldlr =  textData.isLR(tb.line);
995 
996   SV_UINT oldbreaks;
997 
998   if (tb.line == te.line && tb.line < oldsize && tb.line < breaks.size())
999   {
1000     /* This is still the old breaks */
1001     oldbreaks = breaks[tb.line];
1002   }
1003 
1004   /* change in text contents */
1005   SV_UINT mapBefore = textData.getLogicalMap(tb.line);
1006   SV_UINT mapAfter = mapBefore;
1007   if (!event.attribute)
1008   {
1009     /* For efficiency, multiline guys will make it only partial */
1010     if (multiline)
1011     {
1012       /* was recalc */
1013       wrapAndPosition (tb.line, te.line+1,
1014          (int)textData.size() - (int) lineSpan.size());
1015     }
1016     else
1017     {
1018       wrapAndPosition ();
1019     }
1020     mapAfter = textData.getLogicalMap(tb.line);
1021     /* find the highest and visual index */
1022   }
1023 
1024   SWindow* w = getWindow();
1025   if (w == 0)
1026   {
1027     /* This is a strange place to return - but we needed to rebuild indeces */
1028     return;
1029   }
1030 
1031   unsigned int newsize = lineSpan.size();
1032 
1033   unsigned int newspan = sane_index (lineSpan, newsize);
1034   bool samebreak = false;
1035 
1036   bool newlr =  textData.isLR(tb.line);
1037   bool drawwholeline = (newlr != oldlr && tb.line == te.line);
1038   if (tb.line == te.line && tb.line < newsize && tb.line <breaks.size())
1039   {
1040     SV_UINT o = oldbreaks;
1041     SV_UINT n = breaks[tb.line];
1042     samebreak = (o.size() == n.size());
1043     if (samebreak)
1044     {
1045       /* of course it break at the end */
1046       for (unsigned int i=0; i+1<n.size(); i++)
1047       {
1048         /* break changed or it was before the text change */
1049         /* for attribute break can not change */
1050         if (!event.attribute && (n[i] != o[i] || tb.index <= n[i]))
1051         {
1052           samebreak = false;
1053           break;
1054         }
1055         /* break is between begin and end */
1056         if (n[i] >= tb.index && n[i] <= te.index)
1057         {
1058           drawwholeline = true;
1059         }
1060       }
1061     }
1062   }
1063   if (tb.line == te.line && drawwholeline)
1064   {
1065     tb.index = 0;
1066     te.index = mapAfter.size();
1067   }
1068 
1069   /* adjust tb te */
1070   if (tb.line == te.line && !drawwholeline && samebreak)
1071   {
1072     unsigned int i;
1073     /* find out lowest common stuff in map */
1074     unsigned int min = mapAfter.size() < mapBefore.size()
1075       ? mapAfter.size() : mapBefore.size();
1076 
1077     /* make logical to visual maps */
1078     SS_UINT * mapa = new SS_UINT[mapAfter.size()+1];
1079     CHECK_NEW (mapa);
1080     for (i=0; i<mapAfter.size(); i++) mapa[i] = mapAfter[i];
1081 
1082     SS_UINT * mapb = new SS_UINT[mapBefore.size()+1];
1083     CHECK_NEW (mapb);
1084     for (i=0; i<mapBefore.size(); i++) mapb[i] = mapBefore[i];
1085 
1086     unsigned int lowestvis = min;
1087     for (i=0; i<min; i++)
1088     {
1089       if (mapb[i] != mapa[i])
1090       {
1091         tb.index = mapa[i];
1092         lowestvis = i;
1093         break;
1094       }
1095       /* at least from here it changed yeah... */
1096       if (mapa[i] == tb.index)
1097       {
1098         lowestvis = i;
1099         break;
1100       }
1101     }
1102     if (i==0)
1103     {
1104       if (mapAfter.size()> 0)
1105       {
1106         tb.index = mapAfter[0];
1107       }
1108       else
1109       {
1110         tb.index = 0;
1111       }
1112      lowestvis=0;
1113     }
1114     /* find out if there is something between zero and lowes vis */
1115     for (i=0; i<lowestvis; i++)
1116     {
1117       /* we can have one glyph difference  */
1118       if (mapa[i]+1 >= tb.index)
1119       {
1120         tb.index = mapa[i];
1121         lowestvis = i;
1122         break;
1123       }
1124     }
1125     // if still between lowest and end there is a lower index, take 0.
1126     for (i=lowestvis; i<mapAfter.size(); i++)
1127     {
1128       if (mapa[i] <= tb.index)
1129       {
1130         /* find the smallest */
1131         unsigned int smallest = mapa[i];
1132         while (++i < mapAfter.size())
1133         {
1134            if (mapa[i] < smallest) smallest = mapa[i];
1135         }
1136         if (smallest > 0) smallest--;
1137         tb.index = smallest;
1138         break;
1139       }
1140     }
1141 
1142     /* for attribute te.index is also used and mapafter = mapbefore */
1143     if (event.attribute && te.index < mapAfter.size())
1144     {
1145       unsigned int vis = mapAfter[te.index];
1146       unsigned int max = mapAfter.size();
1147       for (i=mapAfter.size(); i>vis; i--)
1148       {
1149         if (mapa[i-1] < te.index)
1150         {
1151           te.index= max;
1152           break;
1153         }
1154         max = mapa[i-1];
1155       }
1156     }
1157     else
1158     {
1159       te.index = mapAfter.size();
1160     }
1161     delete [] mapa;
1162     delete [] mapb;
1163   }
1164 
1165   SLocation lb = getTextLocation (tb);
1166   SLocation le = getTextLocation (te);
1167 
1168   /*
1169    * Get smallest and biggest.
1170    */
1171   if (tb.line == te.line && samebreak && le.y == lb.y)
1172   {
1173     if (le.x < lb.x)
1174     {
1175        int tmp = lb.x; lb.x = le.x; le.x = tmp;
1176     }
1177     for (unsigned int i=tb.index; i<=te.index; i++)
1178     {
1179       SLocation l = getTextLocation (STextIndex(tb.line, i));
1180       if (l.x < lb.x) lb = l;
1181       if (l.x > le.x) le = l;
1182       l = getTextLocation (STextIndex(tb.line, i), false);
1183       if (l.x < lb.x) lb = l;
1184       if (l.x > le.x) le = l;
1185     }
1186   }
1187 
1188 //fprintf (stderr, "lb.x =%d, le.x=%u\n", lb.x, le.x);
1189   /* make sure we are inside the window */
1190   if (lb.y + (int)lineHeight < 0) lb.y = -(int)lineHeight;
1191   if (le.y > location.y + (int)size.height) le.y =  size.height + location.y;
1192 
1193   /* Text content did not change, only the attribute */
1194 
1195   int starty = (lb.y > 5) ? lb.y - odw: 0;
1196   unsigned int lheight = lineHeight + 2*odw;
1197 
1198   if (event.attribute)
1199   {
1200     /* single */
1201     if (lb.y == le.y && samebreak)
1202     {
1203       /* we add 1 to make sure it is non-null positive */
1204       w->redraw (true, lb.x-odw, starty, (unsigned int) (le.x-lb.x)+2*odw, lheight);
1205     }
1206     else // multiline - redraw whole thing.
1207     {
1208       le = getTextLocation (STextIndex (te.line, textData.size(te.line)));
1209       if (lb.y < le.y) /* always */
1210       {
1211         w->redraw (true, location.x, starty,
1212                 size.width, lheight + (unsigned int)(le.y-lb.y));
1213       }
1214       else /* I dont know what happened - redraw */
1215       {
1216         w->redraw (true, location.x, location.y, size.width, size.height);
1217       }
1218     }
1219     return;
1220   }
1221 
1222   /* Change is inside a single paragraph */
1223   if (tb.line == te.line && oldsize == newsize && oldspan == newspan)
1224   {
1225     /* The whole change is on the same line (breaks did not change)  */
1226     if (lb.y == le.y && samebreak)
1227     {
1228       bool lrline  = textData.isLR (tb.line);
1229       int wid = 0;
1230       if (lrline)
1231       {
1232          //lb.x = lb.x;
1233          wid = (int) size.width; /* till end of line */
1234       }
1235       else
1236       {
1237          lb.x = 0;
1238          wid = le.x + location.x;
1239       }
1240       /* redraw till end of line */
1241       w->redraw (true, lb.x-odw, starty, (unsigned int) wid + 2*odw, lheight);
1242     }
1243     else /* This is a multi-line paragraph change. redraw till end */
1244     {
1245       le = getTextLocation (STextIndex (te.line, textData.size(te.line)));
1246       if (le.y > lb.y) /* always */
1247       {
1248         w->redraw (true, location.x, starty,
1249             size.width, lheight + (unsigned int)(le.y-lb.y));
1250       }
1251       else /* I dont know what happened - redraw */
1252       {
1253         w->redraw (true, location.x, location.y, size.width, size.height);
1254       }
1255 
1256     }
1257     return;
1258   }
1259   /* Multi-paragraph change. Is it visible? */
1260   if (starty < location.y + (int) size.height)
1261   {
1262     w->redraw (true, location.x, starty,
1263         size.width, location.y + (int)size.height - starty);
1264   }
1265 }
1266 
1267 /**
1268  * Makr lines so that they will recalculate
1269  */
1270 void
setReordered()1271 STextView::setReordered()
1272 {
1273   /* HACK FOR LABELS - they neeed to know their exact size */
1274   if (!isEditable)
1275   {
1276     for (unsigned int i=0; i<textData.size(); i++)
1277     {
1278       textData.setVisible(i);
1279       textData.setReordered (i);
1280     }
1281     wrapAndPosition();
1282     return;
1283   }
1284   lineSpan.clear ();
1285   unsigned int sum = 0;
1286   for (unsigned int i=0; i<textData.size(); i++)
1287   {
1288     sum++;
1289     textData.setReordered (i);
1290     lineSpan.append (sum);
1291   }
1292 }
1293 /**
1294  * Walk through the text and remake the linespan.
1295  * Recalculate the preferred sizes.
1296  */
1297 void
wrapAndPosition()1298 STextView::wrapAndPosition ()
1299 {
1300   lineHeight = (unsigned int ) (font.ascent() + font.descent() + font.gap());
1301   lineAscent = (unsigned int) font.ascent();
1302   breaks.clear ();
1303   posAfter.clear ();
1304   posBefore.clear ();
1305   lineSpan.clear ();
1306 
1307   SH_UINT hint;
1308   unsigned int sum = 0;
1309   preferredSize.width = 0;
1310   for (unsigned int i=0; i<textData.size(); i++)
1311   {
1312     sum += wrapAndPosition (i, &hint);
1313     lineSpan.append (sum);
1314   }
1315   preferredSize.height = (textData.size()==0) ? lineHeight
1316       : textData.size() *  lineHeight;
1317 }
1318 
1319 /**
1320  * recalculate partially. This is used for multi-line stuff
1321  * to make it more efficient.
1322  * @param from is the starting index.
1323  * @param until is the index before last
1324  * @paran addcount show how many lines were added. can be negative (removed)
1325  */
1326 void
wrapAndPosition(unsigned int from,unsigned int until,int addcount)1327 STextView::wrapAndPosition (unsigned int from, unsigned int until, int addcount)
1328 {
1329   //unsigned int longestline = 1;
1330   unsigned int sum = sane_index (lineSpan, from);
1331   int mycount=0;
1332   unsigned int i=0;
1333   SH_UINT cache;
1334   for (i=from; i<textData.size() && i<until; i++)
1335   {
1336     sum += wrapAndPosition (i, &cache);
1337     lineSpan.insert (i, sum);
1338     mycount++;
1339   }
1340 
1341   unsigned int removesum = sum;
1342   if (i<breaks.size())
1343   {
1344     for (int j=0; j<mycount-addcount; j++)
1345     {
1346       /* yes i ! */
1347       removesum = lineSpan[i];
1348       lineSpan.remove (i);
1349       breaks.remove (i);
1350       posBefore.remove (i);
1351       posAfter.remove (i);
1352     }
1353   }
1354   /* recalibrate the whole linespan array */
1355   if (removesum != sum)
1356   {
1357     while (i < textData.size())
1358     {
1359       unsigned int s = lineSpan[i];
1360       if (removesum > sum)
1361       {
1362          s -= removesum-sum;
1363       }
1364       else
1365       {
1366          s += sum-removesum;
1367       }
1368       lineSpan.replace (i, s);
1369       i++;
1370     }
1371   }
1372   preferredSize.height = (textData.size()==0) ? lineHeight
1373       : textData.size() *  lineHeight;
1374 }
1375 
1376 /**
1377  * Caclulate the extent as one line.
1378  * It inserts an element at line in positions, and breaks.
1379  * The positions array will have the positions of the end
1380  * of the glyph, ragrdless of paragraph embedding, in LR order.
1381  * @param line is the line to calculate.
1382  * @return the linesspan
1383  */
1384 unsigned int
wrapAndPosition(unsigned int line,SH_UINT * cache)1385 STextView::wrapAndPosition (unsigned int line, SH_UINT* cache)
1386 {
1387   /* first line is always visible - multiline */
1388 
1389   if (!textData.isVisible(line))
1390   {
1391     SV_UINT empty;
1392     posAfter.insert(line, empty);
1393     posBefore.insert(line, empty);
1394     breaks.insert(line, empty);
1395     /* make span 1 */
1396     return 1;
1397   }
1398 
1399   /* +1 is only because of zero sized arrays */
1400   SS_UCS4* logical = new SS_UCS4[textData.size(line)+1];
1401   CHECK_NEW(logical);
1402   SS_UCS4* logicalBefore = new SS_UCS4[textData.size(line)+1];
1403   CHECK_NEW(logicalBefore);
1404 
1405   SS_UCS4* visual = new SS_UCS4[textData.size(line)+1];
1406   CHECK_NEW(visual);
1407 
1408   unsigned int ae=0;
1409   unsigned int ce=0;
1410   unsigned int le=0;
1411   unsigned int i;
1412   SV_UINT b;
1413   bool wrapNext = false;
1414   bool wrapPage = false;
1415   unsigned int lastbreak = 0;
1416   /* go through the text in logical order */
1417   for (i=0; i<textData.size(line); i++)
1418   {
1419     const SGlyph& g = textData.glyphAt (STextIndex (line, i));
1420     ce = cache->get (g.charKey());
1421     if (ce ==0)
1422     {
1423       ce = (unsigned int) (0.5 + font.width (g));
1424       SS_UCS4 fc = g.getFirstChar();
1425       if (!isEditable && (g.isEOL() || fc == SD_CD_LRM || fc == SD_CD_RLM
1426          || fc == SD_CD_ZWNJ || fc == SD_CD_ZWJ))
1427       {
1428         ce = 1;
1429       }
1430       else if (fc == SD_CD_ZWSP)
1431       {
1432         ce = 1;
1433       }
1434       else  if (g.isTab())
1435       {
1436         int tabsize = (int)(4.0 * font.getSize());
1437         if (tabsize < 1) tabsize = 1;
1438         ce = tabsize - (le % (unsigned int)tabsize);
1439         if (multiline && (le + ce)> size.width && le > 0)
1440         {
1441            /* force line break. */
1442            ce = (int)(4.0 * font.getSize());
1443         }
1444       }
1445       /* Shaped glyphs width and tab may change. */
1446       if (g.getShapeArray()==0 && !g.isTab())
1447       {
1448         cache->put (g.charKey(), ce);
1449       }
1450     }
1451     le  += ce;
1452     ae += ce;
1453     logical[i] = ce;
1454 
1455     if (multiline && le > size.width && i > 0 && !wrapNext)
1456     {
1457        if (g.isTab())
1458        {
1459          /* nothing to do. we break here */
1460        }
1461        /* we might want to wrap earlier */
1462        else if (isWordWrapOn && !textData.canWrap (STextIndex (line, i-1)))
1463        {
1464          unsigned int oldae = ae;
1465          unsigned int oldi = i;
1466          while (i>lastbreak && !textData.canWrap (STextIndex (line, i-1)))
1467          {
1468            ae -= logical[i];
1469            i--;
1470          }
1471          /* emergency break */
1472          if (i==lastbreak)
1473          {
1474            ae = oldae;
1475            i = oldi;
1476          }
1477        }
1478        le = logical[i];
1479        b.append (i);
1480        lastbreak = i;
1481     }
1482     else if (wrapNext)
1483     {
1484       le = logical[i];
1485       lastbreak = i;
1486       if (wrapPage)
1487       {
1488         if (i == 1 && line == 0) // first line, first char is a FF
1489         {
1490           b.append (i);
1491         }
1492         else if (i > 1) // we have something on the line
1493         {
1494           //const SGlyph& gp = textData.glyphAt (STextIndex (line, i-2));
1495           // the one before FF is an FF too
1496           b.append (i);
1497         }
1498         unsigned int currSpan =  (line == 0 || lineSpan.size() < line-1)
1499           ? 0 : lineSpan[line-1];
1500 
1501         // how many more we need to add to reach top?
1502         unsigned int lh = (lineHeight == 0) ? 1 : lineHeight;
1503         unsigned int linesPerPage = printerPageSize / lh;
1504         while (((currSpan + b.size()) % linesPerPage) != 0)
1505         {
1506            b.append (i);
1507         }
1508 
1509       }
1510       else
1511       {
1512         b.append (i);
1513       }
1514     }
1515     wrapNext = (multiline && g.isEOL() && !g.isEOP());
1516     if (printerPageSize != 0 && wrapNext)
1517     {
1518       wrapPage = g.isFF();
1519     }
1520   }
1521   /* now b contains the logical positions where the glyph should start at 0 */
1522   b.append (textData.size(line));
1523 
1524   /* break the text into lines */
1525   textData.setLineBreaks(line, b);
1526 
1527   if (preferredSize.width < ae) preferredSize.width = ae;
1528 
1529   /* make a visual map */
1530   for (i=0; i<textData.size(line); i++)
1531   {
1532     visual[i] = textData.toLogical (line, i);
1533   }
1534   le = 0;
1535   /* add up visual */
1536   unsigned int nextbreak = 0;
1537 
1538   /* go through in visual order */
1539   for (i=0; i<textData.size(line); i++)
1540   {
1541     /* we use visual break here */
1542     while (nextbreak < b.size() && i == b[nextbreak])
1543     {
1544       nextbreak++;
1545       le = 0;
1546     }
1547     /*
1548      *  save space - make confusion :).
1549      *  logical[visual[i]] will not be used
1550      *  any more here so we re-use it
1551      */
1552     logicalBefore[visual[i]] = le;
1553     le += logical[visual[i]];
1554     logical[visual[i]] = le;
1555   }
1556   SV_UINT pb;
1557   SV_UINT pa;
1558   for (i=0; i<textData.size(line); i++)
1559   {
1560      pb.append (logicalBefore[i]);
1561      pa.append (logical[i]);
1562   }
1563 
1564   delete[] logicalBefore;
1565   delete[] logical;
1566   delete[] visual;
1567 
1568   breaks.insert(line, b);
1569   posAfter.insert(line, pa);
1570   posBefore.insert(line, pb);
1571   /* updating lineSpan is in the calling routine*/
1572 
1573   return b.size();
1574 }
1575 
1576 /**
1577  * Set the background.
1578  * @param bg is the new background
1579  */
1580 void
setBackground(const SColor & bg)1581 STextView::setBackground (const SColor& bg)
1582 {
1583   lrpen.setBackground (bg);
1584   rlpen.setBackground (bg);
1585 }
1586 
1587 /**
1588  * Set the foreground.
1589  * @param fg is the new foreground
1590  */
1591 void
setForeground(const SColor & rlfg,const SColor & lrfg)1592 STextView::setForeground (const SColor& rlfg, const SColor& lrfg)
1593 {
1594   lrpen.setForeground (rlfg);
1595   rlpen.setForeground (lrfg);
1596 }
1597 const SColor&
getBackground()1598 STextView::getBackground ()
1599 {
1600   return lrpen.getBackground();
1601 }
1602 
1603 const SColor&
getForeground(bool lr)1604 STextView::getForeground (bool lr)
1605 {
1606   return (lr) ? lrpen.getForeground() : rlpen.getForeground();
1607 }
1608 
1609 /**
1610  * If show <- newline characters
1611  * @param _lineend is true if lineend is shown.
1612  */
1613 void
setLineEndMark(bool _lineend)1614 STextView::setLineEndMark (bool _lineend)
1615 {
1616   lineend = _lineend;
1617   setReordered();
1618   SWindow* w = getWindow();
1619   if (w == 0)
1620   {
1621     return;
1622   }
1623   if (!w->isVisible()) return;
1624   w->redraw (true, location.x, location.y, size.width, size.height);
1625 }
1626 
1627 /**
1628  * Is new line shown?
1629  * @return true if newline characters are shown.
1630  */
1631 bool
getLineEndMark() const1632 STextView::getLineEndMark () const
1633 {
1634   return lineend;
1635 }
1636 
1637 /**
1638  * calculate the height of the document.
1639  */
1640 unsigned int
getDocumentHeight() const1641 STextView::getDocumentHeight() const
1642 {
1643   if (textData.size() == 0)
1644   {
1645     return lineHeight;
1646   }
1647   unsigned int fheight = lineSpan[textData.size()-1];
1648   if (textData.isProperLine (textData.size()-1))
1649   {
1650     fheight += 1;
1651   }
1652   return (fheight * lineHeight);
1653 }
1654 
1655 /**
1656  * return the 'sane index'.
1657  * That is, at index 0 it should be 0
1658  * at index at array->size() is should be the last element.
1659  */
1660 static unsigned int
sane_index(const SV_UINT & array,unsigned int index)1661 sane_index (const SV_UINT& array, unsigned int index)
1662 {
1663   if (index == 0 || array.size() < index) return  0;
1664   return array[index-1];
1665 }
1666 
1667 void
setUnderlineColor(const SColor & c)1668 STextView::setUnderlineColor (const SColor& c)
1669 {
1670   underlineColor = c;
1671 }
1672 
1673 /**
1674  * Mark this visible
1675  */
1676 void
setVisible(unsigned int line)1677 STextView::setVisible (unsigned int line)
1678 {
1679   if (!textData.isVisible (line))
1680   {
1681     textData.setVisible(line);
1682     wrapAndPosition (line, line+1, 0);
1683   }
1684   else if (textData.isReordered(line))
1685   {
1686     wrapAndPosition (line, line+1, 0);
1687   }
1688 }
1689 
1690 /**
1691  * return the cursor index that is left (screen-wise) of
1692  * ci.
1693  * @checkembed is true  check for embedding boundary
1694  */
1695 SCursorIndex
leftOf(const SCursorIndex & ci)1696 STextView::leftOf (const SCursorIndex& ci)
1697 {
1698   if (ci.textIndex.line >= textData.size())
1699   {
1700     return SCursorIndex(ci.textIndex.line, ci.textIndex.index);
1701   }
1702   setVisible (ci.textIndex.line);
1703   SCursorIndex cn = moveCursor (ci, false);
1704   return SCursorIndex (cn);
1705 }
1706 
1707 SCursorIndex
rightOf(const SCursorIndex & ci)1708 STextView::rightOf (const SCursorIndex& ci)
1709 {
1710   if (ci.textIndex.line >= textData.size())
1711   {
1712     return SCursorIndex(ci.textIndex.line, ci.textIndex.index);
1713   }
1714   setVisible (ci.textIndex.line);
1715   SCursorIndex cn = moveCursor (ci, true);
1716   return SCursorIndex (cn);
1717 }
1718 
1719 /**
1720  * Move the cursor up or down one slot visuallly.
1721  * You have to expand the paragrapgh before this call.
1722  * @param ci is the input index.
1723  * @praram isup is true if we walk right visuallly.
1724  * @return 1 index up or down.
1725  */
1726 SCursorIndex
moveCursor(const SCursorIndex & ci,bool isup)1727 STextView::moveCursor (const SCursorIndex& ci, bool isup)
1728 {
1729   SV_UINT map = textData.getLogicalMap(ci.textIndex.line);
1730   if (map.size()==0) return SCursorIndex(ci.textIndex.line,0);
1731   if (textData.isProperLine (ci.textIndex.line))
1732   {
1733     map.truncate (map.size()-1);
1734   }
1735   if (map.size()==0) return SCursorIndex(ci.textIndex.line,0);
1736   /* we need to find the current index in the map. */
1737   int current = map.size();
1738   for (unsigned int i=0; i<map.size(); i++)
1739   {
1740     if (map[i] == ci.textIndex.index)
1741     {
1742       current = i;
1743     }
1744   }
1745   /* normalize to our visual index. */
1746   bool llr = textData.isLR(ci.textIndex.line);
1747 
1748   /* for lr before is after and vice versa */
1749   bool clr = textData.isLR(ci.textIndex);
1750 
1751   /* it is easier to visualize this in visual order */
1752   bool cbefore = clr ? ci.before : !ci.before;
1753   bool resbefore = false;
1754 
1755   SEmbedState eold = textData.getEmbedState(ci.textIndex);
1756   SEmbedState enew;
1757   bool samembed = true;
1758   if (isup)
1759   {
1760     if (cbefore) // set it to after
1761     {
1762       resbefore = false;
1763     }
1764     else /* increment visual index and increment one */
1765     {
1766       resbefore = false;
1767       //current = current+1;
1768       current = llr ? current+1 : current-1;
1769       /* check the mbedding state of the next */
1770       unsigned int ei = (current < 0) ? map.size()+1
1771         : map[(unsigned int)current];
1772       enew = textData.getEmbedState(STextIndex (ci.textIndex.line, ei));
1773       samembed = (enew==eold);
1774     }
1775   }
1776   else /* !isup */
1777   {
1778     /* set it to after */
1779     if (!cbefore)
1780     {
1781       resbefore = true;
1782     }
1783     /* set it to next */
1784     else
1785     {
1786       resbefore = true;
1787       /* the map is not visual */
1788       current = llr ? current-1 : current+1;
1789       /* check the mbedding state of the next */
1790       unsigned int ei = (current < 0) ? map.size()+1
1791         : map[(unsigned int)current];
1792       enew = textData.getEmbedState(STextIndex (ci.textIndex.line, ei));
1793       samembed = (enew==eold);
1794       //current = current-1;
1795     }
1796   }
1797   /* check bounds */
1798   unsigned int resindex = 0;
1799   if (current < 0)
1800   {
1801     current = 0;
1802     /* we need to move to the rightmost */
1803     resbefore  = llr;
1804     SCursorIndex rc(ci.textIndex.line, map[(unsigned int)current], resbefore);
1805     bool islr = textData.isLR (rc.textIndex);
1806     if (!islr) rc.before = !rc.before;
1807     return SCursorIndex (rc);
1808   }
1809   if (current >= (int)map.size())
1810   {
1811     current = (int)map.size();
1812     resbefore  = true;
1813     return SCursorIndex (ci.textIndex.line, (unsigned int) current, resbefore);
1814   }
1815   resindex = map[(unsigned int) current];
1816   /* check changed index */
1817   SCursorIndex res (ci.textIndex.line, resindex, resbefore);
1818   bool nlr = textData.isLR (res.textIndex);
1819 
1820   /* rl before is logical after switch */
1821   /* nlr already switched */
1822   /* back to logical order */
1823   if (!nlr) res.before = !res.before;
1824 
1825   /* embed changed  */
1826   if (!samembed)
1827   {
1828     res.before = !res.before;
1829     return SCursorIndex (res);
1830   }
1831   /* direction changed */
1832   if (clr != nlr)
1833   {
1834     res.before = !res.before;
1835   }
1836   return SCursorIndex (res);
1837 }
1838 
1839 
1840 void
setHideText(bool is)1841 STextView::setHideText(bool is)
1842 {
1843     isHidingText = is;
1844 }
1845 bool
isHideText()1846 STextView::isHideText()
1847 {
1848     return isHidingText;
1849 }
1850