1 /* This file is part of the KDE libraries
3 Copyright (C) 1997 Bernd Johannes Wuebben <wuebben@math.cornell.edu>
4 Copyright (C) 2000 Waldo Bastian <bastian@kde.org>
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Library General Public
8 License as published by the Free Software Foundation; either
9 version 2 of the License, or (at your option) any later version.
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Library General Public License for more details.
16 You should have received a copy of the GNU Library General Public License
17 along with this library; see the file COPYING.LIB. If not, write to
18 the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor,
19 Boston, MA 02110-1301, USA.
22 #include <limits.h> // INT_MAX
26 #include <qlineedit.h>
27 #include <qvbuttongroup.h>
28 #include <qcheckbox.h>
30 #include <qpushbutton.h>
32 #include <qpopupmenu.h>
34 #include <kapplication.h>
35 #include <kcombobox.h>
36 #include <knuminput.h>
37 #include <kmessagebox.h>
38 #include <knotifyclient.h>
41 #include <kiconloader.h>
46 //////////////////////////////////////////////////////////////////////////
53 if( replace_dialog
&& replace_dialog
->isVisible() )
55 replace_dialog
->hide();
60 srchdialog
= new KEdFind( this, "searchdialog", false);
61 connect(srchdialog
,SIGNAL(search()),this,SLOT(search_slot()));
62 connect(srchdialog
,SIGNAL(done()),this,SLOT(searchdone_slot()));
65 // If we already searched / replaced something before make sure it shows
66 // up in the find dialog line-edit.
69 string
= srchdialog
->getText();
70 srchdialog
->setText(string
.isEmpty() ? pattern
: string
);
80 void KEdit::search_slot(){
87 QString to_find_string
= srchdialog
->getText();
88 getCursorPosition(&line
,&col
);
90 // srchdialog->get_direction() is true if searching backward
92 if (last_search
!= NONE
&& srchdialog
->get_direction()){
93 col
= col
- pattern
.length() - 1 ;
97 int result
= doSearch(to_find_string
, srchdialog
->case_sensitive(),
98 false, (!srchdialog
->get_direction()),line
,col
);
101 if(!srchdialog
->get_direction()){ // forward search
103 int query
= KMessageBox::questionYesNo(
105 i18n("End of document reached.\n"\
106 "Continue from the beginning?"),
107 i18n("Find"),KStdGuiItem::cont(),i18n("Stop"));
108 if (query
== KMessageBox::Yes
){
114 else{ //backward search
116 int query
= KMessageBox::questionYesNo(
118 i18n("Beginning of document reached.\n"\
119 "Continue from the end?"),
120 i18n("Find"),KStdGuiItem::cont(),i18n("Stop"));
121 if (query
== KMessageBox::Yes
){
122 QString string
= textLine( numLines() - 1 );
123 line
= numLines() - 1;
124 col
= string
.length();
125 last_search
= BACKWARD
;
131 emit
CursorPositionChanged();
137 void KEdit::searchdone_slot(){
147 /* antlarr: KDE 4: make it const QString & */
148 int KEdit::doSearch(QString s_pattern
, bool case_sensitive
,
149 bool wildcard
, bool forward
, int line
, int col
){
151 (void) wildcard
; // reserved for possible extension to regex
161 for(i
= line
; i
< numLines(); i
++) {
163 string
= textLine(i
);
165 pos
= string
.find(s_pattern
, i
== line
? col
: 0, case_sensitive
);
169 length
= s_pattern
.length();
171 setCursorPosition(i
,pos
,false);
173 for(int l
= 0 ; l
< length
; l
++){
177 setCursorPosition( i
, pos
+ length
, true );
179 last_search
= FORWARD
;
185 else{ // searching backwards
189 for(i
= line
; i
>= 0; i
--) {
191 string
= textLine(i
);
192 int line_length
= string
.length();
194 pos
= string
.findRev(s_pattern
, line
== i
? col
: line_length
, case_sensitive
);
198 length
= s_pattern
.length();
200 if( ! (line
== i
&& pos
> col
) ){
202 setCursorPosition(i
,pos
,false );
204 for(int l
= 0 ; l
< length
; l
++){
208 setCursorPosition(i
,pos
+ length
,true );
210 last_search
= BACKWARD
;
225 bool KEdit::repeatSearch() {
227 if(!srchdialog
|| pattern
.isEmpty())
241 //////////////////////////////////////////////////////////////////////////
247 void KEdit::replace()
249 if( srchdialog
&& srchdialog
->isVisible() )
254 if( !replace_dialog
)
256 replace_dialog
= new KEdReplace( this, "replace_dialog", false );
257 connect(replace_dialog
,SIGNAL(find()),this,SLOT(replace_search_slot()));
258 connect(replace_dialog
,SIGNAL(replace()),this,SLOT(replace_slot()));
259 connect(replace_dialog
,SIGNAL(replaceAll()),this,SLOT(replace_all_slot()));
260 connect(replace_dialog
,SIGNAL(done()),this,SLOT(replacedone_slot()));
263 QString string
= replace_dialog
->getText();
264 replace_dialog
->setText(string
.isEmpty() ? pattern
: string
);
270 replace_dialog
->show();
271 replace_dialog
->result();
275 void KEdit::replace_slot(){
281 KNotifyClient::beep();
285 int line
,col
, length
;
287 QString string
= replace_dialog
->getReplaceText();
288 length
= string
.length();
292 getCursorPosition(&line
,&col
);
294 insertAt(string
,line
,col
);
298 if (replace_dialog
->get_direction())
301 setCursorPosition(line
,col
+length
);
302 for( int k
= 0; k
< length
; k
++){
309 setCursorPosition(line
,col
);
310 for( int k
= 0; k
< length
; k
++){
316 void KEdit::replace_all_slot(){
321 QString to_find_string
= replace_dialog
->getText();
323 int lineFrom
, lineTo
, colFrom
, colTo
;
324 getSelection(&lineFrom
, &colFrom
, &lineTo
, &colTo
);
326 // replace_dialog->get_direction() is true if searching backward
327 if (replace_dialog
->get_direction())
331 replace_all_col
= colTo
- to_find_string
.length();
332 replace_all_line
= lineTo
;
336 getCursorPosition(&replace_all_line
,&replace_all_col
);
344 replace_all_col
= colFrom
;
345 replace_all_line
= lineFrom
;
349 getCursorPosition(&replace_all_line
,&replace_all_col
);
357 setAutoUpdate(false);
362 result
= doReplace(to_find_string
, replace_dialog
->case_sensitive(),
363 false, (!replace_dialog
->get_direction()),
364 replace_all_line
,replace_all_col
,true);
371 if(!replace_dialog
->get_direction()){ // forward search
373 int query
= KMessageBox::questionYesNo(
375 i18n("End of document reached.\n"\
376 "Continue from the beginning?"),
377 i18n("Find"),KStdGuiItem::cont(),i18n("Stop"));
378 if (query
== KMessageBox::Yes
){
379 replace_all_line
= 0;
384 else{ //backward search
386 int query
= KMessageBox::questionYesNo(
388 i18n("Beginning of document reached.\n"\
389 "Continue from the end?"),
390 i18n("Find"),KStdGuiItem::cont(),i18n("Stop"));
391 if (query
== KMessageBox::Yes
){
392 QString string
= textLine( numLines() - 1 );
393 replace_all_line
= numLines() - 1;
394 replace_all_col
= string
.length();
395 last_replace
= BACKWARD
;
400 emit
CursorPositionChanged();
405 void KEdit::replace_search_slot(){
412 QString to_find_string
= replace_dialog
->getText();
414 int lineFrom
, lineTo
, colFrom
, colTo
;
415 getSelection(&lineFrom
, &colFrom
, &lineTo
, &colTo
);
417 // replace_dialog->get_direction() is true if searching backward
418 if (replace_dialog
->get_direction())
422 col
= colFrom
- to_find_string
.length();
427 getCursorPosition(&line
,&col
);
440 getCursorPosition(&line
,&col
);
446 int result
= doReplace(to_find_string
, replace_dialog
->case_sensitive(),
447 false, (!replace_dialog
->get_direction()), line
, col
, false );
450 if(!replace_dialog
->get_direction()){ // forward search
452 int query
= KMessageBox::questionYesNo(
454 i18n("End of document reached.\n"\
455 "Continue from the beginning?"),
456 i18n("Replace"),KStdGuiItem::cont(),i18n("Stop"));
457 if (query
== KMessageBox::Yes
){
463 else{ //backward search
465 int query
= KMessageBox::questionYesNo(
467 i18n("Beginning of document reached.\n"\
468 "Continue from the end?"),
469 i18n("Replace"),KStdGuiItem::cont(),i18n("Stop"));
470 if (query
== KMessageBox::Yes
){
471 QString string
= textLine( numLines() - 1 );
472 line
= numLines() - 1;
473 col
= string
.length();
474 last_replace
= BACKWARD
;
481 emit
CursorPositionChanged();
487 void KEdit::replacedone_slot(){
492 replace_dialog
->hide();
493 // replace_dialog->clearFocus();
504 /* antlarr: KDE 4: make it const QString & */
505 int KEdit::doReplace(QString s_pattern
, bool case_sensitive
,
506 bool wildcard
, bool forward
, int line
, int col
, bool replace_all
){
509 (void) wildcard
; // reserved for possible extension to regex
511 int line_counter
, length
;
518 replacement
= replace_dialog
->getReplaceText();
520 replace_all_col
= col
;
524 int num_lines
= numLines();
526 while (line_counter
< num_lines
){
528 string
= textLine(line_counter
);
531 pos
= string
.find(s_pattern
, replace_all_col
, case_sensitive
);
534 pos
= string
.find(s_pattern
, line_counter
== line
? col
: 0, case_sensitive
);
540 replace_all_line
= line_counter
;
545 length
= s_pattern
.length();
547 if(replace_all
){ // automatic
549 stringnew
= string
.copy();
552 stringnew
.replace(pos
,length
,replacement
);
554 replace_all_col
= pos
+ replacement
.length();
555 replace_all_line
= line_counter
;
557 pos
= stringnew
.find(s_pattern
, replace_all_col
, case_sensitive
);
561 removeLine(line_counter
);
562 insertLine(stringnew
,line_counter
);
568 setCursorPosition( line_counter
, pos
, false );
570 for(int l
= 0 ; l
< length
; l
++){
574 setCursorPosition( line_counter
, pos
+ length
, true );
576 last_replace
= FORWARD
;
586 else{ // searching backwards
588 while(line_counter
>= 0){
590 string
= textLine(line_counter
);
592 int line_length
= string
.length();
595 if (replace_all_col
< 0)
598 pos
= string
.findRev(s_pattern
, replace_all_col
, case_sensitive
);
601 if ((line
== line_counter
) && (col
< 0))
604 pos
= string
.findRev(s_pattern
,
605 line
== line_counter
? col
: line_length
, case_sensitive
);
612 if(line_counter
>= 0){
613 string
= textLine(line_counter
);
614 replace_all_col
= string
.length();
617 replace_all_line
= line_counter
;
622 length
= s_pattern
.length();
624 if(replace_all
){ // automatic
626 stringnew
= string
.copy();
627 stringnew
.replace(pos
,length
,replacement
);
629 removeLine(line_counter
);
630 insertLine(stringnew
,line_counter
);
632 replace_all_col
= pos
-length
;
633 replace_all_line
= line_counter
;
634 if (replace_all_col
< 0)
638 if(line_counter
>= 0){
639 string
= textLine(line_counter
);
640 replace_all_col
= string
.length();
642 replace_all_line
= line_counter
;
649 // printf("line_counter %d pos %d col %d\n",line_counter, pos,col);
650 if( ! (line
== line_counter
&& pos
> col
) ){
652 setCursorPosition(line_counter
, pos
+ length
,false );
654 for(int l
= 0 ; l
< length
; l
++){
658 setCursorPosition(line_counter
, pos
,true );
661 last_replace
= BACKWARD
;
679 ////////////////////////////////////////////////////////////////////
684 class KEdFind::KEdFindPrivate
687 KEdFindPrivate( QWidget
*parent
) {
688 combo
= new KHistoryCombo( parent
, "value" );
689 combo
->setMaxCount( 20 ); // just some default
695 KHistoryCombo
*combo
;
699 KEdFind::KEdFind( QWidget
*parent
, const char *name
, bool modal
)
700 :KDialogBase( parent
, name
, modal
, i18n("Find"),
701 modal
? User1
|Cancel
: User1
|Close
, User1
, false, KGuiItem( i18n("&Find"), "find") )
703 setWFlags( WType_TopLevel
);
705 QWidget
*page
= new QWidget( this );
707 QVBoxLayout
*topLayout
= new QVBoxLayout( page
, 0, spacingHint() );
709 d
= new KEdFindPrivate( page
);
711 QString text
= i18n("Find:");
712 QLabel
*label
= new QLabel( text
, page
, "find" );
713 topLayout
->addWidget( label
);
715 d
->combo
->setMinimumWidth(fontMetrics().maxWidth()*20);
716 d
->combo
->setFocus();
718 connect(d
->combo
, SIGNAL(textChanged ( const QString
& )),
719 this,SLOT(textSearchChanged ( const QString
& )));
721 topLayout
->addWidget(d
->combo
);
723 group
= new QVButtonGroup( i18n("Options"), page
);
724 topLayout
->addWidget( group
);
726 QHBox
* row1
= new QHBox( group
);
728 text
= i18n("Case &sensitive");
729 sensitive
= new QCheckBox( text
, row1
, "case");
730 text
= i18n("Find &backwards");
731 direction
= new QCheckBox( text
, row1
, "direction" );
734 enableButton( KDialogBase::User1
, !d
->combo
->currentText().isEmpty() );
737 connect( this, SIGNAL( closeClicked() ), this, SLOT( slotCancel() ) );
745 void KEdFind::textSearchChanged ( const QString
&text
)
747 enableButton( KDialogBase::User1
, !text
.isEmpty() );
750 void KEdFind::slotCancel( void )
753 KDialogBase::slotCancel();
756 void KEdFind::slotUser1( void )
758 if( !d
->combo
->currentText().isEmpty() )
760 d
->combo
->addToHistory( d
->combo
->currentText() );
766 QString
KEdFind::getText() const
768 return d
->combo
->currentText();
772 /* antlarr: KDE 4: make it const QString & */
773 void KEdFind::setText(QString string
)
775 d
->combo
->setEditText(string
);
776 d
->combo
->lineEdit()->selectAll();
779 void KEdFind::setCaseSensitive( bool b
)
781 sensitive
->setChecked( b
);
784 bool KEdFind::case_sensitive() const
786 return sensitive
->isChecked();
789 void KEdFind::setDirection( bool b
)
791 direction
->setChecked( b
);
794 bool KEdFind::get_direction() const
796 return direction
->isChecked();
799 KHistoryCombo
* KEdFind::searchCombo() const
806 ////////////////////////////////////////////////////////////////////
811 class KEdReplace::KEdReplacePrivate
814 KEdReplacePrivate( QWidget
*parent
) {
815 searchCombo
= new KHistoryCombo( parent
, "value" );
816 replaceCombo
= new KHistoryCombo( parent
, "replace_value" );
818 searchCombo
->setMaxCount( 20 ); // just some defaults
819 replaceCombo
->setMaxCount( 20 );
821 ~KEdReplacePrivate() {
826 KHistoryCombo
*searchCombo
, *replaceCombo
;
829 KEdReplace::KEdReplace( QWidget
*parent
, const char *name
, bool modal
)
830 :KDialogBase( parent
, name
, modal
, i18n("Replace"),
831 modal
? User3
|User2
|User1
|Cancel
: User3
|User2
|User1
|Close
,
833 i18n("Replace &All"), i18n("&Replace"), KGuiItem( i18n("&Find"), "find") )
835 setWFlags( WType_TopLevel
);
837 setButtonBoxOrientation( Vertical
);
839 QFrame
*page
= makeMainWidget();
840 QVBoxLayout
*topLayout
= new QVBoxLayout( page
, 0, spacingHint() );
842 d
= new KEdReplacePrivate( page
);
844 QString text
= i18n("Find:");
845 QLabel
*label
= new QLabel( text
, page
, "find" );
846 topLayout
->addWidget( label
);
848 d
->searchCombo
->setMinimumWidth(fontMetrics().maxWidth()*20);
849 d
->searchCombo
->setFocus();
850 topLayout
->addWidget(d
->searchCombo
);
852 text
= i18n("Replace with:");
853 label
= new QLabel( text
, page
, "replace" );
854 topLayout
->addWidget( label
);
856 d
->replaceCombo
->setMinimumWidth(fontMetrics().maxWidth()*20);
857 topLayout
->addWidget(d
->replaceCombo
);
859 connect(d
->searchCombo
, SIGNAL(textChanged ( const QString
& )),
860 this,SLOT(textSearchChanged ( const QString
& )));
862 QButtonGroup
*group
= new QButtonGroup( i18n("Options"), page
);
863 topLayout
->addWidget( group
);
865 QGridLayout
*gbox
= new QGridLayout( group
, 3, 2, spacingHint() );
866 gbox
->addRowSpacing( 0, fontMetrics().lineSpacing() );
868 text
= i18n("Case &sensitive");
869 sensitive
= new QCheckBox( text
, group
, "case");
870 text
= i18n("Find &backwards");
871 direction
= new QCheckBox( text
, group
, "direction" );
872 gbox
->addWidget( sensitive
, 1, 0 );
873 gbox
->addWidget( direction
, 1, 1 );
874 gbox
->setRowStretch( 2, 10 );
878 KEdReplace::~KEdReplace()
883 void KEdReplace::textSearchChanged ( const QString
&text
)
885 bool state
=text
.isEmpty();
886 enableButton( KDialogBase::User1
, !state
);
887 enableButton( KDialogBase::User2
, !state
);
888 enableButton( KDialogBase::User3
, !state
);
891 void KEdReplace::slotCancel( void )
894 d
->searchCombo
->clearEdit();
895 d
->replaceCombo
->clearEdit();
896 KDialogBase::slotCancel();
899 void KEdReplace::slotClose( void )
904 void KEdReplace::slotUser1( void )
906 if( !d
->searchCombo
->currentText().isEmpty() )
908 d
->replaceCombo
->addToHistory( d
->replaceCombo
->currentText() );
914 void KEdReplace::slotUser2( void )
916 if( !d
->searchCombo
->currentText().isEmpty() )
918 d
->replaceCombo
->addToHistory( d
->replaceCombo
->currentText() );
923 void KEdReplace::slotUser3( void )
925 if( !d
->searchCombo
->currentText().isEmpty() )
927 d
->searchCombo
->addToHistory( d
->searchCombo
->currentText() );
933 QString
KEdReplace::getText()
935 return d
->searchCombo
->currentText();
939 QString
KEdReplace::getReplaceText()
941 return d
->replaceCombo
->currentText();
945 /* antlarr: KDE 4: make it const QString & */
946 void KEdReplace::setText(QString string
)
948 d
->searchCombo
->setEditText(string
);
949 d
->searchCombo
->lineEdit()->selectAll();
953 bool KEdReplace::case_sensitive()
955 return sensitive
->isChecked();
959 bool KEdReplace::get_direction()
961 return direction
->isChecked();
964 KHistoryCombo
* KEdReplace::searchCombo() const
966 return d
->searchCombo
;
969 KHistoryCombo
* KEdReplace::replaceCombo() const
971 return d
->replaceCombo
;
975 KEdGotoLine::KEdGotoLine( QWidget
*parent
, const char *name
, bool modal
)
976 :KDialogBase( parent
, name
, modal
, i18n("Go to Line"), modal
? Ok
|Cancel
: Ok
|Close
, Ok
, false )
978 QWidget
*page
= new QWidget( this );
980 QVBoxLayout
*topLayout
= new QVBoxLayout( page
, 0, spacingHint() );
982 lineNum
= new KIntNumInput( 1, page
);
983 lineNum
->setRange(1, 1000000, 1, false);
984 lineNum
->setLabel(i18n("Go to line:"), AlignVCenter
| AlignLeft
);
985 // lineNum->setMinimumWidth(fontMetrics().maxWidth()*20);
986 topLayout
->addWidget( lineNum
);
988 topLayout
->addStretch(10);
993 void KEdGotoLine::selected(int)
999 int KEdGotoLine::getLineNumber()
1001 return lineNum
->value();
1005 //////////////////////////////////////////////////////////////////////////////
1010 void KEdit::spellcheck_start()
1012 saved_readonlystate
= isReadOnly();
1016 void KEdit::misspelling (const QString
&word
, const QStringList
&, unsigned int pos
)
1020 unsigned int cnt
= 0;
1021 posToRowCol (pos
, l
, cnt
);
1022 setSelection(l
, cnt
, l
, cnt
+word
.length());
1025 if (cursorPoint().y()>height()/2)
1026 kspell->moveDlg (10, height()/2-kspell->heightDlg()-15);
1028 kspell->moveDlg (10, height()/2 + 15);
1033 //need to use pos for insert, not cur, so forget cur altogether
1034 void KEdit::corrected (const QString
&originalword
, const QString
&newword
, unsigned int pos
)
1036 //we'll reselect the original word in case the user has played with
1037 //the selection in eframe or the word was auto-replaced
1040 unsigned int cnt
= 0;
1042 if( newword
!= originalword
)
1044 posToRowCol (pos
, l
, cnt
);
1045 setSelection(l
, cnt
, l
, cnt
+originalword
.length());
1047 setReadOnly ( false );
1048 removeSelectedText();
1050 setReadOnly ( true );
1058 void KEdit::posToRowCol(unsigned int pos
, unsigned int &line
, unsigned int &col
)
1060 for (line
= 0; line
< static_cast<uint
>(numLines()) && col
<= pos
; line
++)
1062 col
+= lineLength(line
)+1;
1065 col
= pos
- col
+ lineLength(line
) + 1;
1068 void KEdit::spellcheck_stop()
1072 setReadOnly ( saved_readonlystate
);
1075 QString
KEdit::selectWordUnderCursor( )
1080 getCursorPosition(¶g
, &pos
);
1082 QString txt
= text(parag
);
1088 const QChar
&ch
= txt
[start
-1];
1089 if (ch
.isSpace() || ch
.isPunct())
1096 int len
= txt
.length();
1099 const QChar
&ch
= txt
[end
];
1100 if (ch
.isSpace() || ch
.isPunct())
1104 setSelection(parag
, start
, parag
, end
);
1105 return txt
.mid(start
, end
-start
);
1108 QPopupMenu
*KEdit::createPopupMenu( const QPoint
& pos
)
1110 enum { IdUndo
, IdRedo
, IdSep1
, IdCut
, IdCopy
, IdPaste
, IdClear
, IdSep2
, IdSelectAll
};
1112 QPopupMenu
*menu
= QMultiLineEdit::createPopupMenu( pos
);
1115 menu
->changeItem( menu
->idAt(0), SmallIconSet("editcopy"), menu
->text( menu
->idAt(0) ) );
1117 int id
= menu
->idAt(0);
1118 menu
->changeItem( id
- IdUndo
, SmallIconSet("undo"), menu
->text( id
- IdUndo
) );
1119 menu
->changeItem( id
- IdRedo
, SmallIconSet("redo"), menu
->text( id
- IdRedo
) );
1120 menu
->changeItem( id
- IdCut
, SmallIconSet("editcut"), menu
->text( id
- IdCut
) );
1121 menu
->changeItem( id
- IdCopy
, SmallIconSet("editcopy"), menu
->text( id
- IdCopy
) );
1122 menu
->changeItem( id
- IdPaste
, SmallIconSet("editpaste"), menu
->text( id
- IdPaste
) );
1123 menu
->changeItem( id
- IdClear
, SmallIconSet("editclear"), menu
->text( id
- IdClear
) );