libyui-ncurses  2.47.4
 All Classes Functions Variables
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 
435  yuiDebug() << "Text: " << wtxt << " initial length: " << wch - osch << std::endl;
436 
437  NCstring nctxt( wtxt );
438  NCtext ftext( nctxt );
439 
440  std::list<NCstring>::const_iterator line;
441  size_t llen = 0; // longest line
442 
443  // iterate through NCtext
444  for ( line = ftext.Text().begin(); line != ftext.Text().end(); ++line )
445  {
446  size_t tmp_len = 0;
447 
448  tmp_len = textWidth( (*line).str() );
449 
450  if ( tmp_len > llen )
451  llen = tmp_len;
452  }
453  yuiDebug() << "Longest line: " << llen << std::endl;
454 
455  if ( llen > textwidth )
456  {
457  textwidth = llen;
458  AdjustPad( wsze( cl + ftext.Lines(), llen ) ); // adjust pad to longest line
459  }
460 
461 }
462 
463 void NCRichText::DrawHTMLPad()
464 {
465  yuiDebug() << "Start:" << std::endl;
466 
467  liststack = std::stack<int>();
468  canchor = Anchor();
469  anchors.clear();
470  armed = Anchor::unset;
471 
472  cl = 0;
473  cc = 0;
474  cindent = 0;
475  myPad()->move( cl, cc );
476  atbol = true;
477 
478  const wchar_t * wch = ( wchar_t * )text.str().data();
479  const wchar_t * swch = 0;
480 
481  while ( *wch )
482  {
483  switch ( *wch )
484  {
485  case L' ':
486  case L'\t':
487  case L'\n':
488  case L'\v':
489  case L'\r':
490  case L'\f':
491  if ( ! preTag )
492  {
493  SkipWS( wch );
494  PadWS();
495  }
496  else
497  {
498  switch ( *wch )
499  {
500  case L' ': // add white space
501  case L'\t':
502  myPad()->addwstr( wch, 1 );
503  break;
504 
505  case L'\n':
506  case L'\f':
507  PadNL(); // add new line
508  break;
509 
510  default:
511  yuiDebug() << "Ignoring " << *wch << std::endl;
512  }
513  ++wch;
514  }
515 
516  break;
517 
518  case L'<':
519  swch = wch;
520  SkipToken( wch );
521 
522  if ( PadTOKEN( swch, wch ) )
523  break; // strip token
524  else
525  wch = swch; // reset and fall through
526 
527  default:
528  swch = wch;
529 
530  if ( !preTag )
531  {
532  SkipWord( wch );
533  PadTXT( swch, wch - swch );
534  }
535  else
536  {
537  SkipPreTXT( wch );
538  PadPreTXT( swch, wch - swch );
539  }
540 
541  break;
542  }
543  }
544 
545  PadBOL();
546  AdjustPad( wsze( cl, textwidth ) );
547 
548  yuiDebug() << "Anchors: " << anchors.size() << std::endl;
549 
550  for ( unsigned i = 0; i < anchors.size(); ++i )
551  {
552  yuiDebug() << form( " %2d: [%2d,%2d] -> [%2d,%2d]",
553  i,
554  anchors[i].sline, anchors[i].scol,
555  anchors[i].eline, anchors[i].ecol ) << std::endl;
556  }
557 }
558 
559 
560 inline void NCRichText::PadNL()
561 {
562  cc = cindent;
563 
564  if ( ++cl == ( unsigned )myPad()->height() )
565  {
566  AdjustPad( wsze( myPad()->height() + defPadSze().H, textwidth ) );
567  }
568 
569  myPad()->move( cl, cc );
570 
571  atbol = true;
572 }
573 
574 
575 inline void NCRichText::PadBOL()
576 {
577  if ( !atbol )
578  PadNL();
579 }
580 
581 
582 inline void NCRichText::PadWS( const bool tab )
583 {
584  if ( atbol )
585  return; // no WS at beginning of line
586 
587  if ( cc == textwidth )
588  {
589  PadNL();
590  }
591  else
592  {
593  myPad()->addwstr( L" " );
594  ++cc;
595  }
596 }
597 
598 
599 inline void NCRichText::PadTXT( const wchar_t * osch, const unsigned olen )
600 {
601  std::wstring txt( osch, olen );
602 
603  txt = filterEntities( txt );
604 
605  size_t len = textWidth( txt );
606 
607  if ( !atbol && cc + len > textwidth )
608  PadNL();
609 
610  // insert the text
611  const wchar_t * sch = txt.data();
612 
613  while ( *sch )
614  {
615  myPad()->addwstr( sch, 1 ); // add one wide chararacter
616  cc += wcwidth( *sch );
617  atbol = false; // at begin of line = false
618 
619  if ( cc >= textwidth )
620  {
621  PadNL(); // add a new line
622  }
623 
624  sch++;
625  }
626 }
627 
628 /**
629  * Get the number of columns needed to print a 'std::wstring'. Only printable characters
630  * are taken into account because otherwise 'wcwidth' would return -1 (e.g. for '\n').
631  * Tabs are calculated with tabsize().
632  * Attention: only use textWidth() to calculate space, not for iterating through a text
633  * or to get the length of a text (real text length includes new lines).
634  */
635 size_t NCRichText::textWidth( std::wstring wstr )
636 {
637  size_t len = 0;
638  std::wstring::const_iterator wstr_it; // iterator for std::wstring
639 
640  for ( wstr_it = wstr.begin(); wstr_it != wstr.end() ; ++wstr_it )
641  {
642  // check whether char is printable
643  if ( iswprint( *wstr_it ) )
644  {
645  len += wcwidth( *wstr_it );
646  }
647  else if ( *wstr_it == '\t' )
648  {
649  len += myPad()->tabsize();
650  }
651  }
652 
653  return len;
654 }
655 
656 
657 /**
658  * Set character attributes (e.g. color, font face...)
659  **/
660 inline void NCRichText::PadSetAttr()
661 {
662  const NCstyle::StRichtext & style( wStyle().richtext );
663  chtype nbg = style.plain;
664 
665  if ( Tattr & T_ANC )
666  {
667  nbg = style.link;
668  }
669  else if ( Tattr & T_HEAD )
670  {
671  nbg = style.title;
672  }
673  else
674  {
675  switch ( Tattr & Tfontmask )
676  {
677  case T_BOLD:
678  nbg = style.B;
679  break;
680 
681  case T_IT:
682  nbg = style.I;
683  break;
684 
685  case T_TT:
686  nbg = style.T;
687  break;
688 
689  case T_BOLD|T_IT:
690  nbg = style.BI;
691  break;
692 
693  case T_BOLD|T_TT:
694  nbg = style.BT;
695  break;
696 
697  case T_IT|T_TT:
698  nbg = style.IT;
699  break;
700 
701  case T_BOLD|T_IT|T_TT:
702  nbg = style.BIT;
703  break;
704  }
705  }
706 
707  myPad()->bkgdset( nbg );
708 }
709 
710 
711 void NCRichText::PadSetLevel()
712 {
713  cindent = listindent * liststack.size();
714 
715  if ( cindent > textwidth / 2 )
716  cindent = textwidth / 2;
717 
718  if ( atbol )
719  {
720  cc = cindent;
721  myPad()->move( cl, cc );
722  }
723 }
724 
725 
726 void NCRichText::PadChangeLevel( bool down, int tag )
727 {
728  if ( down )
729  {
730  if ( liststack.size() )
731  liststack.pop();
732  }
733  else
734  {
735  liststack.push( tag );
736  }
737 
738  PadSetLevel();
739 }
740 
741 
742 void NCRichText::openAnchor( std::wstring args )
743 {
744  canchor.open( cl, cc );
745 
746  const wchar_t * ch = ( wchar_t * )args.data();
747  const wchar_t * lookupstr = L"href = ";
748  const wchar_t * lookup = lookupstr;
749 
750  for ( ; *ch && *lookup; ++ch )
751  {
752  wchar_t c = towlower( *ch );
753 
754  switch ( c )
755  {
756  case L'\t':
757  case L' ':
758 
759  if ( *lookup != L' ' )
760  lookup = lookupstr;
761 
762  break;
763 
764  default:
765  if ( *lookup == L' ' )
766  {
767  ++lookup;
768 
769  if ( !*lookup )
770  {
771  // ch is the 1st char after lookupstr
772  --ch; // end of loop will ++ch
773  break;
774  }
775  }
776 
777  if ( c == *lookup )
778  ++lookup;
779  else
780  lookup = lookupstr;
781 
782  break;
783  }
784  }
785 
786  if ( !*lookup )
787  {
788  const wchar_t * delim = ( *ch == L'"' ) ? L"\"" : L" \t";
789  args = ( *ch == L'"' ) ? ++ch : ch;
790 
791  std::wstring::size_type end = args.find_first_of( delim );
792 
793  if ( end != std::wstring::npos )
794  args.erase( end );
795 
796  canchor.target = args;
797  }
798  else
799  {
800  yuiError() << "No value for 'HREF=' in anchor '" << args << "'" << std::endl;
801  }
802 }
803 
804 
805 void NCRichText::closeAnchor()
806 {
807  canchor.close( cl, cc );
808 
809  if ( canchor.valid() )
810  anchors.push_back( canchor );
811 
812  canchor = Anchor();
813 }
814 
815 
816 // expect "<[/]value>"
817 bool NCRichText::PadTOKEN( const wchar_t * sch, const wchar_t *& ech )
818 {
819  // "<[/]value>"
820  if ( *sch++ != L'<' || *( ech - 1 ) != L'>' )
821  return false;
822 
823  // "[/]value>"
824  bool endtag = ( *sch == L'/' );
825 
826  if ( endtag )
827  ++sch;
828 
829  // "value>"
830  if ( ech - sch <= 1 )
831  return false;
832 
833  std::wstring value( sch, ech - 1 - sch );
834 
835  std::wstring args;
836 
837  std::wstring::size_type argstart = value.find_first_of( L" \t\n" );
838 
839  if ( argstart != std::wstring::npos )
840  {
841  args = value.substr( argstart );
842  value.erase( argstart );
843  }
844 
845  for ( unsigned i = 0; i < value.length(); ++i )
846  {
847  if ( isupper( value[i] ) )
848  {
849  value[i] = static_cast<char>( tolower( value[i] ) );
850  }
851  }
852 
853  int leveltag = 0;
854 
855  int headinglevel = 0;
856 
857  TOKEN token = T_UNKNOWN;
858 
859  switch ( value.length() )
860  {
861  case 1:
862 
863  if ( value[0] == 'b' ) token = T_BOLD;
864  else if ( value[0] == 'i' ) token = T_IT;
865  else if ( value[0] == 'p' ) token = T_PAR;
866  else if ( value[0] == 'a' ) token = T_ANC;
867  else if ( value[0] == 'u' ) token = T_BOLD;
868 
869  break;
870 
871  case 2:
872  if ( value == L"br" ) token = T_BR;
873  else if ( value == L"em" ) token = T_IT;
874  else if ( value == L"h1" ) { token = T_HEAD; headinglevel = 1; }
875  else if ( value == L"h2" ) { token = T_HEAD; headinglevel = 2; }
876  else if ( value == L"h3" ) { token = T_HEAD; headinglevel = 3; }
877  else if ( value == L"hr" ) token = T_IGNORE;
878  else if ( value == L"li" ) token = T_LI;
879  else if ( value == L"ol" ) { token = T_LEVEL; leveltag = 1; }
880  else if ( value == L"qt" ) token = T_IGNORE;
881  else if ( value == L"tt" ) token = T_TT;
882  else if ( value == L"ul" ) { token = T_LEVEL; leveltag = 0; }
883 
884  break;
885 
886  case 3:
887 
888  if ( value == L"big" ) token = T_IGNORE;
889  else if ( value == L"pre" ) token = T_PLAIN;
890 
891  break;
892 
893  case 4:
894  if ( value == L"bold" ) token = T_BOLD;
895  else if ( value == L"code" ) token = T_TT;
896  else if ( value == L"font" ) token = T_IGNORE;
897 
898  break;
899 
900  case 5:
901  if ( value == L"large" ) token = T_IGNORE;
902  else if ( value == L"small" ) token = T_IGNORE;
903 
904  break;
905 
906  case 6:
907  if ( value == L"center" ) token = T_PAR;
908  else if ( value == L"strong" ) token = T_BOLD;
909 
910  break;
911 
912  case 10:
913  if ( value == L"blockquote" ) token = T_PAR;
914 
915  break;
916 
917  default:
918  token = T_UNKNOWN;
919 
920  break;
921  }
922 
923  if ( token == T_UNKNOWN )
924  {
925  yuiDebug() << "T_UNKNOWN :" << value << ":" << args << ":" << std::endl;
926  // see bug #67319
927  // return false;
928  return true;
929  }
930 
931  if ( token == T_IGNORE )
932  return true;
933 
934  switch ( token )
935  {
936  case T_LEVEL:
937  PadChangeLevel( endtag, leveltag );
938  PadBOL();
939  // add new line after end of the list
940  // (only at the very end)
941  if ( endtag && !cindent )
942  PadNL();
943 
944  break;
945 
946  case T_BR:
947  PadNL();
948 
949  break;
950 
951  case T_HEAD:
952  if ( endtag )
953  Tattr &= ~token;
954  else
955  Tattr |= token;
956 
957  PadSetAttr();
958  PadBOL();
959 
960  if ( headinglevel && endtag )
961  PadNL();
962 
963  break;
964 
965  case T_PAR:
966  PadBOL();
967 
968  if ( !cindent )
969  {
970  if ( endtag )
971  // add new line after closing tag (FaTE 3124)
972  PadNL();
973  }
974 
975  break;
976 
977  case T_LI:
978  PadSetLevel();
979  PadBOL();
980 
981  if ( !endtag )
982  {
983  std::wstring tag;
984 
985  if ( liststack.empty() )
986  {
987  tag = std::wstring( listindent, L' ' );
988  }
989  else
990  {
991  wchar_t buf[16];
992 
993  if ( liststack.top() )
994  {
995  swprintf( buf, 15, L"%2ld. ", liststack.top()++ );
996  }
997  else
998  {
999  swprintf( buf, 15, L" %lc ", listleveltags[liststack.size()%listleveltags.size()] );
1000  }
1001 
1002  tag = buf;
1003  }
1004 
1005  // outsent list tag:
1006  cc = ( tag.size() < cc ? cc - tag.size() : 0 );
1007 
1008  myPad()->move( cl, cc );
1009 
1010  PadTXT( tag.c_str(), tag.size() );
1011 
1012  atbol = true;
1013  }
1014 
1015  break;
1016 
1017  case T_PLAIN:
1018 
1019  if ( !endtag )
1020  {
1021  preTag = true; // display text preserving newlines and spaces
1022  AdjustPrePad( ech );
1023  }
1024  else
1025  {
1026  preTag = false;
1027  PadNL(); // add new line (text may continue after </pre>)
1028  }
1029 
1030  break;
1031 
1032  case T_ANC:
1033 
1034  if ( endtag )
1035  {
1036  closeAnchor();
1037  }
1038  else
1039  {
1040  openAnchor( args );
1041  }
1042 
1043  // fall through
1044 
1045  case T_BOLD:
1046  case T_IT:
1047  case T_TT:
1048  if ( endtag )
1049  Tattr &= ~token;
1050  else
1051  Tattr |= token;
1052 
1053  PadSetAttr();
1054 
1055  break;
1056 
1057  case T_IGNORE:
1058  case T_UNKNOWN:
1059  break;
1060  }
1061 
1062  return true;
1063 }
1064 
1065 
1066 void NCRichText::arm( unsigned i )
1067 {
1068  if ( !myPad() )
1069  {
1070  armed = i;
1071  return;
1072  }
1073 
1074  yuiDebug() << i << " (" << armed << ")" << std::endl;
1075 
1076  if ( i == armed )
1077  {
1078  if ( armed != Anchor::unset )
1079  {
1080  // just redraw
1081  anchors[armed].draw( *myPad(), wStyle().richtext.getArmed( GetState() ), 0 );
1082  myPad()->update();
1083  }
1084 
1085  return;
1086  }
1087 
1088  if ( armed != Anchor::unset )
1089  {
1090  anchors[armed].draw( *myPad(), wStyle().richtext.link, ( int ) wStyle().richtext.visitedlink );
1091  armed = Anchor::unset;
1092  }
1093 
1094  if ( i != Anchor::unset )
1095  {
1096  armed = i;
1097  anchors[armed].draw( *myPad(), wStyle().richtext.getArmed( GetState() ), 0 );
1098  }
1099 
1100  if ( showLinkTarget )
1101  {
1102  if ( armed != Anchor::unset )
1103  NCPadWidget::setLabel( NCstring( anchors[armed].target ) );
1104  else
1105  NCPadWidget::setLabel( NCstring() );
1106  }
1107  else
1108  {
1109  myPad()->update();
1110  }
1111 }
1112 
1113 
1114 void NCRichText::HScroll( unsigned total, unsigned visible, unsigned start )
1115 {
1116  NCPadWidget::HScroll( total, visible, start );
1117  // no hyperlink handling needed, because Ritchtext does not HScroll
1118 }
1119 
1120 
1121 void NCRichText::VScroll( unsigned total, unsigned visible, unsigned start )
1122 {
1123  NCPadWidget::VScroll( total, visible, start );
1124 
1125  if ( plainText || anchors.empty() )
1126  return; // <-- no links to check
1127 
1128  // Take care of hyperlinks: Check whether an armed link is visible.
1129  // If not arm the first visible link on page or none.
1130  vScrollFirstvisible = start;
1131 
1132  vScrollNextinvisible = start + visible;
1133 
1134  if ( armed != Anchor::unset )
1135  {
1136  if ( anchors[armed].within( vScrollFirstvisible, vScrollNextinvisible ) )
1137  return; // <-- armed link is vissble
1138  else
1139  disarm();
1140  }
1141 
1142  for ( unsigned i = 0; i < anchors.size(); ++i )
1143  {
1144  if ( anchors[i].within( vScrollFirstvisible, vScrollNextinvisible ) )
1145  {
1146  arm( i );
1147  break;
1148  }
1149  }
1150 }
1151 
1152 
1153 bool NCRichText::handleInput( wint_t key )
1154 {
1155  if ( plainText || anchors.empty() )
1156  {
1157  return NCPadWidget::handleInput( key );
1158  }
1159 
1160  // take care of hyperlinks
1161  bool handled = true;
1162 
1163  switch ( key )
1164  {
1165  case KEY_LEFT:
1166  // jump to previous link; scroll up if none
1167  {
1168  unsigned newarmed = Anchor::unset;
1169 
1170  if ( armed == Anchor::unset )
1171  {
1172  // look for an anchor above current page
1173  for ( unsigned i = anchors.size(); i; )
1174  {
1175  --i;
1176 
1177  if ( anchors[i].eline < vScrollFirstvisible )
1178  {
1179  newarmed = i;
1180  break;
1181  }
1182  }
1183  }
1184  else if ( armed > 0 )
1185  {
1186  newarmed = armed - 1;
1187  }
1188 
1189  if ( newarmed == Anchor::unset )
1190  {
1191  handled = NCPadWidget::handleInput( KEY_UP );
1192  }
1193  else
1194  {
1195  if ( !anchors[newarmed].within( vScrollFirstvisible, vScrollNextinvisible ) )
1196  myPad()->ScrlLine( anchors[newarmed].sline );
1197 
1198  arm( newarmed );
1199  }
1200  }
1201 
1202  break;
1203 
1204  case KEY_RIGHT:
1205  // jump to next link; scroll down if none
1206  {
1207  unsigned newarmed = Anchor::unset;
1208 
1209  if ( armed == Anchor::unset )
1210  {
1211  // look for an anchor below current page
1212  for ( unsigned i = 0; i < anchors.size(); ++i )
1213  {
1214  if ( anchors[i].sline >= vScrollNextinvisible )
1215  {
1216  newarmed = i;
1217  break;
1218  }
1219  }
1220  }
1221  else if ( armed + 1 < anchors.size() )
1222  {
1223  newarmed = armed + 1;
1224  }
1225 
1226  if ( newarmed == Anchor::unset )
1227  {
1228  handled = NCPadWidget::handleInput( KEY_DOWN );
1229  }
1230  else
1231  {
1232  if ( !anchors[newarmed].within( vScrollFirstvisible, vScrollNextinvisible ) )
1233  myPad()->ScrlLine( anchors[newarmed].sline );
1234 
1235  arm( newarmed );
1236  }
1237  }
1238 
1239  break;
1240 
1241  case KEY_UP:
1242  // arm previous visible link; scroll up if none
1243 
1244  if ( armed != Anchor::unset
1245  && armed > 0
1246  && anchors[armed-1].within( vScrollFirstvisible, vScrollNextinvisible ) )
1247  {
1248  arm( armed - 1 );
1249  }
1250  else
1251  {
1252  handled = NCPadWidget::handleInput( key );
1253  }
1254 
1255  break;
1256 
1257  case KEY_DOWN:
1258  // arm next visible link; scroll down if none
1259 
1260  if ( armed != Anchor::unset
1261  && armed + 1 < anchors.size()
1262  && anchors[armed+1].within( vScrollFirstvisible, vScrollNextinvisible ) )
1263  {
1264  arm( armed + 1 );
1265  }
1266  else
1267  {
1268  handled = NCPadWidget::handleInput( key );
1269  }
1270 
1271  break;
1272 
1273  default:
1274  handled = NCPadWidget::handleInput( key );
1275  };
1276 
1277  return handled;
1278 }
1279 
1280 
int clear()
Clear the window.
Definition: ncursesw.h:1522
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:1052
void bkgdset(chtype ch)
Set the background property.
Definition: ncursesw.h:1448
Definition: NCPad.h:93
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 NCPad * myPad() const
Return the current pad.
Definition: NCPadWidget.h:62
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