Blokkal
an Extendable KDE Blogging Client
SourceForge.net Logo

htmlentrytextedit.cpp

00001 /***************************************************************************
00002  *   Copyright (C) 2006 - 2008 by Martin Mueller                           *
00003  *   orvio@orvio.de                                                        *
00004  *                                                                         *
00005  *   This program is free software; you can redistribute it and/or modify  *
00006  *   it under the terms of the GNU General Public License as published by  *
00007  *   the Free Software Foundation; either version 2 of the License, or     *
00008  *   (at your option) any later version.                                   *
00009  *                                                                         *
00010  *   This program is distributed in the hope that it will be useful,       *
00011  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
00012  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
00013  *   GNU General Public License for more details.                          *
00014  *                                                                         *
00015  *   You should have received a copy of the GNU General Public License     *
00016  *   along with this program; if not, write to the                         *
00017  *   Free Software Foundation, Inc.,                                       *
00018  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
00019  ***************************************************************************/
00020 #include "htmlentrytextedit.h"
00021 #include "htmlentrytextedit.moc"
00022 
00023 #include "stdaction.h"
00024 
00025 #include "../blokkalappearancesettings.h"
00026 
00027 #include <kmenu.h>
00028 #include <kaction.h>
00029 
00030 #include <klocale.h>
00031 #include <kdebug.h>
00032 #include <kglobalsettings.h>
00033 
00034 #include <QContextMenuEvent>
00035 
00036 
00037 //-------------------
00038 //HtmlTextHighlighter
00039 //-------------------
00040 class Blokkal::Ui::HtmlTextHighlighter::Private
00041 {
00042 public:
00043         Private( const QTextEdit * edit ) :
00044         edit( edit ){}
00045 
00046         const QTextEdit * const edit;
00047         
00048         bool highlightSyntax;
00049         bool highlightSpelling;
00050 
00051         QTextCharFormat normalFormat;
00052         QTextCharFormat tagFormat;
00053         QTextCharFormat stringFormat;
00054         QTextCharFormat valueFormat;
00055         
00056 };
00057 
00058 
00059 Blokkal::Ui::HtmlTextHighlighter::HtmlTextHighlighter( QTextEdit * parent ):
00060 Sonnet::Highlighter( parent ),
00061 d( new Private( parent ) )
00062 {
00063         slotAppearanceSettingsChanged();
00064         
00065         connect( AppearanceSettings::self(),
00066                  SIGNAL( appearanceSettingsChanged( void ) ),
00067                  SLOT( slotAppearanceSettingsChanged( void ) ) );
00068         connect( KGlobalSettings::self(),
00069                  SIGNAL( kdisplayFontChanged ( void )),
00070                  SLOT( slotAppearanceSettingsChanged( void ) ) );
00071 }
00072 
00073 Blokkal::Ui::HtmlTextHighlighter::~HtmlTextHighlighter( void )
00074 {
00075         delete d;
00076 }
00077 
00078 void Blokkal::Ui::HtmlTextHighlighter::highlightBlock( const QString & text )
00079 {
00080         if( d->highlightSpelling ) {
00081                 Sonnet::Highlighter::highlightBlock( text );
00082         }
00083         else {
00084                 setFormat( 0,text.length(), d->normalFormat );
00085         }
00086 
00087         if( d->highlightSyntax
00088             || ( !d->highlightSyntax && d->highlightSpelling ) )
00089         {
00090                 int tagStart = 0;
00091                 int tagEnd = 0;
00092                 while( ( tagStart = text.indexOf( '<', tagStart ) ) > -1 ) {
00093                         //make sure this is really something like an html tag
00094                         if( ( ( text.length() > tagStart + 1 )
00095                               && text.at( tagStart + 1 ).isLetter() )
00096                             || ( ( text.length() > tagStart + 2 )
00097                                  && ( text.at( tagStart + 1 ) == '/' && text.at( tagStart + 2 ).isLetter() ) ) )
00098                         {
00099                                 tagEnd = text.indexOf( '>', tagStart );
00100                                 if( tagEnd < 0 ) {
00101                                         tagEnd = text.length();
00102                                 }
00103                 
00104                                 if( d->highlightSyntax ) {
00105                                         setFormat( tagStart, tagEnd - tagStart + 1 , d->tagFormat );
00106                                         bool inString = FALSE;
00107                                         bool inValue = FALSE;
00108                                         int subStart = tagStart;
00109                                         for( int index = tagStart; index < tagEnd; ++index ) {
00110                                                 switch( text.at( index ).toLower().cell() ) {
00111                                                 case '"':
00112                                                         if( inString ) {
00113                                                                 if( text.at( index - 1) != '\\' ) {
00114                                                                         setFormat( subStart, index - subStart + 1, d->stringFormat );
00115                                                                         inString = FALSE;
00116                                                                 }
00117                                                         }
00118                                                         else {
00119                                                                 subStart = index;
00120                                                                 inString = TRUE;
00121                                                         }
00122                                                         break;
00123                                                 case '#':
00124                                                 case '0':
00125                                                 case '1':
00126                                                 case '2':
00127                                                 case '3':
00128                                                 case '4':
00129                                                 case '5':
00130                                                 case '6':
00131                                                 case '7':
00132                                                 case '8':
00133                                                 case '9':
00134                                                         if( !inString
00135                                                                 && !inValue
00136                                                                 && (  text.at( index-1) == ' '
00137                                                                                 || text.at( index-1) == '\t'
00138                                                                                 || text.at( index-1) == '=' ) ) {
00139                                                                 subStart = index;
00140                                                                 inValue = TRUE;
00141                                                         }
00142                                                         break;
00143                                                 case ' ':
00144                                                 case '\t':
00145                                                         if( inValue ) {
00146                                                                 setFormat( subStart, index - subStart + 1, d->valueFormat );
00147                                                                 inValue = FALSE;
00148                                                         }
00149                                                         break;
00150                                                 case 'a':
00151                                                 case 'b':
00152                                                 case 'c':
00153                                                 case 'd':
00154                                                 case 'e':
00155                                                 case 'f':
00156                                                         break;
00157                                                 case '/':
00158                                                         if( inValue && text.at( index + 1 ) == '>' ) {
00159                                                                 setFormat( subStart, index - subStart, d->valueFormat );
00160                                                         }
00161                                                         inValue = FALSE;
00162                                                         break;
00163                                                 default:
00164                                                         if( inValue ) {
00165                                                                 inValue = FALSE;
00166                                                         }
00167                                                         break;
00168                                                 }
00169                                         }
00170                                         if( inValue ) {
00171                                                 setFormat( subStart, tagEnd - subStart, d->valueFormat );
00172                                         }
00173         
00174                                 }
00175                                 else {
00176                                         setFormat( 0,text.length(), d->normalFormat );
00177                                 }
00178         
00179                                 tagStart = tagEnd; //skip tag body
00180                         }
00181                         else {
00182                                 tagStart++; //don't enter endless loop on last found '<'
00183                         }
00184                 }
00185         }
00186 }
00187 
00188 void Blokkal::Ui::HtmlTextHighlighter::slotAppearanceSettingsChanged( void )
00189 {
00190         d->highlightSyntax = AppearanceSettings::self()->highlightSyntax();
00191         d->highlightSpelling = AppearanceSettings::self()->checkSpelling();
00192 
00193         d->normalFormat.setFont( KGlobalSettings::generalFont() );
00194         d->normalFormat.setForeground( d->edit->palette().text() );
00195 
00196         QFont font = KGlobalSettings::generalFont();
00197         font.setBold( AppearanceSettings::self()->highlightTagBold() );
00198         font.setItalic( AppearanceSettings::self()->highlightTagItalic() );
00199         d->tagFormat.setFont( font );
00200         d->tagFormat.setForeground( AppearanceSettings::self()->highlightTagColor() );
00201 
00202         font = KGlobalSettings::generalFont();
00203         font.setBold( AppearanceSettings::self()->highlightStringBold() );
00204         font.setItalic( AppearanceSettings::self()->highlightStringItalic() );
00205         d->stringFormat.setFont( font );
00206         d->stringFormat.setForeground( AppearanceSettings::self()->highlightStringColor() );
00207 
00208         font = KGlobalSettings::generalFont();
00209         font.setBold( AppearanceSettings::self()->highlightValueBold() );
00210         font.setItalic( AppearanceSettings::self()->highlightValueItalic() );
00211         d->valueFormat.setFont( font );
00212         d->valueFormat.setForeground( AppearanceSettings::self()->highlightValueColor() );
00213 
00214         rehighlight();
00215 }
00216 
00217 //-----------------
00218 //HtmlEntryTextEdit
00219 //-----------------
00220 class Blokkal::Ui::HtmlEntryTextEdit::Private
00221 {
00222 public:
00223         Private( void ) :
00224         highlighter( 0 ),
00225         spellCheckAction ( 0 ),
00226         proxy( 0 )
00227         {}
00228 
00229         HtmlTextHighlighter * highlighter;
00230 
00231         QAction * spellCheckAction;
00232 
00233         DropProxy * proxy;
00234 
00235         QTextBlock currentTextBlock;
00236         QString blockText;
00237 };
00238 
00239 Blokkal::Ui::HtmlEntryTextEdit::HtmlEntryTextEdit( QWidget * parent ):
00240 KTextEdit( parent ),
00241 d( new Private() )
00242 {
00243         setAcceptRichText( FALSE );
00244         setTabChangesFocus( TRUE );
00245         d->highlighter = new HtmlTextHighlighter( this );
00246         d->currentTextBlock = textCursor().block();
00247         d->blockText = d->currentTextBlock.text();
00248         connect( this,
00249                  SIGNAL( textChanged( void ) ),
00250                  SLOT( checkCurrentBlock( void ) ) );
00251 }
00252 
00253 Blokkal::Ui::HtmlEntryTextEdit::~HtmlEntryTextEdit( void )
00254 {
00255         delete d;
00256 }
00257 
00258 void Blokkal::Ui::HtmlEntryTextEdit::insertTextAtCursor( const QString & text )
00259 {
00260         QTextCursor cursor( textCursor() );
00261         const int position = cursor.position();
00262         cursor.insertText( text );
00263         cursor.setPosition( position + text.length() );
00264         setTextCursor( cursor );
00265 }
00266 
00267 void Blokkal::Ui::HtmlEntryTextEdit::frameSelection( const QString & front, const QString & tail )
00268 {
00269         QTextCursor cursor( textCursor() );
00270         if( cursor.hasSelection() && !cursor.hasComplexSelection() ) {
00271                 const int anchor = cursor.anchor();
00272                 const int selectionEnd = cursor.selectionEnd();
00273                 cursor.clearSelection();
00274                 if( anchor < selectionEnd ) {
00275                         cursor.insertText( tail );
00276                         cursor.setPosition( anchor );
00277                         cursor.insertText( front );
00278                 }
00279                 else {
00280                         cursor.insertText( front );
00281                         cursor.setPosition( selectionEnd + front.length() );
00282                         cursor.insertText( tail );
00283                 }
00284                 
00285                 cursor.setPosition( selectionEnd + front.length() );
00286                 setTextCursor( cursor );
00287         }
00288         else if( !cursor.hasSelection() ) {
00289                 cursor.insertText( front );
00290                 int position = cursor.position();
00291                 cursor.insertText( tail );
00292                 cursor.setPosition( position );
00293                 setTextCursor( cursor );
00294         }
00295 }
00296 
00297 void Blokkal::Ui::HtmlEntryTextEdit::dropEvent( QDropEvent * event )
00298 {
00299         if( isReadOnly() ) {
00300                 KTextEdit::dropEvent( event );
00301                 return;
00302         }
00303 
00304         if( d->proxy && d->proxy->handleDrop( event, this ) ) {
00305                 return;
00306         }
00307 
00308         //fall back to KTextEdit drag'n'drop
00309         KTextEdit::dropEvent( event );
00310 }
00311 
00312 void Blokkal::Ui::HtmlEntryTextEdit::checkCurrentBlock( void )
00313 {
00314         if( d->currentTextBlock == textCursor().block() ) {
00315                 if( d->blockText != d->currentTextBlock.text() ) {
00316                         d->blockText = d->currentTextBlock.text();
00317                         emit plainTextChanged();
00318                 }
00319         }
00320         else {
00321                 d->currentTextBlock = textCursor().block();
00322                 d->blockText = d->currentTextBlock.text();
00323                 emit plainTextChanged();
00324         }
00325 }
00326 
00327 void Blokkal::Ui::HtmlEntryTextEdit::contextMenuEvent( QContextMenuEvent * event )
00328 {
00329         //code borrowed from KTextEdit
00330         QMenu *popup = createStandardContextMenu();
00331 
00332         connect( popup,
00333                  SIGNAL( triggered ( QAction* ) ),
00334                  SLOT( menuActivated( QAction* ) ) ); //own version, just starts the spellcheck dialog
00335 
00336         if( !isReadOnly() ) {
00337                 QList<QAction *> actionList = popup->actions();
00338                 enum { UndoAct, RedoAct, CutAct, CopyAct, PasteAct, ClearAct, SelectAllAct, NCountActs };
00339                 QAction * separatorAction = 0L;
00340                 int idx = actionList.indexOf( actionList[SelectAllAct] ) + 1;
00341                 if ( idx < actionList.count() ) {
00342                         separatorAction = actionList.at( idx );
00343                 }
00344                 if ( separatorAction ) {
00345                         KAction * clearAllAction = KStandardAction::clear( this, SLOT( clear() ), this );
00346                         if ( toPlainText().isEmpty() ) {
00347                                 clearAllAction->setEnabled( FALSE );
00348                         }
00349                         popup->insertAction( separatorAction, clearAllAction );
00350                 }
00351         }
00352  
00353         KIconTheme::assignIconsToContextMenu( isReadOnly() ? KIconTheme::ReadOnlyText : KIconTheme::TextEditor,
00354                                               popup->actions() );
00355 
00356         if( !isReadOnly() ) {
00357                 popup->addSeparator();
00358                 if( !acceptRichText() ) {
00359                         d->spellCheckAction = popup->addAction( KIcon( "tools-check-spelling" ), i18n( "Check Spelling..." ) );
00360                         if ( document()->isEmpty() ) {
00361                                  d->spellCheckAction->setEnabled( FALSE );
00362                         }
00363                 }
00364         }
00365         popup->exec( event->globalPos() );
00366  
00367    delete popup;
00368 }
00369 
00370 void Blokkal::Ui::HtmlEntryTextEdit::menuActivated( QAction * action )
00371 {
00372         if( action == d->spellCheckAction ) {
00373                 checkSpelling();
00374         }
00375 }
00376 
00377 void Blokkal::Ui::HtmlEntryTextEdit::setDropProxy( Blokkal::Ui::DropProxy * proxy )
00378 {
00379         d->proxy = proxy;
00380 }
00381 
00382 //---------
00383 //DropProxy
00384 //---------
00385 Blokkal::Ui::DropProxy::~DropProxy( void )
00386 {}