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