libyui-ncurses  2.54.5
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 void NCRichText::activateLink( const std::string & url )
262 {
263  NCursesEvent event = NCursesEvent::menu;
264  event.result = url;
265  event.widget = this;
266  YNCursesUI::ui()->sendEvent( event );
267 }
268 
269 
270 NCursesEvent NCRichText::wHandleInput( wint_t key )
271 {
272  NCursesEvent ret;
273  handleInput( key );
274 
275  if ( !( plainText || anchors.empty() ) )
276  {
277  switch ( key )
278  {
279  case KEY_SPACE:
280  case KEY_RETURN:
281 
282  if ( armed != Anchor::unset )
283  {
284  ret = NCursesEvent::menu;
285  std::string str;
286  NCstring::RecodeFromWchar( anchors[armed].target, "UTF-8", &str );
287  yuiMilestone() << "LINK: " << str << std::endl;
288  ret.result = str;
289  ret.selection = NULL;
290  }
291 
292  break;
293  }
294  }
295  return ret;
296 }
297 
298 
299 NCPad * NCRichText::CreatePad()
300 {
301  wsze psze( defPadSze() );
302  textwidth = psze.W;
303  NCPad * npad = new NCPad( psze.H, textwidth, *this );
304  return npad;
305 }
306 
307 
308 void NCRichText::DrawPad()
309 {
310  yuiDebug()
311  << "Start: plain mode " << plainText << std::endl
312  << " padsize " << myPad()->size() << std::endl
313  << " text length " << text.str().size() << std::endl;
314 
315  myPad()->bkgdset( wStyle().richtext.plain );
316  myPad()->clear();
317 
318  if ( plainText )
319  DrawPlainPad();
320  else
321  DrawHTMLPad();
322 
323  yuiDebug() << "Done" << std::endl;
324 }
325 
326 
327 void NCRichText::DrawPlainPad()
328 {
329  NCtext ftext( text );
330  yuiDebug() << "ftext is " << wsze( ftext.Lines(), ftext.Columns() ) << std::endl;
331 
332  AdjustPad( wsze( ftext.Lines(), ftext.Columns() ) );
333 
334  cl = 0;
335 
336  for ( NCtext::const_iterator line = ftext.begin();
337  line != ftext.end(); ++line, ++cl )
338  {
339  myPad()->addwstr( cl, 0, ( *line ).str().c_str() );
340  }
341 }
342 
343 void NCRichText::PadPreTXT( const wchar_t * osch, const unsigned olen )
344 {
345  std::wstring wtxt( osch, olen );
346 
347  // resolve the entities even in PRE (#71718)
348  wtxt = filterEntities( wtxt );
349 
350  NCstring nctxt( wtxt );
351  NCtext ftext( nctxt );
352 
353  // insert the text
354  const wchar_t * sch = wtxt.data();
355 
356  while ( *sch )
357  {
358  myPad()->addwstr( sch, 1 ); // add one wide chararacter
359 
360  ++sch;
361  }
362 }
363 
364 //
365 // DrawHTMLPad tools
366 //
367 
368 inline void SkipToken( const wchar_t *& wch )
369 {
370  do
371  {
372  ++wch;
373  }
374  while ( *wch && *wch != L'>' );
375 
376  if ( *wch )
377  ++wch;
378 }
379 
380 
381 static std::wstring WStoken( L" \n\t\v\r\f" );
382 
383 
384 inline void SkipWS( const wchar_t *& wch )
385 {
386  do
387  {
388  ++wch;
389  }
390  while ( *wch && WStoken.find( *wch ) != std::wstring::npos );
391 }
392 
393 
394 static std::wstring WDtoken( L" <\n\t\v\r\f" ); // WS + TokenStart '<'
395 
396 
397 inline void SkipWord( const wchar_t *& wch )
398 {
399  do
400  {
401  ++wch;
402  }
403  while ( *wch && WDtoken.find( *wch ) == std::wstring::npos );
404 }
405 
406 static std::wstring PREtoken( L"<\n\v\r\f" ); // line manipulations + TokenStart '<'
407 
408 
409 inline void SkipPreTXT( const wchar_t *& wch )
410 {
411  do
412  {
413  ++wch;
414  }
415  while ( *wch && PREtoken.find( *wch ) == std::wstring::npos );
416 }
417 
418 
419 //
420 // Calculate longest line of text in <pre> </pre> tags
421 // and adjust the pad accordingly
422 //
423 void NCRichText::AdjustPrePad( const wchar_t *osch )
424 {
425  const wchar_t * wch = osch;
426  std::wstring wstr( wch, 6 );
427 
428  do
429  {
430  ++wch;
431  wstr.assign( wch, 6 );
432  }
433  while ( *wch && wstr != L"</pre>" );
434 
435  std::wstring wtxt( osch, wch - osch );
436 
437  // resolve the entities to get correct length for calculation of longest line
438  wtxt = filterEntities( wtxt );
439 
440  // replace <br> by \n to get appropriate lines in NCtext
441  boost::replace_all( wtxt, L"<br>", L"\n" );
442  boost::replace_all( wtxt, L"<br/>", L"\n" );
443 
444  yuiDebug() << "Text: " << wtxt << " initial length: " << wch - osch << std::endl;
445 
446  NCstring nctxt( wtxt );
447  NCtext ftext( nctxt );
448 
449  std::list<NCstring>::const_iterator line;
450  size_t llen = 0; // longest line
451 
452  // iterate through NCtext
453  for ( line = ftext.Text().begin(); line != ftext.Text().end(); ++line )
454  {
455  size_t tmp_len = 0;
456 
457  tmp_len = textWidth( (*line).str() );
458 
459  if ( tmp_len > llen )
460  llen = tmp_len;
461  }
462  yuiDebug() << "Longest line: " << llen << std::endl;
463 
464  if ( llen > textwidth )
465  {
466  textwidth = llen;
467  AdjustPad( wsze( cl + ftext.Lines(), llen ) ); // adjust pad to longest line
468  }
469 
470 }
471 
472 void NCRichText::DrawHTMLPad()
473 {
474  yuiDebug() << "Start:" << std::endl;
475 
476  liststack = std::stack<int>();
477  canchor = Anchor();
478  anchors.clear();
479  armed = Anchor::unset;
480 
481  cl = 0;
482  cc = 0;
483  cindent = 0;
484  myPad()->move( cl, cc );
485  atbol = true;
486 
487  const wchar_t * wch = ( wchar_t * )text.str().data();
488  const wchar_t * swch = 0;
489 
490  while ( *wch )
491  {
492  switch ( *wch )
493  {
494  case L' ':
495  case L'\t':
496  case L'\n':
497  case L'\v':
498  case L'\r':
499  case L'\f':
500  if ( ! preTag )
501  {
502  SkipWS( wch );
503  PadWS();
504  }
505  else
506  {
507  switch ( *wch )
508  {
509  case L' ': // add white space
510  case L'\t':
511  myPad()->addwstr( wch, 1 );
512  break;
513 
514  case L'\n':
515  case L'\f':
516  PadNL(); // add new line
517  break;
518 
519  default:
520  yuiDebug() << "Ignoring " << *wch << std::endl;
521  }
522  ++wch;
523  }
524 
525  break;
526 
527  case L'<':
528  swch = wch;
529  SkipToken( wch );
530 
531  if ( PadTOKEN( swch, wch ) )
532  break; // strip token
533  else
534  wch = swch; // reset and fall through
535 
536  default:
537  swch = wch;
538 
539  if ( !preTag )
540  {
541  SkipWord( wch );
542  PadTXT( swch, wch - swch );
543  }
544  else
545  {
546  SkipPreTXT( wch );
547  PadPreTXT( swch, wch - swch );
548  }
549 
550  break;
551  }
552  }
553 
554  PadBOL();
555  AdjustPad( wsze( cl, textwidth ) );
556 
557  yuiDebug() << "Anchors: " << anchors.size() << std::endl;
558 
559  for ( unsigned i = 0; i < anchors.size(); ++i )
560  {
561  yuiDebug() << form( " %2d: [%2d,%2d] -> [%2d,%2d]",
562  i,
563  anchors[i].sline, anchors[i].scol,
564  anchors[i].eline, anchors[i].ecol ) << std::endl;
565  }
566 }
567 
568 
569 inline void NCRichText::PadNL()
570 {
571  cc = cindent;
572 
573  if ( ++cl == ( unsigned )myPad()->height() )
574  {
575  AdjustPad( wsze( myPad()->height() + defPadSze().H, textwidth ) );
576  }
577 
578  myPad()->move( cl, cc );
579 
580  atbol = true;
581 }
582 
583 
584 inline void NCRichText::PadBOL()
585 {
586  if ( !atbol )
587  PadNL();
588 }
589 
590 
591 inline void NCRichText::PadWS( bool tab )
592 {
593  if ( atbol )
594  return; // no WS at beginning of line
595 
596  if ( cc == textwidth )
597  {
598  PadNL();
599  }
600  else
601  {
602  myPad()->addwstr( L" " );
603  ++cc;
604  }
605 }
606 
607 
608 inline void NCRichText::PadTXT( const wchar_t * osch, const unsigned olen )
609 {
610  std::wstring txt( osch, olen );
611 
612  txt = filterEntities( txt );
613 
614  size_t len = textWidth( txt );
615 
616  if ( !atbol && cc + len > textwidth )
617  PadNL();
618 
619  // insert the text
620  const wchar_t * sch = txt.data();
621 
622  while ( *sch )
623  {
624  myPad()->addwstr( sch, 1 ); // add one wide chararacter
625  cc += wcwidth( *sch );
626  atbol = false; // at begin of line = false
627 
628  if ( cc >= textwidth )
629  {
630  PadNL(); // add a new line
631  }
632 
633  sch++;
634  }
635 }
636 
637 /**
638  * Get the number of columns needed to print a 'std::wstring'. Only printable characters
639  * are taken into account because otherwise 'wcwidth' would return -1 (e.g. for '\n').
640  * Tabs are calculated with tabsize().
641  * Attention: only use textWidth() to calculate space, not for iterating through a text
642  * or to get the length of a text (real text length includes new lines).
643  */
644 size_t NCRichText::textWidth( std::wstring wstr )
645 {
646  size_t len = 0;
647  std::wstring::const_iterator wstr_it; // iterator for std::wstring
648 
649  for ( wstr_it = wstr.begin(); wstr_it != wstr.end() ; ++wstr_it )
650  {
651  // check whether char is printable
652  if ( iswprint( *wstr_it ) )
653  {
654  len += wcwidth( *wstr_it );
655  }
656  else if ( *wstr_it == '\t' )
657  {
658  len += myPad()->tabsize();
659  }
660  }
661 
662  return len;
663 }
664 
665 
666 /**
667  * Set character attributes (e.g. color, font face...)
668  **/
669 inline void NCRichText::PadSetAttr()
670 {
671  const NCstyle::StRichtext & style( wStyle().richtext );
672  chtype nbg = style.plain;
673 
674  if ( Tattr & T_ANC )
675  {
676  nbg = style.link;
677  }
678  else if ( Tattr & T_HEAD )
679  {
680  nbg = style.title;
681  }
682  else
683  {
684  switch ( Tattr & Tfontmask )
685  {
686  case T_BOLD:
687  nbg = style.B;
688  break;
689 
690  case T_IT:
691  nbg = style.I;
692  break;
693 
694  case T_TT:
695  nbg = style.T;
696  break;
697 
698  case T_BOLD|T_IT:
699  nbg = style.BI;
700  break;
701 
702  case T_BOLD|T_TT:
703  nbg = style.BT;
704  break;
705 
706  case T_IT|T_TT:
707  nbg = style.IT;
708  break;
709 
710  case T_BOLD|T_IT|T_TT:
711  nbg = style.BIT;
712  break;
713  }
714  }
715 
716  myPad()->bkgdset( nbg );
717 }
718 
719 
720 void NCRichText::PadSetLevel()
721 {
722  cindent = listindent * liststack.size();
723 
724  if ( cindent > textwidth / 2 )
725  cindent = textwidth / 2;
726 
727  if ( atbol )
728  {
729  cc = cindent;
730  myPad()->move( cl, cc );
731  }
732 }
733 
734 
735 void NCRichText::PadChangeLevel( bool down, int tag )
736 {
737  if ( down )
738  {
739  if ( liststack.size() )
740  liststack.pop();
741  }
742  else
743  {
744  liststack.push( tag );
745  }
746 
747  PadSetLevel();
748 }
749 
750 
751 void NCRichText::openAnchor( std::wstring args )
752 {
753  canchor.open( cl, cc );
754 
755  const wchar_t * ch = ( wchar_t * )args.data();
756  const wchar_t * lookupstr = L"href = ";
757  const wchar_t * lookup = lookupstr;
758 
759  for ( ; *ch && *lookup; ++ch )
760  {
761  wchar_t c = towlower( *ch );
762 
763  switch ( c )
764  {
765  case L'\t':
766  case L' ':
767 
768  if ( *lookup != L' ' )
769  lookup = lookupstr;
770 
771  break;
772 
773  default:
774  if ( *lookup == L' ' )
775  {
776  ++lookup;
777 
778  if ( !*lookup )
779  {
780  // ch is the 1st char after lookupstr
781  --ch; // end of loop will ++ch
782  break;
783  }
784  }
785 
786  if ( c == *lookup )
787  ++lookup;
788  else
789  lookup = lookupstr;
790 
791  break;
792  }
793  }
794 
795  if ( !*lookup )
796  {
797  const wchar_t * delim = ( *ch == L'"' ) ? L"\"" : L" \t";
798  args = ( *ch == L'"' ) ? ++ch : ch;
799 
800  std::wstring::size_type end = args.find_first_of( delim );
801 
802  if ( end != std::wstring::npos )
803  args.erase( end );
804 
805  canchor.target = args;
806  }
807  else
808  {
809  yuiError() << "No value for 'HREF=' in anchor '" << args << "'" << std::endl;
810  }
811 }
812 
813 
814 void NCRichText::closeAnchor()
815 {
816  canchor.close( cl, cc );
817 
818  if ( canchor.valid() )
819  anchors.push_back( canchor );
820 
821  canchor = Anchor();
822 }
823 
824 
825 // expect "<[/]value>"
826 bool NCRichText::PadTOKEN( const wchar_t * sch, const wchar_t *& ech )
827 {
828  // "<[/]value>"
829  if ( *sch++ != L'<' || *( ech - 1 ) != L'>' )
830  return false;
831 
832  // "[/]value>"
833  bool endtag = ( *sch == L'/' );
834 
835  if ( endtag )
836  ++sch;
837 
838  // "value>"
839  if ( ech - sch <= 1 )
840  return false;
841 
842  std::wstring value( sch, ech - 1 - sch );
843 
844  std::wstring args;
845 
846  std::wstring::size_type argstart = value.find_first_of( L" \t\n" );
847 
848  if ( argstart != std::wstring::npos )
849  {
850  args = value.substr( argstart );
851  value.erase( argstart );
852  }
853 
854  for ( unsigned i = 0; i < value.length(); ++i )
855  {
856  if ( isupper( value[i] ) )
857  {
858  value[i] = static_cast<char>( tolower( value[i] ) );
859  }
860  }
861 
862  int leveltag = 0;
863 
864  int headinglevel = 0;
865 
866  TOKEN token = T_UNKNOWN;
867 
868  switch ( value.length() )
869  {
870  case 1:
871 
872  if ( value[0] == 'b' ) token = T_BOLD;
873  else if ( value[0] == 'i' ) token = T_IT;
874  else if ( value[0] == 'p' ) token = T_PAR;
875  else if ( value[0] == 'a' ) token = T_ANC;
876  else if ( value[0] == 'u' ) token = T_BOLD;
877 
878  break;
879 
880  case 2:
881  if ( value == L"br" ) token = T_BR;
882  else if ( value == L"em" ) token = T_IT;
883  else if ( value == L"h1" ) { token = T_HEAD; headinglevel = 1; }
884  else if ( value == L"h2" ) { token = T_HEAD; headinglevel = 2; }
885  else if ( value == L"h3" ) { token = T_HEAD; headinglevel = 3; }
886  else if ( value == L"hr" ) token = T_IGNORE;
887  else if ( value == L"li" ) token = T_LI;
888  else if ( value == L"ol" ) { token = T_LEVEL; leveltag = 1; }
889  else if ( value == L"qt" ) token = T_IGNORE;
890  else if ( value == L"tt" ) token = T_TT;
891  else if ( value == L"ul" ) { token = T_LEVEL; leveltag = 0; }
892 
893  break;
894 
895  case 3:
896 
897  if ( value == L"big" ) token = T_IGNORE;
898  else if ( value == L"pre" ) token = T_PLAIN;
899  // <br> and <hr> are the only non-pair tags currently supported.
900  // We treat bellow these two special cases in order to work as
901  // users expect. This issue was described at
902  // https://github.com/libyui/libyui-ncurses/issues/33
903  else if ( value == L"br/" ) token = T_BR;
904  else if ( value == L"hr/" ) token = T_IGNORE;
905 
906  break;
907 
908  case 4:
909  if ( value == L"bold" ) token = T_BOLD;
910  else if ( value == L"code" ) token = T_TT;
911  else if ( value == L"font" ) token = T_IGNORE;
912 
913  break;
914 
915  case 5:
916  if ( value == L"large" ) token = T_IGNORE;
917  else if ( value == L"small" ) token = T_IGNORE;
918 
919  break;
920 
921  case 6:
922  if ( value == L"center" ) token = T_PAR;
923  else if ( value == L"strong" ) token = T_BOLD;
924 
925  break;
926 
927  case 10:
928  if ( value == L"blockquote" ) token = T_PAR;
929 
930  break;
931 
932  default:
933  token = T_UNKNOWN;
934 
935  break;
936  }
937 
938  if ( token == T_UNKNOWN )
939  {
940  yuiDebug() << "T_UNKNOWN :" << value << ":" << args << ":" << std::endl;
941  // see bug #67319
942  // return false;
943  return true;
944  }
945 
946  if ( token == T_IGNORE )
947  return true;
948 
949  switch ( token )
950  {
951  case T_LEVEL:
952  PadChangeLevel( endtag, leveltag );
953  PadBOL();
954  // add new line after end of the list
955  // (only at the very end)
956  if ( endtag && !cindent )
957  PadNL();
958 
959  break;
960 
961  case T_BR:
962  PadNL();
963 
964  break;
965 
966  case T_HEAD:
967  if ( endtag )
968  Tattr &= ~token;
969  else
970  Tattr |= token;
971 
972  PadSetAttr();
973  PadBOL();
974 
975  if ( headinglevel && endtag )
976  PadNL();
977 
978  break;
979 
980  case T_PAR:
981  PadBOL();
982 
983  if ( !cindent )
984  {
985  if ( endtag )
986  // add new line after closing tag (FaTE 3124)
987  PadNL();
988  }
989 
990  break;
991 
992  case T_LI:
993  PadSetLevel();
994  PadBOL();
995 
996  if ( !endtag )
997  {
998  std::wstring tag;
999 
1000  if ( liststack.empty() )
1001  {
1002  tag = std::wstring( listindent, L' ' );
1003  }
1004  else
1005  {
1006  wchar_t buf[16];
1007 
1008  if ( liststack.top() )
1009  {
1010  swprintf( buf, 15, L"%2ld. ", liststack.top()++ );
1011  }
1012  else
1013  {
1014  swprintf( buf, 15, L" %lc ", listleveltags[liststack.size()%listleveltags.size()] );
1015  }
1016 
1017  tag = buf;
1018  }
1019 
1020  // outsent list tag:
1021  cc = ( tag.size() < cc ? cc - tag.size() : 0 );
1022 
1023  myPad()->move( cl, cc );
1024 
1025  PadTXT( tag.c_str(), tag.size() );
1026 
1027  atbol = true;
1028  }
1029 
1030  break;
1031 
1032  case T_PLAIN:
1033 
1034  if ( !endtag )
1035  {
1036  preTag = true; // display text preserving newlines and spaces
1037  AdjustPrePad( ech );
1038  }
1039  else
1040  {
1041  preTag = false;
1042  PadNL(); // add new line (text may continue after </pre>)
1043  }
1044 
1045  break;
1046 
1047  case T_ANC:
1048 
1049  if ( endtag )
1050  {
1051  closeAnchor();
1052  }
1053  else
1054  {
1055  openAnchor( args );
1056  }
1057 
1058  // fall through
1059 
1060  case T_BOLD:
1061  case T_IT:
1062  case T_TT:
1063  if ( endtag )
1064  Tattr &= ~token;
1065  else
1066  Tattr |= token;
1067 
1068  PadSetAttr();
1069 
1070  break;
1071 
1072  case T_IGNORE:
1073  case T_UNKNOWN:
1074  break;
1075  }
1076 
1077  return true;
1078 }
1079 
1080 
1081 void NCRichText::arm( unsigned i )
1082 {
1083  if ( !myPad() )
1084  {
1085  armed = i;
1086  return;
1087  }
1088 
1089  yuiDebug() << i << " (" << armed << ")" << std::endl;
1090 
1091  if ( i == armed )
1092  {
1093  if ( armed != Anchor::unset )
1094  {
1095  // just redraw
1096  anchors[armed].draw( *myPad(), wStyle().richtext.getArmed( GetState() ), 0 );
1097  myPad()->update();
1098  }
1099 
1100  return;
1101  }
1102 
1103  if ( armed != Anchor::unset )
1104  {
1105  anchors[armed].draw( *myPad(), wStyle().richtext.link, ( int ) wStyle().richtext.visitedlink );
1106  armed = Anchor::unset;
1107  }
1108 
1109  if ( i != Anchor::unset )
1110  {
1111  armed = i;
1112  anchors[armed].draw( *myPad(), wStyle().richtext.getArmed( GetState() ), 0 );
1113  }
1114 
1115  if ( showLinkTarget )
1116  {
1117  if ( armed != Anchor::unset )
1118  NCPadWidget::setLabel( NCstring( anchors[armed].target ) );
1119  else
1120  NCPadWidget::setLabel( NCstring() );
1121  }
1122  else
1123  {
1124  myPad()->update();
1125  }
1126 }
1127 
1128 
1129 void NCRichText::HScroll( unsigned total, unsigned visible, unsigned start )
1130 {
1131  NCPadWidget::HScroll( total, visible, start );
1132  // no hyperlink handling needed, because Richtext does not HScroll
1133 }
1134 
1135 
1136 void NCRichText::VScroll( unsigned total, unsigned visible, unsigned start )
1137 {
1138  NCPadWidget::VScroll( total, visible, start );
1139 
1140  if ( plainText || anchors.empty() )
1141  return; // <-- no links to check
1142 
1143  // Take care of hyperlinks: Check whether an armed link is visible.
1144  // If not arm the first visible link on page or none.
1145  vScrollFirstvisible = start;
1146 
1147  vScrollNextinvisible = start + visible;
1148 
1149  if ( armed != Anchor::unset )
1150  {
1151  if ( anchors[armed].within( vScrollFirstvisible, vScrollNextinvisible ) )
1152  return; // <-- armed link is vissble
1153  else
1154  disarm();
1155  }
1156 
1157  for ( unsigned i = 0; i < anchors.size(); ++i )
1158  {
1159  if ( anchors[i].within( vScrollFirstvisible, vScrollNextinvisible ) )
1160  {
1161  arm( i );
1162  break;
1163  }
1164  }
1165 }
1166 
1167 
1168 bool NCRichText::handleInput( wint_t key )
1169 {
1170  if ( plainText || anchors.empty() )
1171  {
1172  return NCPadWidget::handleInput( key );
1173  }
1174 
1175  // take care of hyperlinks
1176  bool handled = true;
1177 
1178  switch ( key )
1179  {
1180  case KEY_LEFT:
1181  // jump to previous link; scroll up if none
1182  {
1183  unsigned newarmed = Anchor::unset;
1184 
1185  if ( armed == Anchor::unset )
1186  {
1187  // look for an anchor above current page
1188  for ( unsigned i = anchors.size(); i; )
1189  {
1190  --i;
1191 
1192  if ( anchors[i].eline < vScrollFirstvisible )
1193  {
1194  newarmed = i;
1195  break;
1196  }
1197  }
1198  }
1199  else if ( armed > 0 )
1200  {
1201  newarmed = armed - 1;
1202  }
1203 
1204  if ( newarmed == Anchor::unset )
1205  {
1206  handled = NCPadWidget::handleInput( KEY_UP );
1207  }
1208  else
1209  {
1210  if ( !anchors[newarmed].within( vScrollFirstvisible, vScrollNextinvisible ) )
1211  myPad()->ScrlLine( anchors[newarmed].sline );
1212 
1213  arm( newarmed );
1214  }
1215  }
1216 
1217  break;
1218 
1219  case KEY_RIGHT:
1220  // jump to next link; scroll down if none
1221  {
1222  unsigned newarmed = Anchor::unset;
1223 
1224  if ( armed == Anchor::unset )
1225  {
1226  // look for an anchor below current page
1227  for ( unsigned i = 0; i < anchors.size(); ++i )
1228  {
1229  if ( anchors[i].sline >= vScrollNextinvisible )
1230  {
1231  newarmed = i;
1232  break;
1233  }
1234  }
1235  }
1236  else if ( armed + 1 < anchors.size() )
1237  {
1238  newarmed = armed + 1;
1239  }
1240 
1241  if ( newarmed == Anchor::unset )
1242  {
1243  handled = NCPadWidget::handleInput( KEY_DOWN );
1244  }
1245  else
1246  {
1247  if ( !anchors[newarmed].within( vScrollFirstvisible, vScrollNextinvisible ) )
1248  myPad()->ScrlLine( anchors[newarmed].sline );
1249 
1250  arm( newarmed );
1251  }
1252  }
1253 
1254  break;
1255 
1256  case KEY_UP:
1257  // arm previous visible link; scroll up if none
1258 
1259  if ( armed != Anchor::unset
1260  && armed > 0
1261  && anchors[armed-1].within( vScrollFirstvisible, vScrollNextinvisible ) )
1262  {
1263  arm( armed - 1 );
1264  }
1265  else
1266  {
1267  handled = NCPadWidget::handleInput( key );
1268  }
1269 
1270  break;
1271 
1272  case KEY_DOWN:
1273  // arm next visible link; scroll down if none
1274 
1275  if ( armed != Anchor::unset
1276  && armed + 1 < anchors.size()
1277  && anchors[armed+1].within( vScrollFirstvisible, vScrollNextinvisible ) )
1278  {
1279  arm( armed + 1 );
1280  }
1281  else
1282  {
1283  handled = NCPadWidget::handleInput( key );
1284  }
1285 
1286  break;
1287 
1288  default:
1289  handled = NCPadWidget::handleInput( key );
1290  };
1291 
1292  return handled;
1293 }
1294 
1295 
1296 std::string NCRichText::vScrollValue() const
1297 {
1298  const NCPad* mypad = myPad();
1299 
1300  if ( !mypad )
1301  return "";
1302 
1303  return std::to_string( mypad->CurPos().L );
1304 }
1305 
1306 
1307 void NCRichText::setVScrollValue( const std::string & newValue )
1308 {
1309  NCPad* mypad = myPad();
1310 
1311  if ( !mypad || newValue.empty() )
1312  return;
1313 
1314  if ( newValue == "minimum" )
1315  mypad->ScrlLine( 0 );
1316  else if ( newValue == "maximum" )
1317  mypad->ScrlLine( mypad->maxy() );
1318  else
1319  {
1320  try
1321  {
1322  mypad->ScrlLine( std::stoi( newValue ) );
1323  }
1324  catch (...)
1325  {
1326  yuiError() << "failed to set vertical scroll value '" << newValue << "'" << endl;
1327  }
1328  }
1329 }
1330 
1331 
1332 std::string NCRichText::hScrollValue() const
1333 {
1334  const NCPad* mypad = myPad();
1335 
1336  if ( !mypad )
1337  return "";
1338 
1339  return std::to_string( mypad->CurPos().C );
1340 }
1341 
1342 
1343 void NCRichText::setHScrollValue( const std::string & newValue )
1344 {
1345  NCPad* mypad = myPad();
1346 
1347  if ( !mypad || newValue.empty() )
1348  return;
1349 
1350  if ( newValue == "minimum" )
1351  mypad->ScrlCol( 0 );
1352  else if ( newValue == "maximum" )
1353  mypad->ScrlCol( mypad->maxx() );
1354  else
1355  {
1356  try
1357  {
1358  mypad->ScrlCol( std::stoi( newValue ) );
1359  }
1360  catch (...)
1361  {
1362  yuiError() << "failed to set horizontal scroll value '" << newValue << "'" << endl;
1363  }
1364  }
1365 }
int clear()
Clear the window.
Definition: ncursesw.h:1521
Definition: NCtext.h:37
virtual void setEnabled(bool do_bv)
Pure virtual to make sure every widget implements it.
Definition: NCRichText.cc:204
static int tabsize()
Size of a tab on terminal, not window.
Definition: ncursesw.h:1051
void bkgdset(chtype ch)
Set the background property.
Definition: ncursesw.h:1447
Definition: NCPad.h:93
static YNCursesUI * ui()
Access the global Y2NCursesUI.
Definition: YNCursesUI.h:93
void sendEvent(NCursesEvent event)
Send an event to the UI.
Definition: YNCursesUI.cc:455
Definition: position.h:109
int addwstr(const wchar_t *str, int n=-1)
Write the wchar_t str to the window, stop writing if the terminating NUL or the limit n is reached...
Definition: ncursesw.cc:123
virtual void activateLink(const std::string &url)
Derived classes should implement this, method is used to trigger event like user has pressed the link...
Definition: NCRichText.cc:261
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:1416
int maxx() const
Largest x coord in window.
Definition: ncursesw.h:1089
int move(int y, int x)
Move cursor the this position.
Definition: ncursesw.h:1154
virtual void setEnabled(bool do_bv)=0
Pure virtual to make sure every widget implements it.
Definition: NCWidget.cc:391
Definition: position.h:154
int maxy() const
Largest y coord in window.
Definition: ncursesw.h:1094
virtual NCPad * myPad() const
Return the current pad.
Definition: NCPadWidget.h:62