/* This file is part of the KDE libraries Copyright (C) 1997 Bernd Johannes Wuebben Copyright (C) 2000 Waldo Bastian This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // remove when Q3PopupMenu is ported #ifndef QT3_SUPPORT #define QT3_SUPPORT #endif #ifndef QT3_SUPPORT_WARNINGS #define QT3_SUPPORT_WARNINGS #endif #include // INT_MAX #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "keditcl.h" ////////////////////////////////////////////////////////////////////////// // // Find Methods // void KEdit::search(){ if( replace_dialog && replace_dialog->isVisible() ) { replace_dialog->hide(); } if( !srchdialog ) { srchdialog = new KEdFind( this, false); srchdialog->setObjectName(QLatin1String("searchdialog")); connect(srchdialog,SIGNAL(search()),this,SLOT(search_slot())); connect(srchdialog,SIGNAL(done()),this,SLOT(searchdone_slot())); } // If we already searched / replaced something before make sure it shows // up in the find dialog line-edit. QString string; string = srchdialog->getText(); srchdialog->setText(string.isEmpty() ? pattern : string); deselect(); last_search = NONE; srchdialog->show(); srchdialog->result(); } void KEdit::search_slot(){ int line, col; if (!srchdialog) return; QString to_find_string = srchdialog->getText(); getCursorPosition(&line,&col); // srchdialog->get_direction() is true if searching backward if (last_search != NONE && srchdialog->get_direction()){ col = col - pattern.length() - 1 ; } again: int result = doSearch(to_find_string, srchdialog->case_sensitive(), false, (!srchdialog->get_direction()),line,col); if(!result){ if(!srchdialog->get_direction()){ // forward search int query = KMessageBox::questionYesNo( srchdialog, i18n("End of document reached.\n"\ "Continue from the beginning?"), i18n("Find"),KStandardGuiItem::cont(),KStandardGuiItem::stop()); if (query == KMessageBox::Yes){ line = 0; col = 0; goto again; } } else{ //backward search int query = KMessageBox::questionYesNo( srchdialog, i18n("Beginning of document reached.\n"\ "Continue from the end?"), i18n("Find"),KStandardGuiItem::cont(),KStandardGuiItem::stop()); if (query == KMessageBox::Yes){ QString string = textLine( numLines() - 1 ); line = numLines() - 1; col = string.length(); last_search = BACKWARD; goto again; } } } else{ emit CursorPositionChanged(); } } void KEdit::searchdone_slot(){ if (!srchdialog) return; srchdialog->hide(); setFocus(); last_search = NONE; } int KEdit::doSearch(const QString& s_pattern, Qt::CaseSensitivity case_sensitive, bool wildcard, bool forward, int line, int col){ (void) wildcard; // reserved for possible extension to regex int i, length; int pos = -1; if(forward){ QString string; for(i = line; i < numLines(); i++) { string = textLine(i); pos = string.indexOf(s_pattern, i == line ? col : 0, case_sensitive); if( pos != -1){ length = s_pattern.length(); setCursorPosition(i,pos,false); for(int l = 0 ; l < length; l++){ cursorRight(true); } setCursorPosition( i , pos + length, true ); pattern = s_pattern; last_search = FORWARD; return 1; } } } else{ // searching backwards QString string; for(i = line; i >= 0; i--) { string = textLine(i); int line_length = string.length(); pos = string.lastIndexOf(s_pattern, line == i ? col : line_length , case_sensitive); if (pos != -1){ length = s_pattern.length(); if( ! (line == i && pos > col ) ){ setCursorPosition(i ,pos ,false ); for(int l = 0 ; l < length; l++){ cursorRight(true); } setCursorPosition(i ,pos + length ,true ); pattern = s_pattern; last_search = BACKWARD; return 1; } } } } return 0; } bool KEdit::repeatSearch() { if(!srchdialog || pattern.isEmpty()) { search(); return true; } search_slot(); setFocus(); return true; } ////////////////////////////////////////////////////////////////////////// // // Replace Methods // void KEdit::replace() { if( srchdialog && srchdialog->isVisible() ) { srchdialog->hide(); } if( !replace_dialog ) { replace_dialog = new KEdReplace( this, false ); replace_dialog->setObjectName(QLatin1String("replace_dialog")); connect(replace_dialog,SIGNAL(find()),this,SLOT(replace_search_slot())); connect(replace_dialog,SIGNAL(replace()),this,SLOT(replace_slot())); connect(replace_dialog,SIGNAL(replaceAll()),this,SLOT(replace_all_slot())); connect(replace_dialog,SIGNAL(done()),this,SLOT(replacedone_slot())); } QString string = replace_dialog->getText(); replace_dialog->setText(string.isEmpty() ? pattern : string); deselect(); last_replace = NONE; replace_dialog->show(); replace_dialog->result(); } void KEdit::replace_slot(){ if (!replace_dialog) return; if(!can_replace){ KNotification::beep(); return; } int line,col, length; QString string = replace_dialog->getReplaceText(); length = string.length(); cut(); getCursorPosition(&line,&col); insertAt(string,line,col); setModified(true); can_replace = false; if (replace_dialog->get_direction()) { // Backward setCursorPosition(line,col+length); for( int k = 0; k < length; k++){ cursorLeft(true); } } else { // Forward setCursorPosition(line,col); for( int k = 0; k < length; k++){ cursorRight(true); } } } void KEdit::replace_all_slot(){ if (!replace_dialog) return; QString to_find_string = replace_dialog->getText(); int lineFrom, lineTo, colFrom, colTo; getSelection(&lineFrom, &colFrom, &lineTo, &colTo); // replace_dialog->get_direction() is true if searching backward if (replace_dialog->get_direction()) { if (colTo != -1) { replace_all_col = colTo - to_find_string.length(); replace_all_line = lineTo; } else { getCursorPosition(&replace_all_line,&replace_all_col); replace_all_col--; } } else { if (colFrom != -1) { replace_all_col = colFrom; replace_all_line = lineFrom; } else { getCursorPosition(&replace_all_line,&replace_all_col); } } deselect(); again: setAutoUpdate(false); int result = 1; while(result){ result = doReplace(to_find_string, replace_dialog->case_sensitive(), false, (!replace_dialog->get_direction()), replace_all_line,replace_all_col,true); } setAutoUpdate(true); update(); if(!replace_dialog->get_direction()){ // forward search int query = KMessageBox::questionYesNo( srchdialog, i18n("End of document reached.\n"\ "Continue from the beginning?"), i18n("Find"),KStandardGuiItem::cont(),KStandardGuiItem::stop()); if (query == KMessageBox::Yes){ replace_all_line = 0; replace_all_col = 0; goto again; } } else{ //backward search int query = KMessageBox::questionYesNo( srchdialog, i18n("Beginning of document reached.\n"\ "Continue from the end?"), i18n("Find"),KStandardGuiItem::cont(),KStandardGuiItem::stop()); if (query == KMessageBox::Yes){ QString string = textLine( numLines() - 1 ); replace_all_line = numLines() - 1; replace_all_col = string.length(); last_replace = BACKWARD; goto again; } } emit CursorPositionChanged(); } void KEdit::replace_search_slot(){ int line, col; if (!replace_dialog) return; QString to_find_string = replace_dialog->getText(); int lineFrom, lineTo, colFrom, colTo; getSelection(&lineFrom, &colFrom, &lineTo, &colTo); // replace_dialog->get_direction() is true if searching backward if (replace_dialog->get_direction()) { if (colFrom != -1) { col = colFrom - to_find_string.length(); line = lineFrom; } else { getCursorPosition(&line,&col); col--; } } else { if (colTo != -1) { col = colTo; line = lineTo; } else { getCursorPosition(&line,&col); } } again: int result = doReplace(to_find_string, replace_dialog->case_sensitive(), false, (!replace_dialog->get_direction()), line, col, false ); if(!result){ if(!replace_dialog->get_direction()){ // forward search int query = KMessageBox::questionYesNo( replace_dialog, i18n("End of document reached.\n"\ "Continue from the beginning?"), i18n("Replace"),KStandardGuiItem::cont(),KStandardGuiItem::stop()); if (query == KMessageBox::Yes){ line = 0; col = 0; goto again; } } else{ //backward search int query = KMessageBox::questionYesNo( replace_dialog, i18n("Beginning of document reached.\n"\ "Continue from the end?"), i18n("Replace"),KStandardGuiItem::cont(),KStandardGuiItem::stop()); if (query == KMessageBox::Yes){ QString string = textLine( numLines() - 1 ); line = numLines() - 1; col = string.length(); last_replace = BACKWARD; goto again; } } } else{ emit CursorPositionChanged(); } } void KEdit::replacedone_slot(){ if (!replace_dialog) return; replace_dialog->hide(); // replace_dialog->clearFocus(); setFocus(); last_replace = NONE; can_replace = false; } int KEdit::doReplace(const QString &s_pattern, Qt::CaseSensitivity case_sensitive, bool wildcard, bool forward, int line, int col, bool replace_all){ (void) wildcard; // reserved for possible extension to regex int line_counter, length; int pos = -1; QString string; QString stringnew; QString replacement; replacement = replace_dialog->getReplaceText(); line_counter = line; replace_all_col = col; if(forward){ int num_lines = numLines(); while (line_counter < num_lines){ string = textLine(line_counter); if (replace_all){ pos = string.indexOf(s_pattern, replace_all_col, case_sensitive); } else{ pos = string.indexOf(s_pattern, line_counter == line ? col : 0, case_sensitive); } if (pos == -1 ){ line_counter++; replace_all_col = 0; replace_all_line = line_counter; } if( pos != -1){ length = s_pattern.length(); if(replace_all){ // automatic stringnew = string; do { stringnew.replace(pos,length,replacement); replace_all_col = pos + replacement.length(); replace_all_line = line_counter; pos = stringnew.indexOf(s_pattern, replace_all_col, case_sensitive); } while( pos != -1); removeLine(line_counter); insertLine(stringnew,line_counter); setModified(true); } else{ // interactive setCursorPosition( line_counter , pos, false ); for(int l = 0 ; l < length; l++){ cursorRight(true); } setCursorPosition( line_counter , pos + length, true ); pattern = s_pattern; last_replace = FORWARD; can_replace = true; return 1; } } } } else{ // searching backwards while(line_counter >= 0){ string = textLine(line_counter); int line_length = string.length(); if( replace_all ){ if (replace_all_col < 0) pos = -1; else pos = string.lastIndexOf(s_pattern, replace_all_col , case_sensitive); } else{ if ((line == line_counter) && (col < 0)) pos = -1; else pos = string.lastIndexOf(s_pattern, line == line_counter ? col : line_length , case_sensitive); } if (pos == -1 ){ line_counter--; replace_all_col = 0; if(line_counter >= 0){ string = textLine(line_counter); replace_all_col = string.length(); } replace_all_line = line_counter; } if (pos != -1){ length = s_pattern.length(); if(replace_all){ // automatic stringnew = string; stringnew.replace(pos,length,replacement); removeLine(line_counter); insertLine(stringnew,line_counter); replace_all_col = pos-length; replace_all_line = line_counter; if (replace_all_col < 0) { line_counter--; if(line_counter >= 0){ string = textLine(line_counter); replace_all_col = string.length(); } replace_all_line = line_counter; } setModified(true); } else{ // interactive // printf("line_counter %d pos %d col %d\n",line_counter, pos,col); if( ! (line == line_counter && pos > col ) ){ setCursorPosition(line_counter, pos + length ,false ); for(int l = 0 ; l < length; l++){ cursorLeft(true); } setCursorPosition(line_counter, pos ,true ); pattern = s_pattern; last_replace = BACKWARD; can_replace = true; return 1; } } } } } return 0; } //////////////////////////////////////////////////////////////////// // // Find Dialog // class KEdFind::KEdFindPrivate { public: KEdFindPrivate( QWidget *parent ) { combo = new KHistoryComboBox( parent ); combo->setMaxCount( 20 ); // just some default } ~KEdFindPrivate() { delete combo; } KHistoryComboBox *combo; }; KEdFind::KEdFind( QWidget *parent, bool modal ) : KDialog( parent ) { // is this really needed at all ? // setWFlags( Qt::WType_TopLevel ); setCaption(i18n("Find")); setButtons( modal ? User1|Cancel : User1|Close ); setButtonGuiItem( User1, KStandardGuiItem::find() ); setModal(modal); showButtonSeparator(false); setDefaultButton(User1); QWidget *page = new QWidget( this ); setMainWidget(page); QVBoxLayout *topLayout = new QVBoxLayout( page ); topLayout->setMargin( 0 ); topLayout->setSpacing( spacingHint() ); d = new KEdFindPrivate( page ); QString text = i18n("Find:"); QLabel *label = new QLabel( text, page ); label->setObjectName( QLatin1String( "find" ) ); topLayout->addWidget( label ); d->combo->setMinimumWidth(fontMetrics().maxWidth()*20); d->combo->setFocus(); connect(d->combo, SIGNAL(textChanged ( const QString & )), this,SLOT(textSearchChanged ( const QString & ))); topLayout->addWidget(d->combo); QGroupBox *group = new QGroupBox( i18n("Options"), page ); topLayout->addWidget( group ); QGridLayout *gbox = new QGridLayout( group ); gbox->setMargin(spacingHint()); gbox->addItem(new QSpacerItem(0,fontMetrics().lineSpacing()), 0, 0); text = i18n("Case &sensitive"); sensitive = new QCheckBox( text, group ); sensitive->setObjectName( QLatin1String( "case") ); text = i18n("Find &backwards"); direction = new QCheckBox( text, group ); direction->setObjectName( QLatin1String( "direction" ) ); gbox->addWidget( sensitive, 1, 0 ); gbox->addWidget( direction, 1, 1 ); gbox->setRowStretch( 2, 10 ); enableButton( KDialog::User1, !d->combo->currentText().isEmpty() ); connect( this, SIGNAL( user1Clicked() ), this, SLOT( slotUser1() ) ); connect( this, SIGNAL( cancelClicked() ), this, SIGNAL( done() ) ); connect( this, SIGNAL( closeClicked() ), this, SIGNAL( done() ) ); } KEdFind::~KEdFind() { delete d; } void KEdFind::textSearchChanged ( const QString &text ) { enableButton( KDialog::User1, !text.isEmpty() ); } void KEdFind::slotUser1( void ) { if( !d->combo->currentText().isEmpty() ) { d->combo->addToHistory( d->combo->currentText() ); emit search(); } } QString KEdFind::getText() const { return d->combo->currentText(); } void KEdFind::setText(const QString &string) { d->combo->setEditText(string); d->combo->lineEdit()->selectAll(); } void KEdFind::setCaseSensitive( Qt::CaseSensitivity b ) { sensitive->setChecked( b == Qt::CaseSensitive ); } Qt::CaseSensitivity KEdFind::case_sensitive() const { return sensitive->isChecked() ? Qt::CaseSensitive : Qt::CaseInsensitive; } void KEdFind::setDirection( bool b ) { direction->setChecked( b ); } bool KEdFind::get_direction() const { return direction->isChecked(); } KHistoryComboBox * KEdFind::searchCombo() const { return d->combo; } //////////////////////////////////////////////////////////////////// // // Replace Dialog // class KEdReplace::KEdReplacePrivate { public: KEdReplacePrivate( QWidget *parent ) { searchCombo = new KHistoryComboBox( parent ); replaceCombo = new KHistoryComboBox( parent ); searchCombo->setMaxCount( 20 ); // just some defaults replaceCombo->setMaxCount( 20 ); } ~KEdReplacePrivate() { delete searchCombo; delete replaceCombo; } KHistoryComboBox *searchCombo, *replaceCombo; }; KEdReplace::KEdReplace( QWidget *parent, bool modal ) : KDialog( parent ) { // is this really needed at all ? // setWFlags( Qt::WType_TopLevel ); setCaption(i18n("Replace")); setButtons( modal ? User3|User2|User1|Cancel : User3|User2|User1|Close ); setButtonGuiItem( User1, KGuiItem(i18n("Replace &All")) ); setButtonGuiItem( User2, KGuiItem(i18n("&Replace")) ); setButtonGuiItem( User3, KStandardGuiItem::find() ); setModal(modal); setDefaultButton(User3); setButtonsOrientation( Qt::Vertical ); showButtonSeparator(false); QWidget *page = new QWidget(this); setMainWidget(page); QVBoxLayout *topLayout = new QVBoxLayout( page ); topLayout->setMargin( 0 ); topLayout->setSpacing( spacingHint() ); d = new KEdReplacePrivate( page ); QString text = i18n("Find:"); QLabel *label = new QLabel( text, page ); label->setObjectName( QLatin1String( "find" ) ); topLayout->addWidget( label ); d->searchCombo->setMinimumWidth(fontMetrics().maxWidth()*20); d->searchCombo->setFocus(); topLayout->addWidget(d->searchCombo); text = i18n("Replace with:"); label = new QLabel( text, page ); label->setObjectName( QLatin1String( "replace" ) ); topLayout->addWidget( label ); d->replaceCombo->setMinimumWidth(fontMetrics().maxWidth()*20); topLayout->addWidget(d->replaceCombo); connect(d->searchCombo, SIGNAL(textChanged ( const QString & )), this,SLOT(textSearchChanged ( const QString & ))); QGroupBox *group = new QGroupBox( i18n("Options"), page ); topLayout->addWidget( group ); QGridLayout *gbox = new QGridLayout( group ); gbox->setMargin(spacingHint()); gbox->addItem(new QSpacerItem(0,fontMetrics().lineSpacing()), 0, 0); text = i18n("Case &sensitive"); sensitive = new QCheckBox( text, group ); sensitive->setObjectName( QLatin1String( "case") ); text = i18n("Find &backwards"); direction = new QCheckBox( text, group ); direction->setObjectName( QLatin1String( "direction" ) ); gbox->addWidget( sensitive, 1, 0 ); gbox->addWidget( direction, 1, 1 ); gbox->setRowStretch( 2, 10 ); connect( this, SIGNAL( user1Clicked() ), this, SLOT( slotUser1() ) ); connect( this, SIGNAL( user2Clicked() ), this, SLOT( slotUser2() ) ); connect( this, SIGNAL( user3Clicked() ), this, SLOT( slotUser3() ) ); connect( this, SIGNAL( cancelClicked() ), this, SLOT( slotCancel() ) ); connect( this, SIGNAL( closeClicked() ), this, SLOT( slotCancel() ) ); } KEdReplace::~KEdReplace() { delete d; } void KEdReplace::textSearchChanged ( const QString &text ) { bool state=text.isEmpty(); enableButton( KDialog::User1, !state ); enableButton( KDialog::User2, !state ); enableButton( KDialog::User3, !state ); } void KEdReplace::slotCancel( void ) { emit done(); d->searchCombo->clearEditText(); d->replaceCombo->clearEditText(); } void KEdReplace::slotUser1( void ) { if( !d->searchCombo->currentText().isEmpty() ) { d->replaceCombo->addToHistory( d->replaceCombo->currentText() ); emit replaceAll(); } } void KEdReplace::slotUser2( void ) { if( !d->searchCombo->currentText().isEmpty() ) { d->replaceCombo->addToHistory( d->replaceCombo->currentText() ); emit replace(); } } void KEdReplace::slotUser3( void ) { if( !d->searchCombo->currentText().isEmpty() ) { d->searchCombo->addToHistory( d->searchCombo->currentText() ); emit find(); } } QString KEdReplace::getText() { return d->searchCombo->currentText(); } QString KEdReplace::getReplaceText() { return d->replaceCombo->currentText(); } void KEdReplace::setText(const QString &string) { d->searchCombo->setEditText(string); d->searchCombo->lineEdit()->selectAll(); } Qt::CaseSensitivity KEdReplace::case_sensitive() { return sensitive->isChecked() ? Qt::CaseSensitive: Qt::CaseInsensitive; } bool KEdReplace::get_direction() { return direction->isChecked(); } KHistoryComboBox * KEdReplace::searchCombo() const { return d->searchCombo; } KHistoryComboBox * KEdReplace::replaceCombo() const { return d->replaceCombo; } KEdGotoLine::KEdGotoLine( QWidget *parent, bool modal ) : KDialog( parent ) { setCaption( i18n("Go to Line") ); setButtons( modal ? Ok|Cancel : Ok|Close ); setModal(modal); setDefaultButton(User3); showButtonSeparator(false); QWidget *page = new QWidget( this ); setMainWidget(page); QVBoxLayout *topLayout = new QVBoxLayout( page ); topLayout->setMargin( 0 ); topLayout->setSpacing( spacingHint() ); lineNum = new KIntNumInput( 1,page); lineNum->setRange(1, 1000000, 1, false); lineNum->setLabel(i18n("Go to line:"), Qt::AlignVCenter | Qt::AlignLeft); // lineNum->setMinimumWidth(fontMetrics().maxWidth()*20); topLayout->addWidget( lineNum ); topLayout->addStretch(10); lineNum->setFocus(); } void KEdGotoLine::selected(int) { accept(); } int KEdGotoLine::getLineNumber() { return lineNum->value(); } ////////////////////////////////////////////////////////////////////////////// // // Spell Checking // void KEdit::spellcheck_start() { saved_readonlystate = isReadOnly(); setReadOnly(true); } void KEdit::misspelling (const QString &word, const QStringList &, unsigned int pos) { unsigned int l = 0; unsigned int cnt = 0; posToRowCol (pos, l, cnt); setSelection(l, cnt, l, cnt+word.length()); /* if (cursorPoint().y()>height()/2) kspell->moveDlg (10, height()/2-kspell->heightDlg()-15); else kspell->moveDlg (10, height()/2 + 15); */ } //need to use pos for insert, not cur, so forget cur altogether void KEdit::corrected (const QString &originalword, const QString &newword, unsigned int pos) { //we'll reselect the original word in case the user has played with //the selection in eframe or the word was auto-replaced unsigned int l = 0; unsigned int cnt = 0; if( newword != originalword ) { posToRowCol (pos, l, cnt); setSelection(l, cnt, l, cnt+originalword.length()); setReadOnly ( false ); removeSelectedText(); insert(newword); setReadOnly ( true ); } else { deselect(); } } void KEdit::posToRowCol(unsigned int pos, unsigned int &line, unsigned int &col) { for (line = 0; line < static_cast(numLines()) && col <= pos; line++) { col += lineLength(line)+1; } line--; col = pos - col + lineLength(line) + 1; } void KEdit::spellcheck_stop() { deselect(); setReadOnly ( saved_readonlystate); } QString KEdit::selectWordUnderCursor( ) { int parag; int pos; getCursorPosition(¶g, &pos); QString txt = text(parag); // Find start int start = pos; while( start > 0 ) { const QChar &ch = txt[start-1]; if (ch.isSpace() || ch.isPunct()) break; start--; } // Find end int end = pos; int len = txt.length(); while( end < len ) { const QChar &ch = txt[end]; if (ch.isSpace() || ch.isPunct()) break; end++; } setSelection(parag, start, parag, end); return txt.mid(start, end-start); } Q3PopupMenu *KEdit::createPopupMenu( const QPoint& pos ) { Q3PopupMenu *menu = Q3MultiLineEdit::createPopupMenu( pos ); QList lstAction = menu->actions(); if ( !lstAction.isEmpty() ) { enum { UndoAct = 0, RedoAct, CutAct, CopyAct, PasteAct, ClearAct, SelectAllAct, NCountActs }; if ( isReadOnly() ) lstAction[0]->setIcon( KIcon("edit-copy") ); else { lstAction[UndoAct]->setIcon( KIcon("edit-undo") ); lstAction[RedoAct]->setIcon( KIcon("edit-redo") ); lstAction[CutAct]->setIcon( KIcon("edit-cut") ); lstAction[CopyAct]->setIcon( KIcon("edit-copy") ); lstAction[PasteAct]->setIcon( KIcon("edit-paste") ); lstAction[ClearAct]->setIcon( KIcon("edit-clear") ); } } return menu; }