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