25 #define YUILogComponent "ncurses"
26 #include <yui/YUILog.h>
27 #include "NCRichText.h"
28 #include "YNCursesUI.h"
29 #include "stringutil.h"
32 #include <boost/algorithm/string.hpp>
34 #include <yui/YMenuItem.h>
35 #include <yui/YApplication.h>
40 const unsigned NCRichText::listindent = 4;
41 const std::wstring NCRichText::listleveltags( L
"@*+o#-%$&" );
43 const bool NCRichText::showLinkTarget =
false;
45 std::map<std::wstring, std::wstring> NCRichText::_charentity;
49 const std::wstring NCRichText::entityLookup(
const std::wstring & val_r )
52 std::wstring::size_type hash = val_r.find( L
"#", 0 );
53 std::wstring ascii = L
"";
55 if ( hash != std::wstring::npos )
57 std::wstring s = val_r.substr( hash + 1 );
60 boost::replace_all( s,
"x",
"0x" );
62 long int c = std::wcstol( s.c_str(), &endptr, 0 );
66 if ( s.c_str() != endptr )
68 std::wostringstream ws;
74 #define REP(l,r) _charentity[l] = r
75 if ( _charentity.empty() )
80 NCstring::RecodeToWchar( YUI::app()->productName(),
"UTF-8", &product );
86 REP( L
"quot", L
"\"" );
87 REP( L
"product", product );
90 std::map<std::wstring, std::wstring>::const_iterator it = _charentity.find( val_r );
92 if ( it != _charentity.end() )
118 const std::wstring NCRichText::filterEntities(
const std::wstring & text )
120 std::wstring txt = text;
123 for ( std::wstring::size_type special = txt.find( L
"&" );
124 special != std::wstring::npos;
125 special = txt.find( L
"&", special + 1 ) )
127 std::wstring::size_type colon = txt.find( L
";", special + 1 );
129 if ( colon == std::wstring::npos )
132 const std::wstring repl = entityLookup( txt.substr( special + 1, colon - special - 1 ) );
135 || txt.substr( special + 1, colon - special - 1 ) == L
"product" )
137 txt.replace( special, colon - special + 1, repl );
140 yuiMilestone() <<
"porn.bat" << std::endl;
147 void NCRichText::Anchor::draw(
NCPad & pad,
const chtype attr,
int color )
155 pad.
chgat( -1, attr, color );
162 pad.
chgat( ecol - c, attr, color );
166 NCRichText::NCRichText( YWidget * parent,
const std::string & ntext,
168 : YRichText( parent, ntext, plainTextMode )
171 , plainText( plainTextMode )
180 yuiDebug() << std::endl;
181 activeLabelOnly =
true;
186 NCRichText::~NCRichText()
188 yuiDebug() << std::endl;
192 int NCRichText::preferredWidth()
194 return wGetDefsze().W;
198 int NCRichText::preferredHeight()
200 return wGetDefsze().H;
207 YRichText::setEnabled( do_bv );
211 void NCRichText::setSize(
int newwidth,
int newheight )
213 wRelocate(
wpos( 0 ),
wsze( newheight, newwidth ) );
217 void NCRichText::setLabel(
const std::string & nlabel )
220 NCPadWidget::setLabel(
NCstring( nlabel ) );
224 void NCRichText::setValue(
const std::string & ntext )
228 YRichText::setValue( ntext );
233 void NCRichText::wRedraw()
238 bool initial = ( !
myPad() || !
myPad()->Destwin() );
240 if ( !( plainText || anchors.empty() ) )
243 NCPadWidget::wRedraw();
245 if ( initial && autoScrollDown() )
254 void NCRichText::wRecoded()
275 if ( !( plainText || anchors.empty() ) )
282 if ( armed != Anchor::unset )
284 ret = NCursesEvent::menu;
286 NCstring::RecodeFromWchar( anchors[armed].target,
"UTF-8", &str );
287 yuiMilestone() <<
"LINK: " << str << std::endl;
289 ret.selection = NULL;
299 NCPad * NCRichText::CreatePad()
301 wsze psze( defPadSze() );
303 NCPad * npad =
new NCPad( psze.H, textwidth, *
this );
308 void NCRichText::DrawPad()
311 <<
"Start: plain mode " << plainText << std::endl
312 <<
" padsize " <<
myPad()->size() << std::endl
313 <<
" text length " << text.str().size() << std::endl;
323 yuiDebug() <<
"Done" << std::endl;
327 void NCRichText::DrawPlainPad()
330 yuiDebug() <<
"ftext is " <<
wsze( ftext.Lines(), ftext.Columns() ) << std::endl;
332 AdjustPad(
wsze( ftext.Lines(), ftext.Columns() ) );
336 for ( NCtext::const_iterator line = ftext.begin();
337 line != ftext.end(); ++line, ++cl )
343 void NCRichText::PadPreTXT(
const wchar_t * osch,
const unsigned olen )
345 std::wstring wtxt( osch, olen );
348 wtxt = filterEntities( wtxt );
354 const wchar_t * sch = wtxt.data();
368 inline void SkipToken(
const wchar_t *& wch )
374 while ( *wch && *wch != L
'>' );
381 static std::wstring WStoken( L
" \n\t\v\r\f" );
384 inline void SkipWS(
const wchar_t *& wch )
390 while ( *wch && WStoken.find( *wch ) != std::wstring::npos );
394 static std::wstring WDtoken( L
" <\n\t\v\r\f" );
397 inline void SkipWord(
const wchar_t *& wch )
403 while ( *wch && WDtoken.find( *wch ) == std::wstring::npos );
406 static std::wstring PREtoken( L
"<\n\v\r\f" );
409 inline void SkipPreTXT(
const wchar_t *& wch )
415 while ( *wch && PREtoken.find( *wch ) == std::wstring::npos );
423 void NCRichText::AdjustPrePad(
const wchar_t *osch )
425 const wchar_t * wch = osch;
426 std::wstring wstr( wch, 6 );
431 wstr.assign( wch, 6 );
433 while ( *wch && wstr != L
"</pre>" );
435 std::wstring wtxt( osch, wch - osch );
438 wtxt = filterEntities( wtxt );
441 boost::replace_all( wtxt, L
"<br>", L
"\n" );
442 boost::replace_all( wtxt, L
"<br/>", L
"\n" );
444 yuiDebug() <<
"Text: " << wtxt <<
" initial length: " << wch - osch << std::endl;
449 std::list<NCstring>::const_iterator line;
453 for ( line = ftext.Text().begin(); line != ftext.Text().end(); ++line )
457 tmp_len = textWidth( (*line).str() );
459 if ( tmp_len > llen )
462 yuiDebug() <<
"Longest line: " << llen << std::endl;
464 if ( llen > textwidth )
467 AdjustPad(
wsze( cl + ftext.Lines(), llen ) );
472 void NCRichText::DrawHTMLPad()
474 yuiDebug() <<
"Start:" << std::endl;
476 liststack = std::stack<int>();
479 armed = Anchor::unset;
487 const wchar_t * wch = (
wchar_t * )text.str().data();
488 const wchar_t * swch = 0;
520 yuiDebug() <<
"Ignoring " << *wch << std::endl;
531 if ( PadTOKEN( swch, wch ) )
542 PadTXT( swch, wch - swch );
547 PadPreTXT( swch, wch - swch );
555 AdjustPad(
wsze( cl, textwidth ) );
557 yuiDebug() <<
"Anchors: " << anchors.size() << std::endl;
559 for (
unsigned i = 0; i < anchors.size(); ++i )
561 yuiDebug() << form(
" %2d: [%2d,%2d] -> [%2d,%2d]",
563 anchors[i].sline, anchors[i].scol,
564 anchors[i].eline, anchors[i].ecol ) << std::endl;
569 inline void NCRichText::PadNL()
573 if ( ++cl == (
unsigned )
myPad()->height() )
575 AdjustPad(
wsze(
myPad()->height() + defPadSze().H, textwidth ) );
584 inline void NCRichText::PadBOL()
591 inline void NCRichText::PadWS(
bool tab )
596 if ( cc == textwidth )
608 inline void NCRichText::PadTXT(
const wchar_t * osch,
const unsigned olen )
610 std::wstring txt( osch, olen );
612 txt = filterEntities( txt );
614 size_t len = textWidth( txt );
616 if ( !atbol && cc + len > textwidth )
620 const wchar_t * sch = txt.data();
625 cc += wcwidth( *sch );
628 if ( cc >= textwidth )
644 size_t NCRichText::textWidth( std::wstring wstr )
647 std::wstring::const_iterator wstr_it;
649 for ( wstr_it = wstr.begin(); wstr_it != wstr.end() ; ++wstr_it )
652 if ( iswprint( *wstr_it ) )
654 len += wcwidth( *wstr_it );
656 else if ( *wstr_it ==
'\t' )
669 inline void NCRichText::PadSetAttr()
672 chtype nbg = style.plain;
678 else if ( Tattr & T_HEAD )
684 switch ( Tattr & Tfontmask )
710 case T_BOLD|T_IT|T_TT:
720 void NCRichText::PadSetLevel()
722 cindent = listindent * liststack.size();
724 if ( cindent > textwidth / 2 )
725 cindent = textwidth / 2;
735 void NCRichText::PadChangeLevel(
bool down,
int tag )
739 if ( liststack.size() )
744 liststack.push( tag );
751 void NCRichText::openAnchor( std::wstring args )
753 canchor.open( cl, cc );
755 const wchar_t * ch = (
wchar_t * )args.data();
756 const wchar_t * lookupstr = L
"href = ";
757 const wchar_t * lookup = lookupstr;
759 for ( ; *ch && *lookup; ++ch )
761 wchar_t c = towlower( *ch );
768 if ( *lookup != L
' ' )
774 if ( *lookup == L
' ' )
797 const wchar_t * delim = ( *ch == L
'"' ) ? L
"\"" : L
" \t";
798 args = ( *ch == L
'"' ) ? ++ch : ch;
800 std::wstring::size_type end = args.find_first_of( delim );
802 if ( end != std::wstring::npos )
805 canchor.target = args;
809 yuiError() <<
"No value for 'HREF=' in anchor '" << args <<
"'" << std::endl;
814 void NCRichText::closeAnchor()
816 canchor.close( cl, cc );
818 if ( canchor.valid() )
819 anchors.push_back( canchor );
826 bool NCRichText::PadTOKEN(
const wchar_t * sch,
const wchar_t *& ech )
829 if ( *sch++ != L
'<' || *( ech - 1 ) != L
'>' )
833 bool endtag = ( *sch == L
'/' );
839 if ( ech - sch <= 1 )
842 std::wstring value( sch, ech - 1 - sch );
846 std::wstring::size_type argstart = value.find_first_of( L
" \t\n" );
848 if ( argstart != std::wstring::npos )
850 args = value.substr( argstart );
851 value.erase( argstart );
854 for (
unsigned i = 0; i < value.length(); ++i )
856 if ( isupper( value[i] ) )
858 value[i] =
static_cast<char>( tolower( value[i] ) );
864 int headinglevel = 0;
866 TOKEN token = T_UNKNOWN;
868 switch ( value.length() )
872 if ( value[0] ==
'b' ) token = T_BOLD;
873 else if ( value[0] ==
'i' ) token = T_IT;
874 else if ( value[0] ==
'p' ) token = T_PAR;
875 else if ( value[0] ==
'a' ) token = T_ANC;
876 else if ( value[0] ==
'u' ) token = T_BOLD;
881 if ( value == L
"br" ) token = T_BR;
882 else if ( value == L
"em" ) token = T_IT;
883 else if ( value == L
"h1" ) { token = T_HEAD; headinglevel = 1; }
884 else if ( value == L
"h2" ) { token = T_HEAD; headinglevel = 2; }
885 else if ( value == L
"h3" ) { token = T_HEAD; headinglevel = 3; }
886 else if ( value == L
"hr" ) token = T_IGNORE;
887 else if ( value == L
"li" ) token = T_LI;
888 else if ( value == L
"ol" ) { token = T_LEVEL; leveltag = 1; }
889 else if ( value == L
"qt" ) token = T_IGNORE;
890 else if ( value == L
"tt" ) token = T_TT;
891 else if ( value == L
"ul" ) { token = T_LEVEL; leveltag = 0; }
897 if ( value == L
"big" ) token = T_IGNORE;
898 else if ( value == L
"pre" ) token = T_PLAIN;
903 else if ( value == L
"br/" ) token = T_BR;
904 else if ( value == L
"hr/" ) token = T_IGNORE;
909 if ( value == L
"bold" ) token = T_BOLD;
910 else if ( value == L
"code" ) token = T_TT;
911 else if ( value == L
"font" ) token = T_IGNORE;
916 if ( value == L
"large" ) token = T_IGNORE;
917 else if ( value == L
"small" ) token = T_IGNORE;
922 if ( value == L
"center" ) token = T_PAR;
923 else if ( value == L
"strong" ) token = T_BOLD;
928 if ( value == L
"blockquote" ) token = T_PAR;
938 if ( token == T_UNKNOWN )
940 yuiDebug() <<
"T_UNKNOWN :" << value <<
":" << args <<
":" << std::endl;
946 if ( token == T_IGNORE )
952 PadChangeLevel( endtag, leveltag );
956 if ( endtag && !cindent )
975 if ( headinglevel && endtag )
1000 if ( liststack.empty() )
1002 tag = std::wstring( listindent, L
' ' );
1008 if ( liststack.top() )
1010 swprintf( buf, 15, L
"%2ld. ", liststack.top()++ );
1014 swprintf( buf, 15, L
" %lc ", listleveltags[liststack.size()%listleveltags.size()] );
1021 cc = ( tag.size() < cc ? cc - tag.size() : 0 );
1025 PadTXT( tag.c_str(), tag.size() );
1037 AdjustPrePad( ech );
1081 void NCRichText::arm(
unsigned i )
1089 yuiDebug() << i <<
" (" << armed <<
")" << std::endl;
1093 if ( armed != Anchor::unset )
1096 anchors[armed].draw( *
myPad(), wStyle().richtext.getArmed( GetState() ), 0 );
1103 if ( armed != Anchor::unset )
1105 anchors[armed].draw( *
myPad(), wStyle().richtext.link, (
int ) wStyle().richtext.visitedlink );
1106 armed = Anchor::unset;
1109 if ( i != Anchor::unset )
1112 anchors[armed].draw( *
myPad(), wStyle().richtext.getArmed( GetState() ), 0 );
1115 if ( showLinkTarget )
1117 if ( armed != Anchor::unset )
1118 NCPadWidget::setLabel(
NCstring( anchors[armed].target ) );
1120 NCPadWidget::setLabel(
NCstring() );
1129 void NCRichText::HScroll(
unsigned total,
unsigned visible,
unsigned start )
1131 NCPadWidget::HScroll( total, visible, start );
1136 void NCRichText::VScroll(
unsigned total,
unsigned visible,
unsigned start )
1138 NCPadWidget::VScroll( total, visible, start );
1140 if ( plainText || anchors.empty() )
1145 vScrollFirstvisible = start;
1147 vScrollNextinvisible = start + visible;
1149 if ( armed != Anchor::unset )
1151 if ( anchors[armed].within( vScrollFirstvisible, vScrollNextinvisible ) )
1157 for (
unsigned i = 0; i < anchors.size(); ++i )
1159 if ( anchors[i].within( vScrollFirstvisible, vScrollNextinvisible ) )
1168 bool NCRichText::handleInput( wint_t key )
1170 if ( plainText || anchors.empty() )
1172 return NCPadWidget::handleInput( key );
1176 bool handled =
true;
1183 unsigned newarmed = Anchor::unset;
1185 if ( armed == Anchor::unset )
1188 for (
unsigned i = anchors.size(); i; )
1192 if ( anchors[i].eline < vScrollFirstvisible )
1199 else if ( armed > 0 )
1201 newarmed = armed - 1;
1204 if ( newarmed == Anchor::unset )
1206 handled = NCPadWidget::handleInput( KEY_UP );
1210 if ( !anchors[newarmed].within( vScrollFirstvisible, vScrollNextinvisible ) )
1211 myPad()->ScrlLine( anchors[newarmed].sline );
1222 unsigned newarmed = Anchor::unset;
1224 if ( armed == Anchor::unset )
1227 for (
unsigned i = 0; i < anchors.size(); ++i )
1229 if ( anchors[i].sline >= vScrollNextinvisible )
1236 else if ( armed + 1 < anchors.size() )
1238 newarmed = armed + 1;
1241 if ( newarmed == Anchor::unset )
1243 handled = NCPadWidget::handleInput( KEY_DOWN );
1247 if ( !anchors[newarmed].within( vScrollFirstvisible, vScrollNextinvisible ) )
1248 myPad()->ScrlLine( anchors[newarmed].sline );
1259 if ( armed != Anchor::unset
1261 && anchors[armed-1].within( vScrollFirstvisible, vScrollNextinvisible ) )
1267 handled = NCPadWidget::handleInput( key );
1275 if ( armed != Anchor::unset
1276 && armed + 1 < anchors.size()
1277 && anchors[armed+1].within( vScrollFirstvisible, vScrollNextinvisible ) )
1283 handled = NCPadWidget::handleInput( key );
1289 handled = NCPadWidget::handleInput( key );
1296 std::string NCRichText::vScrollValue()
const
1303 return std::to_string( mypad->CurPos().L );
1307 void NCRichText::setVScrollValue(
const std::string & newValue )
1311 if ( !mypad || newValue.empty() )
1314 if ( newValue ==
"minimum" )
1315 mypad->ScrlLine( 0 );
1316 else if ( newValue ==
"maximum" )
1317 mypad->ScrlLine( mypad->
maxy() );
1322 mypad->ScrlLine( std::stoi( newValue ) );
1326 yuiError() <<
"failed to set vertical scroll value '" << newValue <<
"'" << endl;
1332 std::string NCRichText::hScrollValue()
const
1339 return std::to_string( mypad->CurPos().C );
1343 void NCRichText::setHScrollValue(
const std::string & newValue )
1347 if ( !mypad || newValue.empty() )
1350 if ( newValue ==
"minimum" )
1351 mypad->ScrlCol( 0 );
1352 else if ( newValue ==
"maximum" )
1353 mypad->ScrlCol( mypad->
maxx() );
1358 mypad->ScrlCol( std::stoi( newValue ) );
1362 yuiError() <<
"failed to set horizontal scroll value '" << newValue <<
"'" << endl;