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