00001 
00002 
00003 
00004 
00005 
00006 
00007 
00008 
00009 
00010 
00011 
00012 
00013 
00014 
00015 
00016 
00017 
00018 
00019 
00020 
00021 
00022 
00023 
00024 
00025 
00026 
00027 
00028 
00029 #include "docwordcompletion.h"
00030 
00031 #include <ktexteditor/document.h>
00032 #include <ktexteditor/viewcursorinterface.h>
00033 #include <ktexteditor/editinterface.h>
00034 #include <ktexteditor/variableinterface.h>
00035 
00036 #include <kapplication.h>
00037 #include <kconfig.h>
00038 #include <kdialog.h>
00039 #include <kgenericfactory.h>
00040 #include <klocale.h>
00041 #include <kaction.h>
00042 #include <knotifyclient.h>
00043 #include <kparts/part.h>
00044 #include <kiconloader.h>
00045 
00046 #include <qregexp.h>
00047 #include <qstring.h>
00048 #include <qdict.h>
00049 #include <qspinbox.h>
00050 #include <qlabel.h>
00051 #include <qlayout.h>
00052 #include <qhbox.h>
00053 #include <qwhatsthis.h>
00054 #include <qcheckbox.h>
00055 
00056 
00057 
00058 
00059 
00060 K_EXPORT_COMPONENT_FACTORY( ktexteditor_docwordcompletion, KGenericFactory<DocWordCompletionPlugin>( "ktexteditor_docwordcompletion" ) )
00061 DocWordCompletionPlugin::DocWordCompletionPlugin( QObject *parent,
00062                             const char* name,
00063                             const QStringList&  )
00064     : KTextEditor::Plugin ( (KTextEditor::Document*) parent, name )
00065 {
00066   readConfig();
00067 }
00068 
00069 void DocWordCompletionPlugin::readConfig()
00070 {
00071   KConfig *config = kapp->config();
00072   config->setGroup( "DocWordCompletion Plugin" );
00073   m_treshold = config->readNumEntry( "treshold", 3 );
00074   m_autopopup = config->readBoolEntry( "autopopup", true );
00075 }
00076 
00077 void DocWordCompletionPlugin::writeConfig()
00078 {
00079   KConfig *config = kapp->config();
00080   config->setGroup("DocWordCompletion Plugin");
00081   config->writeEntry("autopopup", m_autopopup );
00082   config->writeEntry("treshold", m_treshold );
00083 }
00084 
00085 void DocWordCompletionPlugin::addView(KTextEditor::View *view)
00086 {
00087   DocWordCompletionPluginView *nview = new DocWordCompletionPluginView (m_treshold, m_autopopup, view, "Document word completion");
00088   m_views.append (nview);
00089 }
00090 
00091 void DocWordCompletionPlugin::removeView(KTextEditor::View *view)
00092 {
00093   for (uint z=0; z < m_views.count(); z++)
00094     if (m_views.at(z)->parentClient() == view)
00095     {
00096        DocWordCompletionPluginView *nview = m_views.at(z);
00097        m_views.remove (nview);
00098        delete nview;
00099     }
00100 }
00101 
00102 KTextEditor::ConfigPage* DocWordCompletionPlugin::configPage( uint, QWidget *parent, const char *name )
00103 {
00104   return new DocWordCompletionConfigPage( this, parent, name );
00105 }
00106 
00107 QString DocWordCompletionPlugin::configPageName( uint ) const
00108 {
00109   return i18n("Word Completion Plugin");
00110 }
00111 
00112 QString DocWordCompletionPlugin::configPageFullName( uint ) const
00113 {
00114   return i18n("Configure the Word Completion Plugin");
00115 }
00116 
00117 
00118        QPixmap DocWordCompletionPlugin::configPagePixmap( uint, int size ) const
00119 {
00120   return UserIcon( "kte_wordcompletion", size );
00121 }
00122 
00123 
00124 
00125 struct DocWordCompletionPluginViewPrivate
00126 {
00127   uint line, col;       
00128   uint cline, ccol;     
00129   uint lilen;           
00130   QString last;         
00131   QString lastIns;      
00132   QRegExp re;           
00133   KToggleAction *autopopup; 
00134   uint treshold;        
00135   int directionalPos;   
00136 };
00137 
00138 DocWordCompletionPluginView::DocWordCompletionPluginView( uint treshold, bool autopopup, KTextEditor::View *view, const char *name )
00139   : QObject( view, name ),
00140     KXMLGUIClient( view ),
00141     m_view( view ),
00142     d( new DocWordCompletionPluginViewPrivate )
00143 {
00144   d->treshold = treshold;
00145   view->insertChildClient( this );
00146   setInstance( KGenericFactory<DocWordCompletionPlugin>::instance() );
00147 
00148   (void) new KAction( i18n("Reuse Word Above"), CTRL+Key_8, this,
00149     SLOT(completeBackwards()), actionCollection(), "doccomplete_bw" );
00150   (void) new KAction( i18n("Reuse Word Below"), CTRL+Key_9, this,
00151     SLOT(completeForwards()), actionCollection(), "doccomplete_fw" );
00152   (void) new KAction( i18n("Pop Up Completion List"), 0, this,
00153     SLOT(popupCompletionList()), actionCollection(), "doccomplete_pu" );
00154   (void) new KAction( i18n("Shell Completion"), 0, this,
00155     SLOT(shellComplete()), actionCollection(), "doccomplete_sh" );
00156   d->autopopup = new KToggleAction( i18n("Automatic Completion Popup"), 0, this,
00157     SLOT(toggleAutoPopup()), actionCollection(), "enable_autopopup" );
00158 
00159   d->autopopup->setChecked( autopopup );
00160   toggleAutoPopup();
00161 
00162   setXMLFile("docwordcompletionui.rc");
00163 
00164   KTextEditor::VariableInterface *vi = KTextEditor::variableInterface( view->document() );
00165   if ( vi )
00166   {
00167     QString e = vi->variable("wordcompletion-autopopup");
00168     if ( ! e.isEmpty() )
00169       d->autopopup->setEnabled( e == "true" );
00170 
00171     connect( view->document(), SIGNAL(variableChanged(const QString &, const QString &)),
00172              this, SLOT(slotVariableChanged(const QString &, const QString &)) );
00173   }
00174 }
00175 
00176 void DocWordCompletionPluginView::settreshold( uint t )
00177 {
00178   d->treshold = t;
00179 }
00180 
00181 void DocWordCompletionPluginView::completeBackwards()
00182 {
00183   complete( false );
00184 }
00185 
00186 void DocWordCompletionPluginView::completeForwards()
00187 {
00188   complete();
00189 }
00190 
00191 
00192 void DocWordCompletionPluginView::popupCompletionList( QString w )
00193 {
00194   if ( w.isEmpty() )
00195     w = word();
00196   if ( w.isEmpty() )
00197     return;
00198 
00199   KTextEditor::CodeCompletionInterface *cci = codeCompletionInterface( m_view );
00200   cci->showCompletionBox( allMatches( w ), w.length() );
00201 }
00202 
00203 void DocWordCompletionPluginView::toggleAutoPopup()
00204 {
00205   if ( d->autopopup->isChecked() ) {
00206     if ( ! connect( m_view->document(), SIGNAL(charactersInteractivelyInserted(int ,int ,const QString&)),
00207          this, SLOT(autoPopupCompletionList()) ))
00208     {
00209       connect( m_view->document(), SIGNAL(textChanged()), this, SLOT(autoPopupCompletionList()) );
00210     }
00211   } else {
00212     disconnect( m_view->document(), SIGNAL(textChanged()), this, SLOT(autoPopupCompletionList()) );
00213     disconnect( m_view->document(), SIGNAL(charactersInteractivelyInserted(int ,int ,const QString&)),
00214                 this, SLOT(autoPopupCompletionList()) );
00215 
00216   }
00217 }
00218 
00219 
00220 void DocWordCompletionPluginView::autoPopupCompletionList()
00221 {
00222   if ( ! m_view->hasFocus() ) return;
00223   QString w = word();
00224   if ( w.length() >= d->treshold )
00225   {
00226       popupCompletionList( w );
00227   }
00228 }
00229 
00230 
00231 void DocWordCompletionPluginView::shellComplete()
00232 {
00233     
00234   KTextEditor::EditInterface * ei = KTextEditor::editInterface(m_view->document());
00235     
00236   uint cline, ccol;
00237   viewCursorInterface(m_view)->cursorPositionReal(&cline, &ccol);
00238   QString wrd = word();
00239   if (wrd.isEmpty())
00240     return;
00241 
00242   QValueList < KTextEditor::CompletionEntry > matches = allMatches(wrd);
00243   if (matches.size() == 0)
00244     return;
00245   QString partial = findLongestUnique(matches);
00246   if (partial.length() == wrd.length())
00247   {
00248     KTextEditor::CodeCompletionInterface * cci = codeCompletionInterface(m_view);
00249     cci->showCompletionBox(matches, wrd.length());
00250   }
00251   else
00252   {
00253     partial.remove(0, wrd.length());
00254     ei->insertText(cline, ccol, partial);
00255   }
00256 }
00257 
00258 
00259 
00260 void DocWordCompletionPluginView::complete( bool fw )
00261 {
00262   
00263   KTextEditor::EditInterface *ei = KTextEditor::editInterface( m_view->document() );
00264   
00265   uint cline, ccol;
00266   viewCursorInterface( m_view )->cursorPositionReal( &cline, &ccol );
00267   QString wrd = word();
00268   if ( wrd.isEmpty() )
00269     return;
00270 
00271   int inc = fw ? 1 : -1;
00272 
00273   
00274 
00275 
00276 
00277 
00278 
00279   if ( cline == d-> cline &&
00280           ccol - d->lilen == d->ccol &&
00281           wrd.endsWith( d->lastIns ) )
00282   {
00283     
00284 
00285     
00286     if ( ( fw && d->directionalPos == -1 ) ||
00287          ( !fw && d->directionalPos == 1 ) )
00288     {
00289       if ( d->lilen )
00290         ei->removeText( d->cline, d->ccol, d->cline, d->ccol + d->lilen );
00291 
00292       d->lastIns = "";
00293       d->lilen = 0;
00294       d->line = d->cline;
00295       d->col = d->ccol;
00296       d->directionalPos = 0;
00297 
00298       return;
00299     }
00300 
00301     if ( fw )
00302       d->col += d->lilen;
00303 
00304     ccol = d->ccol;
00305     wrd = d->last;
00306 
00307     d->directionalPos += inc;
00308   }
00309   else
00310   {
00311     d->cline = cline;
00312     d->ccol = ccol;
00313     d->last = wrd;
00314     d->lastIns = "";
00315     d->line = cline;
00316     d->col = ccol - wrd.length();
00317     d->lilen = 0;
00318     d->directionalPos = inc;
00319   }
00320 
00321   d->re.setPattern( "\\b" + wrd + "(\\w+)" );
00322   int pos ( 0 );
00323   QString ln = ei->textLine( d->line );
00324 
00325   while ( true )
00326   {
00327     pos = fw ?
00328       d->re.search( ln, d->col ) :
00329       d->re.searchRev( ln, d->col );
00330 
00331     if ( pos > -1 ) 
00332     {
00333       QString m = d->re.cap( 1 );
00334       if ( m != d->lastIns )
00335       {
00336         
00337         if ( d->lilen )
00338           ei->removeText( d->cline, d->ccol, d->cline, d->ccol + d->lilen );
00339         ei->insertText( d->cline, d->ccol, m );
00340 
00341         d->lastIns = m;
00342         d->lilen = m.length();
00343         d->col = pos; 
00344 
00345         return;
00346       }
00347 
00348       
00349       else
00350       {
00351         d->col = pos; 
00352 
00353         if ( fw )
00354           d->col += d->re.matchedLength();
00355 
00356         else
00357         {
00358           if ( pos == 0 )
00359           {
00360             if ( d->line > 0 )
00361             {
00362               d->line += inc;
00363               ln = ei->textLine( d->line );
00364               d->col = ln.length();
00365             }
00366             else
00367             {
00368               KNotifyClient::beep();
00369               return;
00370             }
00371           }
00372 
00373           else
00374             d->col--;
00375         }
00376       }
00377     }
00378 
00379     else  
00380     {
00381       if ( (! fw && d->line == 0 ) || ( fw && d->line >= (uint)ei->numLines() ) )
00382       {
00383         KNotifyClient::beep();
00384         return;
00385       }
00386 
00387       d->line += inc;
00388 
00389       ln = ei->textLine( d->line );
00390       d->col = fw ? 0 : ln.length();
00391     }
00392   } 
00393 }
00394 
00395 
00396 QString DocWordCompletionPluginView::findLongestUnique(const QValueList < KTextEditor::CompletionEntry > &matches)
00397 {
00398   QString partial = matches.front().text;
00399   QValueList < KTextEditor::CompletionEntry >::const_iterator i = matches.begin();
00400   for (++i; i != matches.end(); ++i)
00401   {
00402     if (!(*i).text.startsWith(partial))
00403     {
00404       while(partial.length() > 0)
00405       {
00406         partial.remove(partial.length() - 1, 1);
00407         if ((*i).text.startsWith(partial))
00408         {
00409           break;
00410         }
00411       }
00412       if (partial.length() == 0)
00413         return QString();
00414     }
00415   }
00416 
00417   return partial;
00418 }
00419 
00420 
00421 QString DocWordCompletionPluginView::word()
00422 {
00423   uint cline, ccol;
00424   viewCursorInterface( m_view )->cursorPositionReal( &cline, &ccol );
00425   if ( ! ccol ) return QString::null; 
00426   KTextEditor::EditInterface *ei = KTextEditor::editInterface( m_view->document() );
00427   d->re.setPattern( "\\b(\\w+)$" );
00428   if ( d->re.searchRev(
00429         ei->text( cline, 0, cline, ccol )
00430         ) < 0 )
00431     return QString::null; 
00432   return d->re.cap( 1 );
00433 }
00434 
00435 
00436 
00437 QValueList<KTextEditor::CompletionEntry> DocWordCompletionPluginView::allMatches( const QString &word )
00438 {
00439   QValueList<KTextEditor::CompletionEntry> l;
00440   uint i( 0 );
00441   int pos( 0 );
00442   d->re.setPattern( "\\b("+word+"\\w+)" );
00443   QString s, m;
00444   KTextEditor::EditInterface *ei = KTextEditor::editInterface( m_view->document() );
00445   QDict<int> seen; 
00446   int sawit(1);    
00447   uint cline, ccol;
00448   viewCursorInterface( m_view )->cursorPositionReal( &cline, &ccol );
00449 
00450   while( i < ei->numLines() )
00451   {
00452     s = ei->textLine( i );
00453     pos = 0;
00454     while ( pos >= 0 )
00455     {
00456       pos = d->re.search( s, pos );
00457       if ( pos >= 0 )
00458       {
00459         
00460         if ( i == cline && pos + word.length() == ccol )
00461         {
00462           pos += word.length();
00463           continue;
00464         }
00465 
00466         m = d->re.cap( 1 );
00467         if ( ! seen[ m ] ) {
00468           seen.insert( m, &sawit );
00469           KTextEditor::CompletionEntry e;
00470           e.text = m;
00471           l.append( e );
00472         }
00473         pos += d->re.matchedLength();
00474       }
00475     }
00476     i++;
00477   }
00478   return l;
00479 }
00480 
00481 void DocWordCompletionPluginView::slotVariableChanged( const QString &var, const QString &val )
00482 {
00483   if ( var == "wordcompletion-autopopup" )
00484     d->autopopup->setEnabled( val == "true" );
00485   else if ( var == "wordcompletion-treshold" )
00486     d->treshold = val.toInt();
00487 }
00488 
00489 
00490 
00491 DocWordCompletionConfigPage::DocWordCompletionConfigPage( DocWordCompletionPlugin *completion, QWidget *parent, const char *name )
00492   : KTextEditor::ConfigPage( parent, name )
00493   , m_completion( completion )
00494 {
00495   QVBoxLayout *lo = new QVBoxLayout( this );
00496   lo->setSpacing( KDialog::spacingHint() );
00497 
00498   cbAutoPopup = new QCheckBox( i18n("Automatically &show completion list"), this );
00499   lo->addWidget( cbAutoPopup );
00500 
00501   QHBox *hb = new QHBox( this );
00502   hb->setSpacing( KDialog::spacingHint() );
00503   lo->addWidget( hb );
00504   QLabel *l = new QLabel( i18n(
00505       "Translators: This is the first part of two strings wich will comprise the "
00506       "sentence 'Show completions when a word is at least N characters'. The first "
00507       "part is on the right side of the N, which is represented by a spinbox "
00508       "widget, followed by the second part: 'characters long'. Characters is a "
00509       "ingeger number between and including 1 and 30. Feel free to leave the "
00510       "second part of the sentence blank if it suits your language better. ",
00511       "Show completions &when a word is at least"), hb );
00512   sbAutoPopup = new QSpinBox( 1, 30, 1, hb );
00513   l->setBuddy( sbAutoPopup );
00514   lSbRight = new QLabel( i18n(
00515       "This is the second part of two strings that will comprise teh sentence "
00516       "'Show completions when a word is at least N characters'",
00517       "characters long."), hb );
00518 
00519   QWhatsThis::add( cbAutoPopup, i18n(
00520       "Enable the automatic completion list popup as default. The popup can "
00521       "be disabled on a view basis from the 'Tools' menu.") );
00522   QWhatsThis::add( sbAutoPopup, i18n(
00523       "Define the length a word should have before the completion list "
00524       "is displayed.") );
00525 
00526   cbAutoPopup->setChecked( m_completion->autoPopupEnabled() );
00527   sbAutoPopup->setValue( m_completion->treshold() );
00528 
00529   lo->addStretch();
00530 }
00531 
00532 void DocWordCompletionConfigPage::apply()
00533 {
00534   m_completion->setAutoPopupEnabled( cbAutoPopup->isChecked() );
00535   m_completion->setTreshold( sbAutoPopup->value() );
00536   m_completion->writeConfig();
00537 }
00538 
00539 void DocWordCompletionConfigPage::reset()
00540 {
00541   cbAutoPopup->setChecked( m_completion->autoPopupEnabled() );
00542   sbAutoPopup->setValue( m_completion->treshold() );
00543 }
00544 
00545 void DocWordCompletionConfigPage::defaults()
00546 {
00547   cbAutoPopup->setChecked( true );
00548   sbAutoPopup->setValue( 3 );
00549 }
00550 
00551 
00552 
00553 #include "docwordcompletion.moc"
00554