libyui-ncurses  2.48.1
NCRichText.cc
1 /*
2  Copyright (C) 2000-2012 Novell, Inc
3  This library is free software; you can redistribute it and/or modify
4  it under the terms of the GNU Lesser General Public License as
5  published by the Free Software Foundation; either version 2.1 of the
6  License, or (at your option) version 3.0 of the License. This library
7  is distributed in the hope that it will be useful, but WITHOUT ANY
8  WARRANTY; without even the implied warranty of MERCHANTABILITY or
9  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
10  License for more details. You should have received a copy of the GNU
11  Lesser General Public License along with this library; if not, write
12  to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
13  Floor, Boston, MA 02110-1301 USA
14 */
15 
16 
17 /*-/
18 
19  File: NCRichText.cc
20 
21  Author: Michael Andres <ma@suse.de>
22 
23 /-*/
24 
25 #define YUILogComponent "ncurses"
26 #include <yui/YUILog.h>
27 #include "NCRichText.h"
28 #include "YNCursesUI.h"
29 #include "stringutil.h"
30 #include "stdutil.h"
31 #include <sstream>
32 #include <boost/algorithm/string.hpp>
33 
34 #include <yui/YMenuItem.h>
35 #include <yui/YApplication.h>
36 
37 using stdutil::form;
38 
39 
40 const unsigned NCRichText::listindent = 4;
41 const std::wstring NCRichText::listleveltags( L"@*+o#-%$&" );//
42 
43 const bool NCRichText::showLinkTarget = false;
44 
45 std::map<std::wstring, std::wstring> NCRichText::_charentity;
46 
47 
48 
49 const std::wstring NCRichText::entityLookup( const std::wstring & val_r )
50 {
51  //strip leading '#', if any
52  std::wstring::size_type hash = val_r.find( L"#", 0 );
53  std::wstring ascii = L"";
54 
55  if ( hash != std::wstring::npos )
56  {
57  std::wstring s = val_r.substr( hash + 1 );
58  wchar_t *endptr;
59  //and try to convert to int (wcstol only knows "0x" for hex)
60  boost::replace_all( s, "x", "0x" );
61 
62  long int c = std::wcstol( s.c_str(), &endptr, 0 );
63 
64  //conversion succeeded
65 
66  if ( s.c_str() != endptr )
67  {
68  std::wostringstream ws;
69  ws << char( c );
70  ascii = ws.str();
71  }
72  }
73 
74 #define REP(l,r) _charentity[l] = r
75  if ( _charentity.empty() )
76  {
77  // initialize replacement for character entities. A value of NULL
78  // means do not replace.
79  std::wstring product;
80  NCstring::RecodeToWchar( YUI::app()->productName(), "UTF-8", &product );
81 
82  REP( L"amp", L"&" );
83  REP( L"gt", L">" );
84  REP( L"lt", L"<" );
85  REP( L"nbsp", L" " );
86  REP( L"quot", L"\"" );
87  REP( L"product", product );
88  }
89 
90  std::map<std::wstring, std::wstring>::const_iterator it = _charentity.find( val_r );
91 
92  if ( it != _charentity.end() )
93  {
94  //known entity - already in the map
95  return it->second;
96  }
97  else
98  {
99  if ( !ascii.empty() )
100  {
101  //replace ascii code by character - e.g. #42 -> '*'
102  //and insert into map to remember it
103  REP( val_r, ascii );
104  }
105  }
106 
107  return ascii;
108 
109 #undef REP
110 }
111 
112 
113 
114 /**
115  * Filter out the known &...; entities and return the text with entities
116  * replaced
117  **/
118 const std::wstring NCRichText::filterEntities( const std::wstring & text )
119 {
120  std::wstring txt = text;
121  // filter known '&..;'
122 
123  for ( std::wstring::size_type special = txt.find( L"&" );
124  special != std::wstring::npos;
125  special = txt.find( L"&", special + 1 ) )
126  {
127  std::wstring::size_type colon = txt.find( L";", special + 1 );
128 
129  if ( colon == std::wstring::npos )
130  break; // no ';' -> no need to continue
131 
132  const std::wstring repl = entityLookup( txt.substr( special + 1, colon - special - 1 ) );
133 
134  if ( !repl.empty()
135  || txt.substr( special + 1, colon - special - 1 ) == L"product" ) // always replace &product;
136  {
137  txt.replace( special, colon - special + 1, repl );
138  }
139  else
140  yuiMilestone() << "porn.bat" << std::endl;
141  }
142 
143  return txt;
144 }
145 
146 
147 void NCRichText::Anchor::draw( NCPad & pad, const chtype attr, int color )
148 {
149  unsigned l = sline;
150  unsigned c = scol;
151 
152  while ( l < eline )
153  {
154  pad.move( l, c );
155  pad.chgat( -1, attr, color );
156  ++l;
157  c = 0;
158  }
159 
160  pad.move( l, c );
161 
162  pad.chgat( ecol - c, attr, color );
163 }
164 
165 
166 NCRichText::NCRichText( YWidget * parent, const std::string & ntext,
167  bool plainTextMode )
168  : YRichText( parent, ntext, plainTextMode )
169  , NCPadWidget( parent )
170  , text( ntext )
171  , plainText( plainTextMode )
172  , textwidth( 0 )
173  , cl( 0 )
174  , cc( 0 )
175  , cindent( 0 )
176  , atbol( true )
177  , preTag( false )
178  , Tattr( 0 )
179 {
180  yuiDebug() << std::endl;
181  activeLabelOnly = true;
182  setValue( ntext );
183 }
184 
185 
186 NCRichText::~NCRichText()
187 {
188  yuiDebug() << std::endl;
189 }
190 
191 
192 int NCRichText::preferredWidth()
193 {
194  return wGetDefsze().W;
195 }
196 
197 
198 int NCRichText::preferredHeight()
199 {
200  return wGetDefsze().H;
201 }
202 
203 
204 void NCRichText::setEnabled( bool do_bv )
205 {
206  NCWidget::setEnabled( do_bv );
207  YRichText::setEnabled( do_bv );
208 }
209 
210 
211 void NCRichText::setSize( int newwidth, int newheight )
212 {
213  wRelocate( wpos( 0 ), wsze( newheight, newwidth ) );
214 }
215 
216 
217 void NCRichText::setLabel( const std::string & nlabel )
218 {
219  // not implemented: YRichText::setLabel( nlabel );
220  NCPadWidget::setLabel( NCstring( nlabel ) );
221 }
222 
223 
224 void NCRichText::setValue( const std::string & ntext )
225 {
226  DelPad();
227  text = NCstring( ntext );
228  YRichText::setValue( ntext );
229  Redraw();
230 }
231 
232 
233 void NCRichText::wRedraw()
234 {
235  if ( !win )
236  return;
237 
238  bool initial = ( !myPad() || !myPad()->Destwin() );
239 
240  if ( !( plainText || anchors.empty() ) )
241  arm( armed );
242 
243  NCPadWidget::wRedraw();
244 
245  if ( initial && autoScrollDown() )
246  {
247  myPad()->ScrlTo( wpos( myPad()->maxy(), 0 ) );
248  }
249 
250  return;
251 }
252 
253 
254 void NCRichText::wRecoded()
255 {
256  DelPad();
257  wRedraw();
258 }
259 
260 
261 NCursesEvent NCRichText::wHandleInput( wint_t key )
262 {
263  NCursesEvent ret;
264  handleInput( key );
265 
266  if ( !( plainText || anchors.empty() ) )
267  {
268  switch ( key )
269  {
270  case KEY_SPACE:
271  case KEY_RETURN:
272 
273  if ( armed != Anchor::unset )
274  {
275  ret = NCursesEvent::menu;
276  std::string str;
277  NCstring::RecodeFromWchar( anchors[armed].target, "UTF-8", &str );
278  yuiMilestone() << "LINK: " << str << std::endl;
279  ret.result = str;
280  ret.selection = NULL;
281  }
282 
283  break;
284  }
285  }
286 
287  return ret;
288 }
289 
290 
291 NCPad * NCRichText::CreatePad()
292 {
293  wsze psze( defPadSze() );
294  textwidth = psze.W;
295  NCPad * npad = new NCPad( psze.H, textwidth, *this );
296  return npad;
297 }
298 
299 
300 void NCRichText::DrawPad()
301 {
302  yuiDebug()
303  << "Start: plain mode " << plainText << std::endl
304  << " padsize " << myPad()->size() << std::endl
305  << " text length " << text.str().size() << std::endl;
306 
307  myPad()->bkgdset( wStyle().richtext.plain );
308  myPad()->clear();
309 
310  if ( plainText )
311  DrawPlainPad();
312  else
313  DrawHTMLPad();
314 
315  yuiDebug() << "Done" << std::endl;
316 }
317 
318 
319 void NCRichText::DrawPlainPad()
320 {
321  NCtext ftext( text );
322  yuiDebug() << "ftext is " << wsze( ftext.Lines(), ftext.Columns() ) << std::endl;
323 
324  AdjustPad( wsze( ftext.Lines(), ftext.Columns() ) );
325 
326  cl = 0;
327 
328  for ( NCtext::const_iterator line = ftext.begin();
329  line != ftext.end(); ++line, ++cl )
330  {
331  myPad()->addwstr( cl, 0, ( *line ).str().c_str() );
332  }
333 }
334 
335 void NCRichText::PadPreTXT( const wchar_t * osch, const unsigned olen )
336 {
337  std::wstring wtxt( osch, olen );
338 
339  // resolve the entities even in PRE (#71718)
340  wtxt = filterEntities( wtxt );
341 
342  NCstring nctxt( wtxt );
343  NCtext ftext( nctxt );
344 
345  // insert the text
346  const wchar_t * sch = wtxt.data();
347 
348  while ( *sch )
349  {
350  myPad()->addwstr( sch, 1 ); // add one wide chararacter
351 
352  ++sch;
353  }
354 }
355 
356 //
357 // DrawHTMLPad tools
358 //
359 
360 inline void SkipToken( const wchar_t *& wch )
361 {
362  do
363  {
364  ++wch;
365  }
366  while ( *wch && *wch != L'>' );
367 
368  if ( *wch )
369  ++wch;
370 }
371 
372 
373 static std::wstring WStoken( L" \n\t\v\r\f" );
374 
375 
376 inline void SkipWS( const wchar_t *& wch )
377 {
378  do
379  {
380  ++wch;
381  }
382  while ( *wch && WStoken.find( *wch ) != std::wstring::npos );
383 }
384 
385 
386 static std::wstring WDtoken( L" <\n\t\v\r\f" ); // WS + TokenStart '<'
387 
388 
389 inline void SkipWord( const wchar_t *& wch )
390 {
391  do
392  {
393  ++wch;
394  }
395  while ( *wch && WDtoken.find( *wch ) == std::wstring::npos );
396 }
397 
398 static std::wstring PREtoken( L"<\n\v\r\f" ); // line manipulations + TokenStart '<'
399 
400 
401 inline void SkipPreTXT( const wchar_t *& wch )
402 {
403  do
404  {
405  ++wch;
406  }
407  while ( *wch && PREtoken.find( *wch ) == std::wstring::npos );
408 }
409 
410 
411 //
412 // Calculate longest line of text in <pre> </pre> tags
413 // and adjust the pad accordingly
414 //
415 void NCRichText::AdjustPrePad( const wchar_t *osch )
416 {
417  const wchar_t * wch = osch;
418  std::wstring wstr( wch, 6 );
419 
420  do
421  {
422  ++wch;
423  wstr.assign( wch, 6 );
424  }
425  while ( *wch && wstr != L"</pre>" );
426 
427  std::wstring wtxt( osch, wch - osch );
428 
429  // resolve the entities to get correct length for calculation of longest line
430  wtxt = filterEntities( wtxt );
431 
432  // replace <br> by \n to get appropriate lines in NCtext
433  boost::replace_all( wtxt, L"<br>", L"\n" );
434  boost::replace_all( wtxt, L"<br/>", L"\n" );
435 
436  yuiDebug() << "Text: " << wtxt << " initial length: " << wch - osch << std::endl;
437 
438  NCstring nctxt( wtxt );
439  NCtext ftext( nctxt );
440 
441  std::list<NCstring>::const_iterator line;
442  size_t llen = 0; // longest line
443 
444  // iterate through NCtext
445  for ( line = ftext.Text().begin(); line != ftext.Text().end(); ++line )
446  {
447  size_t tmp_len = 0;
448 
449  tmp_len = textWidth( (*line).str() );
450 
451  if ( tmp_len > llen )
452  llen = tmp_len;
453  }
454  yuiDebug() << "Longest line: " << llen << std::endl;
455 
456  if ( llen > textwidth )
457  {
458  textwidth = llen;
459  AdjustPad( wsze( cl + ftext.Lines(), llen ) ); // adjust pad to longest line
460  }
461 
462 }
463 
464 void NCRichText::DrawHTMLPad()
465 {
466  yuiDebug() << "Start:" << std::endl;
467 
468  liststack = std::stack<int>();
469  canchor = Anchor();
470  anchors.clear();
471  armed = Anchor::unset;
472 
473  cl = 0;
474  cc = 0;
475  cindent = 0;
476  myPad()->move( cl, cc );
477  atbol = true;
478 
479  const wchar_t * wch = ( wchar_t * )text.str().data();
480  const wchar_t * swch = 0;
481 
482  while ( *wch )
483  {
484  switch ( *wch )
485  {
486  case L' ':
487  case L'\t':
488  case L'\n':
489  case L'\v':
490  case L'\r':
491  case L'\f':
492  if ( ! preTag )
493  {
494  SkipWS( wch );
495  PadWS();
496  }
497  else
498  {
499  switch ( *wch )
500  {
501  case L' ': // add white space
502  case L'\t':
503  myPad()->addwstr( wch, 1 );
504  break;
505 
506  case L'\n':
507  case L'\f':
508  PadNL(); // add new line
509  break;
510 
511  default:
512  yuiDebug() << "Ignoring " << *wch << std::endl;
513  }
514  ++wch;
515  }
516 
517  break;
518 
519  case L'<':
520  swch = wch;
521  SkipToken( wch );
522 
523  if ( PadTOKEN( swch, wch ) )
524  break; // strip token
525  else
526  wch = swch; // reset and fall through
527 
528  default:
529  swch = wch;
530 
531  if ( !preTag )
532  {
533  SkipWord( wch );
534  PadTXT( swch, wch - swch );
535  }
536  else
537  {
538  SkipPreTXT( wch );
539  PadPreTXT( swch, wch - swch );
540  }
541 
542  break;
543  }
544  }
545 
546  PadBOL();
547  AdjustPad( wsze( cl, textwidth ) );
548 
549  yuiDebug() << "Anchors: " << anchors.size() << std::endl;
550 
551  for ( unsigned i = 0; i < anchors.size(); ++i )
552  {
553  yuiDebug() << form( " %2d: [%2d,%2d] -> [%2d,%2d]",
554  i,
555  anchors[i].sline, anchors[i].scol,
556  anchors[i].eline, anchors[i].ecol ) << std::endl;
557  }
558 }
559 
560 
561 inline void NCRichText::PadNL()
562 {
563  cc = cindent;
564 
565  if ( ++cl == ( unsigned )myPad()->height() )
566  {
567  AdjustPad( wsze( myPad()->height() + defPadSze().H, textwidth ) );
568  }
569 
570  myPad()->move( cl, cc );
571 
572  atbol = true;
573 }
574 
575 
576 inline void NCRichText::PadBOL()
577 {
578  if ( !atbol )
579  PadNL();
580 }
581 
582 
583 inline void NCRichText::PadWS( const bool tab )
584 {
585  if ( atbol )
586  return; // no WS at beginning of line
587 
588  if ( cc == textwidth )
589  {
590  PadNL();
591  }
592  else
593  {
594  myPad()->addwstr( L" " );
595  ++cc;
596  }
597 }
598 
599 
600 inline void NCRichText::PadTXT( const wchar_t * osch, const unsigned olen )
601 {
602  std::wstring txt( osch, olen );
603 
604  txt = filterEntities( txt );
605 
606  size_t len = textWidth( txt );
607 
608  if ( !atbol && cc + len > textwidth )
609  PadNL();
610 
611  // insert the text
612  const wchar_t * sch = txt.data();
613 
614  while ( *sch )
615  {
616  myPad()->addwstr( sch, 1 ); // add one wide chararacter
617  cc += wcwidth( *sch );
618  atbol = false; // at begin of line = false
619 
620  if ( cc >= textwidth )
621  {
622  PadNL(); // add a new line
623  }
624 
625  sch++;
626  }
627 }
628 
629 /**
630  * Get the number of columns needed to print a 'std::wstring'. Only printable characters
631  * are taken into account because otherwise 'wcwidth' would return -1 (e.g. for '\n').
632  * Tabs are calculated with tabsize().
633  * Attention: only use textWidth() to calculate space, not for iterating through a text
634  * or to get the length of a text (real text length includes new lines).
635  */
636 size_t NCRichText::textWidth( std::wstring wstr )
637 {
638  size_t len = 0;
639  std::wstring::const_iterator wstr_it; // iterator for std::wstring
640 
641  for ( wstr_it = wstr.begin(); wstr_it != wstr.end() ; ++wstr_it )
642  {
643  // check whether char is printable
644  if ( iswprint( *wstr_it ) )
645  {
646  len += wcwidth( *wstr_it );
647  }
648  else if ( *wstr_it == '\t' )
649  {
650  len += myPad()->tabsize();
651  }
652  }
653 
654  return len;
655 }
656 
657 
658 /**
659  * Set character attributes (e.g. color, font face...)
660  **/
661 inline void NCRichText::PadSetAttr()
662 {
663  const NCstyle::StRichtext & style( wStyle().richtext );
664  chtype nbg = style.plain;
665 
666  if ( Tattr & T_ANC )
667  {
668  nbg = style.link;
669  }
670  else if ( Tattr & T_HEAD )
671  {
672  nbg = style.title;
673  }
674  else
675  {
676  switch ( Tattr & Tfontmask )
677  {
678  case T_BOLD:
679  nbg = style.B;
680  break;
681 
682  case T_IT:
683  nbg = style.I;
684  break;
685 
686  case T_TT:
687  nbg = style.T;
688  break;
689 
690  case T_BOLD|T_IT:
691  nbg = style.BI;
692  break;
693 
694  case T_BOLD|T_TT:
695  nbg = style.BT;
696  break;
697 
698  case T_IT|T_TT:
699  nbg = style.IT;
700  break;
701 
702  case T_BOLD|T_IT|T_TT:
703  nbg = style.BIT;
704  break;
705  }
706  }
707 
708  myPad()->bkgdset( nbg );
709 }
710 
711 
712 void NCRichText::PadSetLevel()
713 {
714  cindent = listindent * liststack.size();
715 
716  if ( cindent > textwidth / 2 )
717  cindent = textwidth / 2;
718 
719  if ( atbol )
720  {
721  cc = cindent;
722  myPad()->move( cl, cc );
723  }
724 }
725 
726 
727 void NCRichText::PadChangeLevel( bool down, int tag )
728 {
729  if ( down )
730  {
731  if ( liststack.size() )
732  liststack.pop();
733  }
734  else
735  {
736  liststack.push( tag );
737  }
738 
739  PadSetLevel();
740 }
741 
742 
743 void NCRichText::openAnchor( std::wstring args )
744 {
745  canchor.open( cl, cc );
746 
747  const wchar_t * ch = ( wchar_t * )args.data();
748  const wchar_t * lookupstr = L"href = ";
749  const wchar_t * lookup = lookupstr;
750 
751  for ( ; *ch && *lookup; ++ch )
752  {
753  wchar_t c = towlower( *ch );
754 
755  switch ( c )
756  {
757  case L'\t':
758  case L' ':
759 
760  if ( *lookup != L' ' )
761  lookup = lookupstr;
762 
763  break;
764 
765  default:
766  if ( *lookup == L' ' )
767  {
768  ++lookup;
769 
770  if ( !*lookup )
771  {
772  // ch is the 1st char after lookupstr
773  --ch; // end of loop will ++ch
774  break;
775  }
776  }
777 
778  if ( c == *lookup )
779  ++lookup;
780  else
781  lookup = lookupstr;
782 
783  break;
784  }
785  }
786 
787  if ( !*lookup )
788  {
789  const wchar_t * delim = ( *ch == L'"' ) ? L"\"" : L" \t";
790  args = ( *ch == L'"' ) ? ++ch : ch;
791 
792  std::wstring::size_type end = args.find_first_of( delim );
793 
794  if ( end != std::wstring::npos )
795  args.erase( end );
796 
797  canchor.target = args;
798  }
799  else
800  {
801  yuiError() << "No value for 'HREF=' in anchor '" << args << "'" << std::endl;
802  }
803 }
804 
805 
806 void NCRichText::closeAnchor()
807 {
808  canchor.close( cl, cc );
809 
810  if ( canchor.valid() )
811  anchors.push_back( canchor );
812 
813  canchor = Anchor();
814 }
815 
816 
817 // expect "<[/]value>"
818 bool NCRichText::PadTOKEN( const wchar_t * sch, const wchar_t *& ech )
819 {
820  // "<[/]value>"
821  if ( *sch++ != L'<' || *( ech - 1 ) != L'>' )
822  return false;
823 
824  // "[/]value>"
825  bool endtag = ( *sch == L'/' );
826 
827  if ( endtag )
828  ++sch;
829 
830  // "value>"
831  if ( ech - sch <= 1 )
832  return false;
833 
834  std::wstring value( sch, ech - 1 - sch );
835 
836  std::wstring args;
837 
838  std::wstring::size_type argstart = value.find_first_of( L" \t\n" );
839 
840  if ( argstart != std::wstring::npos )
841  {
842  args = value.substr( argstart );
843  value.erase( argstart );
844  }
845 
846  for ( unsigned i = 0; i < value.length(); ++i )
847  {
848  if ( isupper( value[i] ) )
849  {
850  value[i] = static_cast<char>( tolower( value[i] ) );
851  }
852  }
853 
854  int leveltag = 0;
855 
856  int headinglevel = 0;
857 
858  TOKEN token = T_UNKNOWN;
859 
860  switch ( value.length() )
861  {
862  case 1:
863 
864  if ( value[0] == 'b' ) token = T_BOLD;
865  else if ( value[0] == 'i' ) token = T_IT;
866  else if ( value[0] == 'p' ) token = T_PAR;
867  else if ( value[0] == 'a' ) token = T_ANC;
868  else if ( value[0] == 'u' ) token = T_BOLD;
869 
870  break;
871 
872  case 2:
873  if ( value == L"br" ) token = T_BR;
874  else if ( value == L"em" ) token = T_IT;
875  else if ( value == L"h1" ) { token = T_HEAD; headinglevel = 1; }
876  else if ( value == L"h2" ) { token = T_HEAD; headinglevel = 2; }
877  else if ( value == L"h3" ) { token = T_HEAD; headinglevel = 3; }
878  else if ( value == L"hr" ) token = T_IGNORE;
879  else if ( value == L"li" ) token = T_LI;
880  else if ( value == L"ol" ) { token = T_LEVEL; leveltag = 1; }
881  else if ( value == L"qt" ) token = T_IGNORE;
882  else if ( value == L"tt" ) token = T_TT;
883  else if ( value == L"ul" ) { token = T_LEVEL; leveltag = 0; }
884 
885  break;
886 
887  case 3:
888 
889  if ( value == L"big" ) token = T_IGNORE;
890  else if ( value == L"pre" ) token = T_PLAIN;
891  // <br> and <hr> are the only non-pair tags currently supported.
892  // We treat bellow these two special cases in order to work as
893  // users expect. This issue was described at
894  // https://github.com/libyui/libyui-ncurses/issues/33
895  else if ( value == L"br/" ) token = T_BR;
896  else if ( value == L"hr/" ) token = T_IGNORE;
897 
898  break;
899 
900  case 4:
901  if ( value == L"bold" ) token = T_BOLD;
902  else if ( value == L"code" ) token = T_TT;
903  else if ( value == L"font" ) token = T_IGNORE;
904 
905  break;
906 
907  case 5:
908  if ( value == L"large" ) token = T_IGNORE;
909  else if ( value == L"small" ) token = T_IGNORE;
910 
911  break;
912 
913  case 6:
914  if ( value == L"center" ) token = T_PAR;
915  else if ( value == L"strong" ) token = T_BOLD;
916 
917  break;
918 
919  case 10:
920  if ( value == L"blockquote" ) token = T_PAR;
921 
922  break;
923 
924  default:
925  token = T_UNKNOWN;
926 
927  break;
928  }
929 
930  if ( token == T_UNKNOWN )
931  {
932  yuiDebug() << "T_UNKNOWN :" << value << ":" << args << ":" << std::endl;
933  // see bug #67319
934  // return false;
935  return true;
936  }
937 
938  if ( token == T_IGNORE )
939  return true;
940 
941  switch ( token )
942  {
943  case T_LEVEL:
944  PadChangeLevel( endtag, leveltag );
945  PadBOL();
946  // add new line after end of the list
947  // (only at the very end)
948  if ( endtag && !cindent )
949  PadNL();
950 
951  break;
952 
953  case T_BR:
954  PadNL();
955 
956  break;
957 
958  case T_HEAD:
959  if ( endtag )
960  Tattr &= ~token;
961  else
962  Tattr |= token;
963 
964  PadSetAttr();
965  PadBOL();
966 
967  if ( headinglevel && endtag )
968  PadNL();
969 
970  break;
971 
972  case T_PAR:
973  PadBOL();
974 
975  if ( !cindent )
976  {
977  if ( endtag )
978  // add new line after closing tag (FaTE 3124)
979  PadNL();
980  }
981 
982  break;
983 
984  case T_LI:
985  PadSetLevel();
986  PadBOL();
987 
988  if ( !endtag )
989  {
990  std::wstring tag;
991 
992  if ( liststack.empty() )
993  {
994  tag = std::wstring( listindent, L' ' );
995  }
996  else
997  {
998  wchar_t buf[16];
999 
1000  if ( liststack.top() )
1001  {
1002  swprintf( buf, 15, L"%2ld. ", liststack.top()++ );
1003  }
1004  else
1005  {
1006  swprintf( buf, 15, L" %lc ", listleveltags[liststack.size()%listleveltags.size()] );
1007  }
1008 
1009  tag = buf;
1010  }
1011 
1012  // outsent list tag:
1013  cc = ( tag.size() < cc ? cc - tag.size() : 0 );
1014 
1015  myPad()->move( cl, cc );
1016 
1017  PadTXT( tag.c_str(), tag.size() );
1018 
1019  atbol = true;
1020  }
1021 
1022  break;
1023 
1024  case T_PLAIN:
1025 
1026  if ( !endtag )
1027  {
1028  preTag = true; // display text preserving newlines and spaces
1029  AdjustPrePad( ech );
1030  }
1031  else
1032  {
1033  preTag = false;
1034  PadNL(); // add new line (text may continue after </pre>)
1035  }
1036 
1037  break;
1038 
1039  case T_ANC:
1040 
1041  if ( endtag )
1042  {
1043  closeAnchor();
1044  }
1045  else
1046  {
1047  openAnchor( args );
1048  }
1049 
1050  // fall through
1051 
1052  case T_BOLD:
1053  case T_IT:
1054  case T_TT:
1055  if ( endtag )
1056  Tattr &= ~token;
1057  else
1058  Tattr |= token;
1059 
1060  PadSetAttr();
1061 
1062  break;
1063 
1064  case T_IGNORE:
1065  case T_UNKNOWN:
1066  break;
1067  }
1068 
1069  return true;
1070 }
1071 
1072 
1073 void NCRichText::arm( unsigned i )
1074 {
1075  if ( !myPad() )
1076  {
1077  armed = i;
1078  return;
1079  }
1080 
1081  yuiDebug() << i << " (" << armed << ")" << std::endl;
1082 
1083  if ( i == armed )
1084  {
1085  if ( armed != Anchor::unset )
1086  {
1087  // just redraw
1088  anchors[armed].draw( *myPad(), wStyle().richtext.getArmed( GetState() ), 0 );
1089  myPad()->update();
1090  }
1091 
1092  return;
1093  }
1094 
1095  if ( armed != Anchor::unset )
1096  {
1097  anchors[armed].draw( *myPad(), wStyle().richtext.link, ( int ) wStyle().richtext.visitedlink );
1098  armed = Anchor::unset;
1099  }
1100 
1101  if ( i != Anchor::unset )
1102  {
1103  armed = i;
1104  anchors[armed].draw( *myPad(), wStyle().richtext.getArmed( GetState() ), 0 );
1105  }
1106 
1107  if ( showLinkTarget )
1108  {
1109  if ( armed != Anchor::unset )
1110  NCPadWidget::setLabel( NCstring( anchors[armed].target ) );
1111  else
1112  NCPadWidget::setLabel( NCstring() );
1113  }
1114  else
1115  {
1116  myPad()->update();
1117  }
1118 }
1119 
1120 
1121 void NCRichText::HScroll( unsigned total, unsigned visible, unsigned start )
1122 {
1123  NCPadWidget::HScroll( total, visible, start );
1124  // no hyperlink handling needed, because Ritchtext does not HScroll
1125 }
1126 
1127 
1128 void NCRichText::VScroll( unsigned total, unsigned visible, unsigned start )
1129 {
1130  NCPadWidget::VScroll( total, visible, start );
1131 
1132  if ( plainText || anchors.empty() )
1133  return; // <-- no links to check
1134 
1135  // Take care of hyperlinks: Check whether an armed link is visible.
1136  // If not arm the first visible link on page or none.
1137  vScrollFirstvisible = start;
1138 
1139  vScrollNextinvisible = start + visible;
1140 
1141  if ( armed != Anchor::unset )
1142  {
1143  if ( anchors[armed].within( vScrollFirstvisible, vScrollNextinvisible ) )
1144  return; // <-- armed link is vissble
1145  else
1146  disarm();
1147  }
1148 
1149  for ( unsigned i = 0; i < anchors.size(); ++i )
1150  {
1151  if ( anchors[i].within( vScrollFirstvisible, vScrollNextinvisible ) )
1152  {
1153  arm( i );
1154  break;
1155  }
1156  }
1157 }
1158 
1159 
1160 bool NCRichText::handleInput( wint_t key )
1161 {
1162  if ( plainText || anchors.empty() )
1163  {
1164  return NCPadWidget::handleInput( key );
1165  }
1166 
1167  // take care of hyperlinks
1168  bool handled = true;
1169 
1170  switch ( key )
1171  {
1172  case KEY_LEFT:
1173  // jump to previous link; scroll up if none
1174  {
1175  unsigned newarmed = Anchor::unset;
1176 
1177  if ( armed == Anchor::unset )
1178  {
1179  // look for an anchor above current page
1180  for ( unsigned i = anchors.size(); i; )
1181  {
1182  --i;
1183 
1184  if ( anchors[i].eline < vScrollFirstvisible )
1185  {
1186  newarmed = i;
1187  break;
1188  }
1189  }
1190  }
1191  else if ( armed > 0 )
1192  {
1193  newarmed = armed - 1;
1194  }
1195 
1196  if ( newarmed == Anchor::unset )
1197  {
1198  handled = NCPadWidget::handleInput( KEY_UP );
1199  }
1200  else
1201  {
1202  if ( !anchors[newarmed].within( vScrollFirstvisible, vScrollNextinvisible ) )
1203  myPad()->ScrlLine( anchors[newarmed].sline );
1204 
1205  arm( newarmed );
1206  }
1207  }
1208 
1209  break;
1210 
1211  case KEY_RIGHT:
1212  // jump to next link; scroll down if none
1213  {
1214  unsigned newarmed = Anchor::unset;
1215 
1216  if ( armed == Anchor::unset )
1217  {
1218  // look for an anchor below current page
1219  for ( unsigned i = 0; i < anchors.size(); ++i )
1220  {
1221  if ( anchors[i].sline >= vScrollNextinvisible )
1222  {
1223  newarmed = i;
1224  break;
1225  }
1226  }
1227  }
1228  else if ( armed + 1 < anchors.size() )
1229  {
1230  newarmed = armed + 1;
1231  }
1232 
1233  if ( newarmed == Anchor::unset )
1234  {
1235  handled = NCPadWidget::handleInput( KEY_DOWN );
1236  }
1237  else
1238  {
1239  if ( !anchors[newarmed].within( vScrollFirstvisible, vScrollNextinvisible ) )
1240  myPad()->ScrlLine( anchors[newarmed].sline );
1241 
1242  arm( newarmed );
1243  }
1244  }
1245 
1246  break;
1247 
1248  case KEY_UP:
1249  // arm previous visible link; scroll up if none
1250 
1251  if ( armed != Anchor::unset
1252  && armed > 0
1253  && anchors[armed-1].within( vScrollFirstvisible, vScrollNextinvisible ) )
1254  {
1255  arm( armed - 1 );
1256  }
1257  else
1258  {
1259  handled = NCPadWidget::handleInput( key );
1260  }
1261 
1262  break;
1263 
1264  case KEY_DOWN:
1265  // arm next visible link; scroll down if none
1266 
1267  if ( armed != Anchor::unset
1268  && armed + 1 < anchors.size()
1269  && anchors[armed+1].within( vScrollFirstvisible, vScrollNextinvisible ) )
1270  {
1271  arm( armed + 1 );
1272  }
1273  else
1274  {
1275  handled = NCPadWidget::handleInput( key );
1276  }
1277 
1278  break;
1279 
1280  default:
1281  handled = NCPadWidget::handleInput( key );
1282  };
1283 
1284  return handled;
1285 }
1286 
1287 
Definition: NCtext.h:37
virtual void setEnabled(bool do_bv)
Pure virtual to make sure every widget implements it.
Definition: NCRichText.cc:204
Definition: NCPad.h:93
Definition: position.h:109
int chgat(int n, attr_t attr, short color, const void *opts=NULL)
Change the attributes of the next n characters in the current line.
Definition: ncursesw.h:1417
int move(int y, int x)
Move cursor the this position.
Definition: ncursesw.h:1155
virtual void setEnabled(bool do_bv)=0
Pure virtual to make sure every widget implements it.
Definition: NCWidget.cc:391
Definition: position.h:154