libyui-ncurses
2.44.1
|
00001 /* 00002 Copyright (C) 2000-2012 Novell, Inc 00003 This library is free software; you can redistribute it and/or modify 00004 it under the terms of the GNU Lesser General Public License as 00005 published by the Free Software Foundation; either version 2.1 of the 00006 License, or (at your option) version 3.0 of the License. This library 00007 is distributed in the hope that it will be useful, but WITHOUT ANY 00008 WARRANTY; without even the implied warranty of MERCHANTABILITY or 00009 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 00010 License for more details. You should have received a copy of the GNU 00011 Lesser General Public License along with this library; if not, write 00012 to the Free Software Foundation, Inc., 51 Franklin Street, Fifth 00013 Floor, Boston, MA 02110-1301 USA 00014 */ 00015 00016 00017 /*-/ 00018 00019 File: NCRichText.cc 00020 00021 Author: Michael Andres <ma@suse.de> 00022 00023 /-*/ 00024 00025 #define YUILogComponent "ncurses" 00026 #include <yui/YUILog.h> 00027 #include "NCRichText.h" 00028 #include "YNCursesUI.h" 00029 #include "stringutil.h" 00030 #include "stdutil.h" 00031 #include <sstream> 00032 #include <boost/algorithm/string.hpp> 00033 00034 #include <yui/YMenuItem.h> 00035 #include <yui/YApplication.h> 00036 00037 using stdutil::form; 00038 00039 00040 const unsigned NCRichText::listindent = 4; 00041 const std::wstring NCRichText::listleveltags( L"@*+o#-%$&" );// 00042 00043 const bool NCRichText::showLinkTarget = false; 00044 00045 std::map<std::wstring, std::wstring> NCRichText::_charentity; 00046 00047 00048 00049 const std::wstring NCRichText::entityLookup( const std::wstring & val_r ) 00050 { 00051 //strip leading '#', if any 00052 std::wstring::size_type hash = val_r.find( L"#", 0 ); 00053 std::wstring ascii = L""; 00054 00055 if ( hash != std::wstring::npos ) 00056 { 00057 std::wstring s = val_r.substr( hash + 1 ); 00058 wchar_t *endptr; 00059 //and try to convert to int (wcstol only knows "0x" for hex) 00060 boost::replace_all( s, "x", "0x" ); 00061 00062 long int c = std::wcstol( s.c_str(), &endptr, 0 ); 00063 00064 //conversion succeeded 00065 00066 if ( s.c_str() != endptr ) 00067 { 00068 std::wostringstream ws; 00069 ws << char( c ); 00070 ascii = ws.str(); 00071 } 00072 } 00073 00074 #define REP(l,r) _charentity[l] = r 00075 if ( _charentity.empty() ) 00076 { 00077 // initialize replacement for character entities. A value of NULL 00078 // means do not replace. 00079 std::wstring product; 00080 NCstring::RecodeToWchar( YUI::app()->productName(), "UTF-8", &product ); 00081 00082 REP( L"amp", L"&" ); 00083 REP( L"gt", L">" ); 00084 REP( L"lt", L"<" ); 00085 REP( L"nbsp", L" " ); 00086 REP( L"quot", L"\"" ); 00087 REP( L"product", product ); 00088 } 00089 00090 std::map<std::wstring, std::wstring>::const_iterator it = _charentity.find( val_r ); 00091 00092 if ( it != _charentity.end() ) 00093 { 00094 //known entity - already in the map 00095 return it->second; 00096 } 00097 else 00098 { 00099 if ( !ascii.empty() ) 00100 { 00101 //replace ascii code by character - e.g. #42 -> '*' 00102 //and insert into map to remember it 00103 REP( val_r, ascii ); 00104 } 00105 } 00106 00107 return ascii; 00108 00109 #undef REP 00110 } 00111 00112 00113 00114 /** 00115 * Filter out the known &...; entities and return the text with entities 00116 * replaced 00117 **/ 00118 const std::wstring NCRichText::filterEntities( const std::wstring & text ) 00119 { 00120 std::wstring txt = text; 00121 // filter known '&..;' 00122 00123 for ( std::wstring::size_type special = txt.find( L"&" ); 00124 special != std::wstring::npos; 00125 special = txt.find( L"&", special + 1 ) ) 00126 { 00127 std::wstring::size_type colon = txt.find( L";", special + 1 ); 00128 00129 if ( colon == std::wstring::npos ) 00130 break; // no ';' -> no need to continue 00131 00132 const std::wstring repl = entityLookup( txt.substr( special + 1, colon - special - 1 ) ); 00133 00134 if ( !repl.empty() 00135 || txt.substr( special + 1, colon - special - 1 ) == L"product" ) // always replace &product; 00136 { 00137 txt.replace( special, colon - special + 1, repl ); 00138 } 00139 else 00140 yuiMilestone() << "porn.bat" << std::endl; 00141 } 00142 00143 return txt; 00144 } 00145 00146 00147 void NCRichText::Anchor::draw( NCPad & pad, const chtype attr, int color ) 00148 { 00149 unsigned l = sline; 00150 unsigned c = scol; 00151 00152 while ( l < eline ) 00153 { 00154 pad.move( l, c ); 00155 pad.chgat( -1, attr, color ); 00156 ++l; 00157 c = 0; 00158 } 00159 00160 pad.move( l, c ); 00161 00162 pad.chgat( ecol - c, attr, color ); 00163 } 00164 00165 00166 NCRichText::NCRichText( YWidget * parent, const std::string & ntext, 00167 bool plainTextMode ) 00168 : YRichText( parent, ntext, plainTextMode ) 00169 , NCPadWidget( parent ) 00170 , text( ntext ) 00171 , plainText( plainTextMode ) 00172 , textwidth( 0 ) 00173 , cl( 0 ) 00174 , cc( 0 ) 00175 , cindent( 0 ) 00176 , atbol( true ) 00177 , preTag( false ) 00178 , Tattr( 0 ) 00179 { 00180 yuiDebug() << std::endl; 00181 activeLabelOnly = true; 00182 setValue( ntext ); 00183 } 00184 00185 00186 NCRichText::~NCRichText() 00187 { 00188 yuiDebug() << std::endl; 00189 } 00190 00191 00192 int NCRichText::preferredWidth() 00193 { 00194 return wGetDefsze().W; 00195 } 00196 00197 00198 int NCRichText::preferredHeight() 00199 { 00200 return wGetDefsze().H; 00201 } 00202 00203 00204 void NCRichText::setEnabled( bool do_bv ) 00205 { 00206 NCWidget::setEnabled( do_bv ); 00207 YRichText::setEnabled( do_bv ); 00208 } 00209 00210 00211 void NCRichText::setSize( int newwidth, int newheight ) 00212 { 00213 wRelocate( wpos( 0 ), wsze( newheight, newwidth ) ); 00214 } 00215 00216 00217 void NCRichText::setLabel( const std::string & nlabel ) 00218 { 00219 // not implemented: YRichText::setLabel( nlabel ); 00220 NCPadWidget::setLabel( NCstring( nlabel ) ); 00221 } 00222 00223 00224 void NCRichText::setValue( const std::string & ntext ) 00225 { 00226 DelPad(); 00227 text = NCstring( ntext ); 00228 YRichText::setValue( ntext ); 00229 Redraw(); 00230 } 00231 00232 00233 void NCRichText::wRedraw() 00234 { 00235 if ( !win ) 00236 return; 00237 00238 bool initial = ( !myPad() || !myPad()->Destwin() ); 00239 00240 if ( !( plainText || anchors.empty() ) ) 00241 arm( armed ); 00242 00243 NCPadWidget::wRedraw(); 00244 00245 if ( initial && autoScrollDown() ) 00246 { 00247 myPad()->ScrlTo( wpos( myPad()->maxy(), 0 ) ); 00248 } 00249 00250 return; 00251 } 00252 00253 00254 void NCRichText::wRecoded() 00255 { 00256 DelPad(); 00257 wRedraw(); 00258 } 00259 00260 00261 NCursesEvent NCRichText::wHandleInput( wint_t key ) 00262 { 00263 NCursesEvent ret; 00264 handleInput( key ); 00265 00266 if ( !( plainText || anchors.empty() ) ) 00267 { 00268 switch ( key ) 00269 { 00270 case KEY_SPACE: 00271 case KEY_RETURN: 00272 00273 if ( armed != Anchor::unset ) 00274 { 00275 ret = NCursesEvent::menu; 00276 std::string str; 00277 NCstring::RecodeFromWchar( anchors[armed].target, "UTF-8", &str ); 00278 yuiMilestone() << "LINK: " << str << std::endl; 00279 ret.selection = new YMenuItem( str ); 00280 } 00281 00282 break; 00283 } 00284 } 00285 00286 return ret; 00287 } 00288 00289 00290 NCPad * NCRichText::CreatePad() 00291 { 00292 wsze psze( defPadSze() ); 00293 textwidth = psze.W; 00294 NCPad * npad = new NCPad( psze.H, textwidth, *this ); 00295 return npad; 00296 } 00297 00298 00299 void NCRichText::DrawPad() 00300 { 00301 yuiDebug() 00302 << "Start: plain mode " << plainText << std::endl 00303 << " padsize " << myPad()->size() << std::endl 00304 << " text length " << text.str().size() << std::endl; 00305 00306 myPad()->bkgdset( wStyle().richtext.plain ); 00307 myPad()->clear(); 00308 00309 if ( plainText ) 00310 DrawPlainPad(); 00311 else 00312 DrawHTMLPad(); 00313 00314 yuiDebug() << "Done" << std::endl; 00315 } 00316 00317 00318 void NCRichText::DrawPlainPad() 00319 { 00320 NCtext ftext( text ); 00321 yuiDebug() << "ftext is " << wsze( ftext.Lines(), ftext.Columns() ) << std::endl; 00322 00323 AdjustPad( wsze( ftext.Lines(), ftext.Columns() ) ); 00324 00325 cl = 0; 00326 00327 for ( NCtext::const_iterator line = ftext.begin(); 00328 line != ftext.end(); ++line, ++cl ) 00329 { 00330 myPad()->addwstr( cl, 0, ( *line ).str().c_str() ); 00331 } 00332 } 00333 00334 void NCRichText::PadPreTXT( const wchar_t * osch, const unsigned olen ) 00335 { 00336 std::wstring wtxt( osch, olen ); 00337 00338 // resolve the entities even in PRE (#71718) 00339 wtxt = filterEntities( wtxt ); 00340 00341 NCstring nctxt( wtxt ); 00342 NCtext ftext( nctxt ); 00343 00344 // insert the text 00345 const wchar_t * sch = wtxt.data(); 00346 00347 while ( *sch ) 00348 { 00349 myPad()->addwstr( sch, 1 ); // add one wide chararacter 00350 00351 ++sch; 00352 } 00353 } 00354 00355 // 00356 // DrawHTMLPad tools 00357 // 00358 00359 inline void SkipToken( const wchar_t *& wch ) 00360 { 00361 do 00362 { 00363 ++wch; 00364 } 00365 while ( *wch && *wch != L'>' ); 00366 00367 if ( *wch ) 00368 ++wch; 00369 } 00370 00371 00372 static std::wstring WStoken( L" \n\t\v\r\f" ); 00373 00374 00375 inline void SkipWS( const wchar_t *& wch ) 00376 { 00377 do 00378 { 00379 ++wch; 00380 } 00381 while ( *wch && WStoken.find( *wch ) != std::wstring::npos ); 00382 } 00383 00384 00385 static std::wstring WDtoken( L" <\n\t\v\r\f" ); // WS + TokenStart '<' 00386 00387 00388 inline void SkipWord( const wchar_t *& wch ) 00389 { 00390 do 00391 { 00392 ++wch; 00393 } 00394 while ( *wch && WDtoken.find( *wch ) == std::wstring::npos ); 00395 } 00396 00397 static std::wstring PREtoken( L"<\n\v\r\f" ); // line manipulations + TokenStart '<' 00398 00399 00400 inline void SkipPreTXT( const wchar_t *& wch ) 00401 { 00402 do 00403 { 00404 ++wch; 00405 } 00406 while ( *wch && PREtoken.find( *wch ) == std::wstring::npos ); 00407 } 00408 00409 00410 // 00411 // Calculate longest line of text in <pre> </pre> tags 00412 // and adjust the pad accordingly 00413 // 00414 void NCRichText::AdjustPrePad( const wchar_t *osch ) 00415 { 00416 const wchar_t * wch = osch; 00417 std::wstring wstr( wch, 6 ); 00418 00419 do 00420 { 00421 ++wch; 00422 wstr.assign( wch, 6 ); 00423 } 00424 while ( *wch && wstr != L"</pre>" ); 00425 00426 std::wstring wtxt( osch, wch - osch ); 00427 00428 // resolve the entities to get correct length for calculation of longest line 00429 wtxt = filterEntities( wtxt ); 00430 00431 // replace <br> by \n to get appropriate lines in NCtext 00432 boost::replace_all( wtxt, L"<br>", L"\n" ); 00433 00434 yuiDebug() << "Text: " << wtxt << " initial length: " << wch - osch << std::endl; 00435 00436 NCstring nctxt( wtxt ); 00437 NCtext ftext( nctxt ); 00438 00439 std::list<NCstring>::const_iterator line; 00440 size_t llen = 0; // longest line 00441 00442 // iterate through NCtext 00443 for ( line = ftext.Text().begin(); line != ftext.Text().end(); ++line ) 00444 { 00445 size_t tmp_len = 0; 00446 00447 tmp_len = textWidth( (*line).str() ); 00448 00449 if ( tmp_len > llen ) 00450 llen = tmp_len; 00451 } 00452 yuiDebug() << "Longest line: " << llen << std::endl; 00453 00454 if ( llen > textwidth ) 00455 { 00456 textwidth = llen; 00457 AdjustPad( wsze( cl + ftext.Lines(), llen ) ); // adjust pad to longest line 00458 } 00459 00460 } 00461 00462 void NCRichText::DrawHTMLPad() 00463 { 00464 yuiDebug() << "Start:" << std::endl; 00465 00466 liststack = std::stack<int>(); 00467 canchor = Anchor(); 00468 anchors.clear(); 00469 armed = Anchor::unset; 00470 00471 cl = 0; 00472 cc = 0; 00473 cindent = 0; 00474 myPad()->move( cl, cc ); 00475 atbol = true; 00476 00477 const wchar_t * wch = ( wchar_t * )text.str().data(); 00478 const wchar_t * swch = 0; 00479 00480 while ( *wch ) 00481 { 00482 switch ( *wch ) 00483 { 00484 case L' ': 00485 case L'\t': 00486 case L'\n': 00487 case L'\v': 00488 case L'\r': 00489 case L'\f': 00490 if ( ! preTag ) 00491 { 00492 SkipWS( wch ); 00493 PadWS(); 00494 } 00495 else 00496 { 00497 switch ( *wch ) 00498 { 00499 case L' ': // add white space 00500 case L'\t': 00501 myPad()->addwstr( wch, 1 ); 00502 break; 00503 00504 case L'\n': 00505 case L'\f': 00506 PadNL(); // add new line 00507 break; 00508 00509 default: 00510 yuiDebug() << "Ignoring " << *wch << std::endl; 00511 } 00512 ++wch; 00513 } 00514 00515 break; 00516 00517 case L'<': 00518 swch = wch; 00519 SkipToken( wch ); 00520 00521 if ( PadTOKEN( swch, wch ) ) 00522 break; // strip token 00523 else 00524 wch = swch; // reset and fall through 00525 00526 default: 00527 swch = wch; 00528 00529 if ( !preTag ) 00530 { 00531 SkipWord( wch ); 00532 PadTXT( swch, wch - swch ); 00533 } 00534 else 00535 { 00536 SkipPreTXT( wch ); 00537 PadPreTXT( swch, wch - swch ); 00538 } 00539 00540 break; 00541 } 00542 } 00543 00544 PadBOL(); 00545 AdjustPad( wsze( cl, textwidth ) ); 00546 00547 yuiDebug() << "Anchors: " << anchors.size() << std::endl; 00548 00549 for ( unsigned i = 0; i < anchors.size(); ++i ) 00550 { 00551 yuiDebug() << form( " %2d: [%2d,%2d] -> [%2d,%2d]", 00552 i, 00553 anchors[i].sline, anchors[i].scol, 00554 anchors[i].eline, anchors[i].ecol ) << std::endl; 00555 } 00556 } 00557 00558 00559 inline void NCRichText::PadNL() 00560 { 00561 cc = cindent; 00562 00563 if ( ++cl == ( unsigned )myPad()->height() ) 00564 { 00565 AdjustPad( wsze( myPad()->height() + defPadSze().H, textwidth ) ); 00566 } 00567 00568 myPad()->move( cl, cc ); 00569 00570 atbol = true; 00571 } 00572 00573 00574 inline void NCRichText::PadBOL() 00575 { 00576 if ( !atbol ) 00577 PadNL(); 00578 } 00579 00580 00581 inline void NCRichText::PadWS( const bool tab ) 00582 { 00583 if ( atbol ) 00584 return; // no WS at beginning of line 00585 00586 if ( cc == textwidth ) 00587 { 00588 PadNL(); 00589 } 00590 else 00591 { 00592 myPad()->addwstr( L" " ); 00593 ++cc; 00594 } 00595 } 00596 00597 00598 inline void NCRichText::PadTXT( const wchar_t * osch, const unsigned olen ) 00599 { 00600 std::wstring txt( osch, olen ); 00601 00602 txt = filterEntities( txt ); 00603 00604 size_t len = textWidth( txt ); 00605 00606 if ( !atbol && cc + len > textwidth ) 00607 PadNL(); 00608 00609 // insert the text 00610 const wchar_t * sch = txt.data(); 00611 00612 while ( *sch ) 00613 { 00614 myPad()->addwstr( sch, 1 ); // add one wide chararacter 00615 cc += wcwidth( *sch ); 00616 atbol = false; // at begin of line = false 00617 00618 if ( cc >= textwidth ) 00619 { 00620 PadNL(); // add a new line 00621 } 00622 00623 sch++; 00624 } 00625 } 00626 00627 /** 00628 * Get the number of columns needed to print a 'std::wstring'. Only printable characters 00629 * are taken into account because otherwise 'wcwidth' would return -1 (e.g. for '\n'). 00630 * Tabs are calculated with tabsize(). 00631 * Attention: only use textWidth() to calculate space, not for iterating through a text 00632 * or to get the length of a text (real text length includes new lines). 00633 */ 00634 size_t NCRichText::textWidth( std::wstring wstr ) 00635 { 00636 size_t len = 0; 00637 std::wstring::const_iterator wstr_it; // iterator for std::wstring 00638 00639 for ( wstr_it = wstr.begin(); wstr_it != wstr.end() ; ++wstr_it ) 00640 { 00641 // check whether char is printable 00642 if ( iswprint( *wstr_it ) ) 00643 { 00644 len += wcwidth( *wstr_it ); 00645 } 00646 else if ( *wstr_it == '\t' ) 00647 { 00648 len += myPad()->tabsize(); 00649 } 00650 } 00651 00652 return len; 00653 } 00654 00655 00656 /** 00657 * Set character attributes (e.g. color, font face...) 00658 **/ 00659 inline void NCRichText::PadSetAttr() 00660 { 00661 const NCstyle::StRichtext & style( wStyle().richtext ); 00662 chtype nbg = style.plain; 00663 00664 if ( Tattr & T_ANC ) 00665 { 00666 nbg = style.link; 00667 } 00668 else if ( Tattr & T_HEAD ) 00669 { 00670 nbg = style.title; 00671 } 00672 else 00673 { 00674 switch ( Tattr & Tfontmask ) 00675 { 00676 case T_BOLD: 00677 nbg = style.B; 00678 break; 00679 00680 case T_IT: 00681 nbg = style.I; 00682 break; 00683 00684 case T_TT: 00685 nbg = style.T; 00686 break; 00687 00688 case T_BOLD|T_IT: 00689 nbg = style.BI; 00690 break; 00691 00692 case T_BOLD|T_TT: 00693 nbg = style.BT; 00694 break; 00695 00696 case T_IT|T_TT: 00697 nbg = style.IT; 00698 break; 00699 00700 case T_BOLD|T_IT|T_TT: 00701 nbg = style.BIT; 00702 break; 00703 } 00704 } 00705 00706 myPad()->bkgdset( nbg ); 00707 } 00708 00709 00710 void NCRichText::PadSetLevel() 00711 { 00712 cindent = listindent * liststack.size(); 00713 00714 if ( cindent > textwidth / 2 ) 00715 cindent = textwidth / 2; 00716 00717 if ( atbol ) 00718 { 00719 cc = cindent; 00720 myPad()->move( cl, cc ); 00721 } 00722 } 00723 00724 00725 void NCRichText::PadChangeLevel( bool down, int tag ) 00726 { 00727 if ( down ) 00728 { 00729 if ( liststack.size() ) 00730 liststack.pop(); 00731 } 00732 else 00733 { 00734 liststack.push( tag ); 00735 } 00736 00737 PadSetLevel(); 00738 } 00739 00740 00741 void NCRichText::openAnchor( std::wstring args ) 00742 { 00743 canchor.open( cl, cc ); 00744 00745 const wchar_t * ch = ( wchar_t * )args.data(); 00746 const wchar_t * lookupstr = L"href = "; 00747 const wchar_t * lookup = lookupstr; 00748 00749 for ( ; *ch && *lookup; ++ch ) 00750 { 00751 wchar_t c = towlower( *ch ); 00752 00753 switch ( c ) 00754 { 00755 case L'\t': 00756 case L' ': 00757 00758 if ( *lookup != L' ' ) 00759 lookup = lookupstr; 00760 00761 break; 00762 00763 default: 00764 if ( *lookup == L' ' ) 00765 { 00766 ++lookup; 00767 00768 if ( !*lookup ) 00769 { 00770 // ch is the 1st char after lookupstr 00771 --ch; // end of loop will ++ch 00772 break; 00773 } 00774 } 00775 00776 if ( c == *lookup ) 00777 ++lookup; 00778 else 00779 lookup = lookupstr; 00780 00781 break; 00782 } 00783 } 00784 00785 if ( !*lookup ) 00786 { 00787 const wchar_t * delim = ( *ch == L'"' ) ? L"\"" : L" \t"; 00788 args = ( *ch == L'"' ) ? ++ch : ch; 00789 00790 std::wstring::size_type end = args.find_first_of( delim ); 00791 00792 if ( end != std::wstring::npos ) 00793 args.erase( end ); 00794 00795 canchor.target = args; 00796 } 00797 else 00798 { 00799 yuiError() << "No value for 'HREF=' in anchor '" << args << "'" << std::endl; 00800 } 00801 } 00802 00803 00804 void NCRichText::closeAnchor() 00805 { 00806 canchor.close( cl, cc ); 00807 00808 if ( canchor.valid() ) 00809 anchors.push_back( canchor ); 00810 00811 canchor = Anchor(); 00812 } 00813 00814 00815 // expect "<[/]value>" 00816 bool NCRichText::PadTOKEN( const wchar_t * sch, const wchar_t *& ech ) 00817 { 00818 // "<[/]value>" 00819 if ( *sch++ != L'<' || *( ech - 1 ) != L'>' ) 00820 return false; 00821 00822 // "[/]value>" 00823 bool endtag = ( *sch == L'/' ); 00824 00825 if ( endtag ) 00826 ++sch; 00827 00828 // "value>" 00829 if ( ech - sch <= 1 ) 00830 return false; 00831 00832 std::wstring value( sch, ech - 1 - sch ); 00833 00834 std::wstring args; 00835 00836 std::wstring::size_type argstart = value.find_first_of( L" \t\n" ); 00837 00838 if ( argstart != std::wstring::npos ) 00839 { 00840 args = value.substr( argstart ); 00841 value.erase( argstart ); 00842 } 00843 00844 for ( unsigned i = 0; i < value.length(); ++i ) 00845 { 00846 if ( isupper( value[i] ) ) 00847 { 00848 value[i] = static_cast<char>( tolower( value[i] ) ); 00849 } 00850 } 00851 00852 int leveltag = 0; 00853 00854 int headinglevel = 0; 00855 00856 TOKEN token = T_UNKNOWN; 00857 00858 switch ( value.length() ) 00859 { 00860 case 1: 00861 00862 if ( value[0] == 'b' ) token = T_BOLD; 00863 else if ( value[0] == 'i' ) token = T_IT; 00864 else if ( value[0] == 'p' ) token = T_PAR; 00865 else if ( value[0] == 'a' ) token = T_ANC; 00866 else if ( value[0] == 'u' ) token = T_BOLD; 00867 00868 break; 00869 00870 case 2: 00871 if ( value == L"br" ) token = T_BR; 00872 else if ( value == L"em" ) token = T_IT; 00873 else if ( value == L"h1" ) { token = T_HEAD; headinglevel = 1; } 00874 else if ( value == L"h2" ) { token = T_HEAD; headinglevel = 2; } 00875 else if ( value == L"h3" ) { token = T_HEAD; headinglevel = 3; } 00876 else if ( value == L"hr" ) token = T_IGNORE; 00877 else if ( value == L"li" ) token = T_LI; 00878 else if ( value == L"ol" ) { token = T_LEVEL; leveltag = 1; } 00879 else if ( value == L"qt" ) token = T_IGNORE; 00880 else if ( value == L"tt" ) token = T_TT; 00881 else if ( value == L"ul" ) { token = T_LEVEL; leveltag = 0; } 00882 00883 break; 00884 00885 case 3: 00886 00887 if ( value == L"big" ) token = T_IGNORE; 00888 else if ( value == L"pre" ) token = T_PLAIN; 00889 00890 break; 00891 00892 case 4: 00893 if ( value == L"bold" ) token = T_BOLD; 00894 else if ( value == L"code" ) token = T_TT; 00895 else if ( value == L"font" ) token = T_IGNORE; 00896 00897 break; 00898 00899 case 5: 00900 if ( value == L"large" ) token = T_IGNORE; 00901 else if ( value == L"small" ) token = T_IGNORE; 00902 00903 break; 00904 00905 case 6: 00906 if ( value == L"center" ) token = T_PAR; 00907 else if ( value == L"strong" ) token = T_BOLD; 00908 00909 break; 00910 00911 case 10: 00912 if ( value == L"blockquote" ) token = T_PAR; 00913 00914 break; 00915 00916 default: 00917 token = T_UNKNOWN; 00918 00919 break; 00920 } 00921 00922 if ( token == T_UNKNOWN ) 00923 { 00924 yuiDebug() << "T_UNKNOWN :" << value << ":" << args << ":" << std::endl; 00925 // see bug #67319 00926 // return false; 00927 return true; 00928 } 00929 00930 if ( token == T_IGNORE ) 00931 return true; 00932 00933 switch ( token ) 00934 { 00935 case T_LEVEL: 00936 PadChangeLevel( endtag, leveltag ); 00937 PadBOL(); 00938 // add new line after end of the list 00939 // (only at the very end) 00940 if ( endtag && !cindent ) 00941 PadNL(); 00942 00943 break; 00944 00945 case T_BR: 00946 PadNL(); 00947 00948 break; 00949 00950 case T_HEAD: 00951 if ( endtag ) 00952 Tattr &= ~token; 00953 else 00954 Tattr |= token; 00955 00956 PadSetAttr(); 00957 PadBOL(); 00958 00959 if ( headinglevel && endtag ) 00960 PadNL(); 00961 00962 break; 00963 00964 case T_PAR: 00965 PadBOL(); 00966 00967 if ( !cindent ) 00968 { 00969 if ( endtag ) 00970 // add new line after closing tag (FaTE 3124) 00971 PadNL(); 00972 } 00973 00974 break; 00975 00976 case T_LI: 00977 PadSetLevel(); 00978 PadBOL(); 00979 00980 if ( !endtag ) 00981 { 00982 std::wstring tag; 00983 00984 if ( liststack.empty() ) 00985 { 00986 tag = std::wstring( listindent, L' ' ); 00987 } 00988 else 00989 { 00990 wchar_t buf[16]; 00991 00992 if ( liststack.top() ) 00993 { 00994 swprintf( buf, 15, L"%2ld. ", liststack.top()++ ); 00995 } 00996 else 00997 { 00998 swprintf( buf, 15, L" %lc ", listleveltags[liststack.size()%listleveltags.size()] ); 00999 } 01000 01001 tag = buf; 01002 } 01003 01004 // outsent list tag: 01005 cc = ( tag.size() < cc ? cc - tag.size() : 0 ); 01006 01007 myPad()->move( cl, cc ); 01008 01009 PadTXT( tag.c_str(), tag.size() ); 01010 01011 atbol = true; 01012 } 01013 01014 break; 01015 01016 case T_PLAIN: 01017 01018 if ( !endtag ) 01019 { 01020 preTag = true; // display text preserving newlines and spaces 01021 AdjustPrePad( ech ); 01022 } 01023 else 01024 { 01025 preTag = false; 01026 PadNL(); // add new line (text may continue after </pre>) 01027 } 01028 01029 break; 01030 01031 case T_ANC: 01032 01033 if ( endtag ) 01034 { 01035 closeAnchor(); 01036 } 01037 else 01038 { 01039 openAnchor( args ); 01040 } 01041 01042 // fall through 01043 01044 case T_BOLD: 01045 case T_IT: 01046 case T_TT: 01047 if ( endtag ) 01048 Tattr &= ~token; 01049 else 01050 Tattr |= token; 01051 01052 PadSetAttr(); 01053 01054 break; 01055 01056 case T_IGNORE: 01057 case T_UNKNOWN: 01058 break; 01059 } 01060 01061 return true; 01062 } 01063 01064 01065 void NCRichText::arm( unsigned i ) 01066 { 01067 if ( !myPad() ) 01068 { 01069 armed = i; 01070 return; 01071 } 01072 01073 yuiDebug() << i << " (" << armed << ")" << std::endl; 01074 01075 if ( i == armed ) 01076 { 01077 if ( armed != Anchor::unset ) 01078 { 01079 // just redraw 01080 anchors[armed].draw( *myPad(), wStyle().richtext.getArmed( GetState() ), 0 ); 01081 myPad()->update(); 01082 } 01083 01084 return; 01085 } 01086 01087 if ( armed != Anchor::unset ) 01088 { 01089 anchors[armed].draw( *myPad(), wStyle().richtext.link, ( int ) wStyle().richtext.visitedlink ); 01090 armed = Anchor::unset; 01091 } 01092 01093 if ( i != Anchor::unset ) 01094 { 01095 armed = i; 01096 anchors[armed].draw( *myPad(), wStyle().richtext.getArmed( GetState() ), 0 ); 01097 } 01098 01099 if ( showLinkTarget ) 01100 { 01101 if ( armed != Anchor::unset ) 01102 NCPadWidget::setLabel( NCstring( anchors[armed].target ) ); 01103 else 01104 NCPadWidget::setLabel( NCstring() ); 01105 } 01106 else 01107 { 01108 myPad()->update(); 01109 } 01110 } 01111 01112 01113 void NCRichText::HScroll( unsigned total, unsigned visible, unsigned start ) 01114 { 01115 NCPadWidget::HScroll( total, visible, start ); 01116 // no hyperlink handling needed, because Ritchtext does not HScroll 01117 } 01118 01119 01120 void NCRichText::VScroll( unsigned total, unsigned visible, unsigned start ) 01121 { 01122 NCPadWidget::VScroll( total, visible, start ); 01123 01124 if ( plainText || anchors.empty() ) 01125 return; // <-- no links to check 01126 01127 // Take care of hyperlinks: Check whether an armed link is visible. 01128 // If not arm the first visible link on page or none. 01129 vScrollFirstvisible = start; 01130 01131 vScrollNextinvisible = start + visible; 01132 01133 if ( armed != Anchor::unset ) 01134 { 01135 if ( anchors[armed].within( vScrollFirstvisible, vScrollNextinvisible ) ) 01136 return; // <-- armed link is vissble 01137 else 01138 disarm(); 01139 } 01140 01141 for ( unsigned i = 0; i < anchors.size(); ++i ) 01142 { 01143 if ( anchors[i].within( vScrollFirstvisible, vScrollNextinvisible ) ) 01144 { 01145 arm( i ); 01146 break; 01147 } 01148 } 01149 } 01150 01151 01152 bool NCRichText::handleInput( wint_t key ) 01153 { 01154 if ( plainText || anchors.empty() ) 01155 { 01156 return NCPadWidget::handleInput( key ); 01157 } 01158 01159 // take care of hyperlinks 01160 bool handled = true; 01161 01162 switch ( key ) 01163 { 01164 case KEY_LEFT: 01165 // jump to previous link; scroll up if none 01166 { 01167 unsigned newarmed = Anchor::unset; 01168 01169 if ( armed == Anchor::unset ) 01170 { 01171 // look for an anchor above current page 01172 for ( unsigned i = anchors.size(); i; ) 01173 { 01174 --i; 01175 01176 if ( anchors[i].eline < vScrollFirstvisible ) 01177 { 01178 newarmed = i; 01179 break; 01180 } 01181 } 01182 } 01183 else if ( armed > 0 ) 01184 { 01185 newarmed = armed - 1; 01186 } 01187 01188 if ( newarmed == Anchor::unset ) 01189 { 01190 handled = NCPadWidget::handleInput( KEY_UP ); 01191 } 01192 else 01193 { 01194 if ( !anchors[newarmed].within( vScrollFirstvisible, vScrollNextinvisible ) ) 01195 myPad()->ScrlLine( anchors[newarmed].sline ); 01196 01197 arm( newarmed ); 01198 } 01199 } 01200 01201 break; 01202 01203 case KEY_RIGHT: 01204 // jump to next link; scroll down if none 01205 { 01206 unsigned newarmed = Anchor::unset; 01207 01208 if ( armed == Anchor::unset ) 01209 { 01210 // look for an anchor below current page 01211 for ( unsigned i = 0; i < anchors.size(); ++i ) 01212 { 01213 if ( anchors[i].sline >= vScrollNextinvisible ) 01214 { 01215 newarmed = i; 01216 break; 01217 } 01218 } 01219 } 01220 else if ( armed + 1 < anchors.size() ) 01221 { 01222 newarmed = armed + 1; 01223 } 01224 01225 if ( newarmed == Anchor::unset ) 01226 { 01227 handled = NCPadWidget::handleInput( KEY_DOWN ); 01228 } 01229 else 01230 { 01231 if ( !anchors[newarmed].within( vScrollFirstvisible, vScrollNextinvisible ) ) 01232 myPad()->ScrlLine( anchors[newarmed].sline ); 01233 01234 arm( newarmed ); 01235 } 01236 } 01237 01238 break; 01239 01240 case KEY_UP: 01241 // arm previous visible link; scroll up if none 01242 01243 if ( armed != Anchor::unset 01244 && armed > 0 01245 && anchors[armed-1].within( vScrollFirstvisible, vScrollNextinvisible ) ) 01246 { 01247 arm( armed - 1 ); 01248 } 01249 else 01250 { 01251 handled = NCPadWidget::handleInput( key ); 01252 } 01253 01254 break; 01255 01256 case KEY_DOWN: 01257 // arm next visible link; scroll down if none 01258 01259 if ( armed != Anchor::unset 01260 && armed + 1 < anchors.size() 01261 && anchors[armed+1].within( vScrollFirstvisible, vScrollNextinvisible ) ) 01262 { 01263 arm( armed + 1 ); 01264 } 01265 else 01266 { 01267 handled = NCPadWidget::handleInput( key ); 01268 } 01269 01270 break; 01271 01272 default: 01273 handled = NCPadWidget::handleInput( key ); 01274 }; 01275 01276 return handled; 01277 } 01278 01279