klineedit.cpp
00001 /* This file is part of the KDE libraries 00002 00003 Copyright (C) 1997 Sven Radej (sven.radej@iname.com) 00004 Copyright (c) 1999 Patrick Ward <PAT_WARD@HP-USA-om5.om.hp.com> 00005 Copyright (c) 1999 Preston Brown <pbrown@kde.org> 00006 00007 Re-designed for KDE 2.x by 00008 Copyright (c) 2000, 2001 Dawit Alemayehu <adawit@kde.org> 00009 Copyright (c) 2000, 2001 Carsten Pfeiffer <pfeiffer@kde.org> 00010 00011 This library is free software; you can redistribute it and/or 00012 modify it under the terms of the GNU Lesser General Public 00013 License (LGPL) as published by the Free Software Foundation; 00014 either version 2 of the License, or (at your option) any later 00015 version. 00016 00017 This library is distributed in the hope that it will be useful, 00018 but WITHOUT ANY WARRANTY; without even the implied warranty of 00019 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00020 Lesser General Public License for more details. 00021 00022 You should have received a copy of the GNU Lesser General Public License 00023 along with this library; see the file COPYING.LIB. If not, write to 00024 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00025 Boston, MA 02110-1301, USA. 00026 */ 00027 00028 #include <qclipboard.h> 00029 #include <qpainter.h> 00030 #include <qtimer.h> 00031 00032 #include <kconfig.h> 00033 #include <qtooltip.h> 00034 #include <kcursor.h> 00035 #include <klocale.h> 00036 #include <kstdaccel.h> 00037 #include <kpopupmenu.h> 00038 #include <kdebug.h> 00039 #include <kcompletionbox.h> 00040 #include <kurl.h> 00041 #include <kurldrag.h> 00042 #include <kiconloader.h> 00043 #include <kapplication.h> 00044 00045 #include "klineedit.h" 00046 #include "klineedit.moc" 00047 00048 00049 class KLineEdit::KLineEditPrivate 00050 { 00051 public: 00052 KLineEditPrivate() 00053 { 00054 completionBox = 0L; 00055 handleURLDrops = true; 00056 grabReturnKeyEvents = false; 00057 00058 userSelection = true; 00059 autoSuggest = false; 00060 disableRestoreSelection = false; 00061 enableSqueezedText = false; 00062 00063 if ( !initialized ) 00064 { 00065 KConfigGroup config( KGlobal::config(), "General" ); 00066 backspacePerformsCompletion = config.readBoolEntry( "Backspace performs completion", false ); 00067 00068 initialized = true; 00069 } 00070 00071 } 00072 00073 ~KLineEditPrivate() 00074 { 00075 // causes a weird crash in KWord at least, so let Qt delete it for us. 00076 // delete completionBox; 00077 } 00078 00079 static bool initialized; 00080 static bool backspacePerformsCompletion; // Configuration option 00081 00082 QColor previousHighlightColor; 00083 QColor previousHighlightedTextColor; 00084 00085 bool userSelection: 1; 00086 bool autoSuggest : 1; 00087 bool disableRestoreSelection: 1; 00088 bool handleURLDrops:1; 00089 bool grabReturnKeyEvents:1; 00090 bool enableSqueezedText:1; 00091 00092 int squeezedEnd; 00093 int squeezedStart; 00094 BackgroundMode bgMode; 00095 QString squeezedText; 00096 KCompletionBox *completionBox; 00097 00098 QString clickMessage; 00099 bool drawClickMsg:1; 00100 }; 00101 00102 bool KLineEdit::KLineEditPrivate::backspacePerformsCompletion = false; 00103 bool KLineEdit::KLineEditPrivate::initialized = false; 00104 00105 00106 KLineEdit::KLineEdit( const QString &string, QWidget *parent, const char *name ) 00107 :QLineEdit( string, parent, name ) 00108 { 00109 init(); 00110 } 00111 00112 KLineEdit::KLineEdit( QWidget *parent, const char *name ) 00113 :QLineEdit( parent, name ) 00114 { 00115 init(); 00116 } 00117 00118 KLineEdit::~KLineEdit () 00119 { 00120 delete d; 00121 d = 0; 00122 } 00123 00124 void KLineEdit::init() 00125 { 00126 d = new KLineEditPrivate; 00127 possibleTripleClick = false; 00128 d->bgMode = backgroundMode (); 00129 00130 // Enable the context menu by default. 00131 KLineEdit::setContextMenuEnabled( true ); 00132 KCursor::setAutoHideCursor( this, true, true ); 00133 installEventFilter( this ); 00134 00135 KGlobalSettings::Completion mode = completionMode(); 00136 d->autoSuggest = (mode == KGlobalSettings::CompletionMan || 00137 mode == KGlobalSettings::CompletionPopupAuto || 00138 mode == KGlobalSettings::CompletionAuto); 00139 connect( this, SIGNAL(selectionChanged()), this, SLOT(slotRestoreSelectionColors())); 00140 00141 QPalette p = palette(); 00142 if ( !d->previousHighlightedTextColor.isValid() ) 00143 d->previousHighlightedTextColor=p.color(QPalette::Normal,QColorGroup::HighlightedText); 00144 if ( !d->previousHighlightColor.isValid() ) 00145 d->previousHighlightColor=p.color(QPalette::Normal,QColorGroup::Highlight); 00146 00147 d->drawClickMsg = false; 00148 } 00149 00150 void KLineEdit::setCompletionMode( KGlobalSettings::Completion mode ) 00151 { 00152 KGlobalSettings::Completion oldMode = completionMode(); 00153 00154 if ( oldMode != mode && (oldMode == KGlobalSettings::CompletionPopup || 00155 oldMode == KGlobalSettings::CompletionPopupAuto ) && 00156 d->completionBox && d->completionBox->isVisible() ) 00157 d->completionBox->hide(); 00158 00159 // If the widgets echo mode is not Normal, no completion 00160 // feature will be enabled even if one is requested. 00161 if ( echoMode() != QLineEdit::Normal ) 00162 mode = KGlobalSettings::CompletionNone; // Override the request. 00163 00164 if ( kapp && !kapp->authorize("lineedit_text_completion") ) 00165 mode = KGlobalSettings::CompletionNone; 00166 00167 if ( mode == KGlobalSettings::CompletionPopupAuto || 00168 mode == KGlobalSettings::CompletionAuto || 00169 mode == KGlobalSettings::CompletionMan ) 00170 d->autoSuggest = true; 00171 else 00172 d->autoSuggest = false; 00173 00174 KCompletionBase::setCompletionMode( mode ); 00175 } 00176 00177 void KLineEdit::setCompletedText( const QString& t, bool marked ) 00178 { 00179 if ( !d->autoSuggest ) 00180 return; 00181 00182 QString txt = text(); 00183 00184 if ( t != txt ) 00185 { 00186 int start = marked ? txt.length() : t.length(); 00187 validateAndSet( t, cursorPosition(), start, t.length() ); 00188 setUserSelection(false); 00189 } 00190 else 00191 setUserSelection(true); 00192 00193 } 00194 00195 void KLineEdit::setCompletedText( const QString& text ) 00196 { 00197 KGlobalSettings::Completion mode = completionMode(); 00198 bool marked = ( mode == KGlobalSettings::CompletionAuto || 00199 mode == KGlobalSettings::CompletionMan || 00200 mode == KGlobalSettings::CompletionPopup || 00201 mode == KGlobalSettings::CompletionPopupAuto ); 00202 setCompletedText( text, marked ); 00203 } 00204 00205 void KLineEdit::rotateText( KCompletionBase::KeyBindingType type ) 00206 { 00207 KCompletion* comp = compObj(); 00208 if ( comp && 00209 (type == KCompletionBase::PrevCompletionMatch || 00210 type == KCompletionBase::NextCompletionMatch ) ) 00211 { 00212 QString input; 00213 00214 if (type == KCompletionBase::PrevCompletionMatch) 00215 comp->previousMatch(); 00216 else 00217 comp->nextMatch(); 00218 00219 // Skip rotation if previous/next match is null or the same text 00220 if ( input.isNull() || input == displayText() ) 00221 return; 00222 setCompletedText( input, hasSelectedText() ); 00223 } 00224 } 00225 00226 void KLineEdit::makeCompletion( const QString& text ) 00227 { 00228 KCompletion *comp = compObj(); 00229 KGlobalSettings::Completion mode = completionMode(); 00230 00231 if ( !comp || mode == KGlobalSettings::CompletionNone ) 00232 return; // No completion object... 00233 00234 QString match = comp->makeCompletion( text ); 00235 00236 if ( mode == KGlobalSettings::CompletionPopup || 00237 mode == KGlobalSettings::CompletionPopupAuto ) 00238 { 00239 if ( match.isNull() ) 00240 { 00241 if ( d->completionBox ) 00242 { 00243 d->completionBox->hide(); 00244 d->completionBox->clear(); 00245 } 00246 } 00247 else 00248 setCompletedItems( comp->allMatches() ); 00249 } 00250 else // Auto, ShortAuto (Man) and Shell 00251 { 00252 // all other completion modes 00253 // If no match or the same match, simply return without completing. 00254 if ( match.isNull() || match == text ) 00255 return; 00256 00257 if ( mode != KGlobalSettings::CompletionShell ) 00258 setUserSelection(false); 00259 00260 if ( d->autoSuggest ) 00261 setCompletedText( match ); 00262 } 00263 } 00264 00265 void KLineEdit::setReadOnly(bool readOnly) 00266 { 00267 // Do not do anything if nothing changed... 00268 if (readOnly == isReadOnly ()) 00269 return; 00270 00271 QLineEdit::setReadOnly (readOnly); 00272 00273 if (readOnly) 00274 { 00275 d->bgMode = backgroundMode (); 00276 setBackgroundMode (Qt::PaletteBackground); 00277 if (d->enableSqueezedText && d->squeezedText.isEmpty()) 00278 { 00279 d->squeezedText = text(); 00280 setSqueezedText(); 00281 } 00282 } 00283 else 00284 { 00285 if (!d->squeezedText.isEmpty()) 00286 { 00287 setText(d->squeezedText); 00288 d->squeezedText = QString::null; 00289 } 00290 setBackgroundMode (d->bgMode); 00291 } 00292 } 00293 00294 void KLineEdit::setSqueezedText( const QString &text) 00295 { 00296 setEnableSqueezedText(true); 00297 setText(text); 00298 } 00299 00300 void KLineEdit::setEnableSqueezedText( bool enable ) 00301 { 00302 d->enableSqueezedText = enable; 00303 } 00304 00305 bool KLineEdit::isSqueezedTextEnabled() const 00306 { 00307 return d->enableSqueezedText; 00308 } 00309 00310 void KLineEdit::setText( const QString& text ) 00311 { 00312 d->drawClickMsg = text.isEmpty() && !d->clickMessage.isEmpty(); 00313 update(); 00314 00315 if( d->enableSqueezedText && isReadOnly() ) 00316 { 00317 d->squeezedText = text; 00318 setSqueezedText(); 00319 return; 00320 } 00321 00322 QLineEdit::setText( text ); 00323 } 00324 00325 void KLineEdit::setSqueezedText() 00326 { 00327 d->squeezedStart = 0; 00328 d->squeezedEnd = 0; 00329 QString fullText = d->squeezedText; 00330 QFontMetrics fm(fontMetrics()); 00331 int labelWidth = size().width() - 2*frameWidth() - 2; 00332 int textWidth = fm.width(fullText); 00333 00334 if (textWidth > labelWidth) 00335 { 00336 // start with the dots only 00337 QString squeezedText = "..."; 00338 int squeezedWidth = fm.width(squeezedText); 00339 00340 // estimate how many letters we can add to the dots on both sides 00341 int letters = fullText.length() * (labelWidth - squeezedWidth) / textWidth / 2; 00342 squeezedText = fullText.left(letters) + "..." + fullText.right(letters); 00343 squeezedWidth = fm.width(squeezedText); 00344 00345 if (squeezedWidth < labelWidth) 00346 { 00347 // we estimated too short 00348 // add letters while text < label 00349 do 00350 { 00351 letters++; 00352 squeezedText = fullText.left(letters) + "..." + fullText.right(letters); 00353 squeezedWidth = fm.width(squeezedText); 00354 } while (squeezedWidth < labelWidth); 00355 letters--; 00356 squeezedText = fullText.left(letters) + "..." + fullText.right(letters); 00357 } 00358 else if (squeezedWidth > labelWidth) 00359 { 00360 // we estimated too long 00361 // remove letters while text > label 00362 do 00363 { 00364 letters--; 00365 squeezedText = fullText.left(letters) + "..." + fullText.right(letters); 00366 squeezedWidth = fm.width(squeezedText); 00367 } while (squeezedWidth > labelWidth); 00368 } 00369 00370 if (letters < 5) 00371 { 00372 // too few letters added -> we give up squeezing 00373 QLineEdit::setText(fullText); 00374 } 00375 else 00376 { 00377 QLineEdit::setText(squeezedText); 00378 d->squeezedStart = letters; 00379 d->squeezedEnd = fullText.length() - letters; 00380 } 00381 00382 QToolTip::remove( this ); 00383 QToolTip::add( this, fullText ); 00384 00385 } 00386 else 00387 { 00388 QLineEdit::setText(fullText); 00389 00390 QToolTip::remove( this ); 00391 QToolTip::hide(); 00392 } 00393 00394 setCursorPosition(0); 00395 } 00396 00397 void KLineEdit::copy() const 00398 { 00399 if( !copySqueezedText(true)) 00400 QLineEdit::copy(); 00401 } 00402 00403 bool KLineEdit::copySqueezedText(bool clipboard) const 00404 { 00405 if (!d->squeezedText.isEmpty() && d->squeezedStart) 00406 { 00407 int start, end; 00408 KLineEdit *that = const_cast<KLineEdit *>(this); 00409 if (!that->getSelection(&start, &end)) 00410 return false; 00411 if (start >= d->squeezedStart+3) 00412 start = start - 3 - d->squeezedStart + d->squeezedEnd; 00413 else if (start > d->squeezedStart) 00414 start = d->squeezedStart; 00415 if (end >= d->squeezedStart+3) 00416 end = end - 3 - d->squeezedStart + d->squeezedEnd; 00417 else if (end > d->squeezedStart) 00418 end = d->squeezedEnd; 00419 if (start == end) 00420 return false; 00421 QString t = d->squeezedText; 00422 t = t.mid(start, end - start); 00423 disconnect( QApplication::clipboard(), SIGNAL(selectionChanged()), this, 0); 00424 QApplication::clipboard()->setText( t, clipboard ? QClipboard::Clipboard : QClipboard::Selection ); 00425 connect( QApplication::clipboard(), SIGNAL(selectionChanged()), this, 00426 SLOT(clipboardChanged()) ); 00427 return true; 00428 } 00429 return false; 00430 } 00431 00432 void KLineEdit::resizeEvent( QResizeEvent * ev ) 00433 { 00434 if (!d->squeezedText.isEmpty()) 00435 setSqueezedText(); 00436 00437 QLineEdit::resizeEvent(ev); 00438 } 00439 00440 void KLineEdit::keyPressEvent( QKeyEvent *e ) 00441 { 00442 KKey key( e ); 00443 00444 if ( KStdAccel::copy().contains( key ) ) 00445 { 00446 copy(); 00447 return; 00448 } 00449 else if ( KStdAccel::paste().contains( key ) ) 00450 { 00451 paste(); 00452 return; 00453 } 00454 else if ( KStdAccel::pasteSelection().contains( key ) ) 00455 { 00456 QString text = QApplication::clipboard()->text( QClipboard::Selection); 00457 insert( text ); 00458 deselect(); 00459 return; 00460 } 00461 00462 else if ( KStdAccel::cut().contains( key ) ) 00463 { 00464 cut(); 00465 return; 00466 } 00467 else if ( KStdAccel::undo().contains( key ) ) 00468 { 00469 undo(); 00470 return; 00471 } 00472 else if ( KStdAccel::redo().contains( key ) ) 00473 { 00474 redo(); 00475 return; 00476 } 00477 else if ( KStdAccel::deleteWordBack().contains( key ) ) 00478 { 00479 cursorWordBackward(true); 00480 if ( hasSelectedText() ) 00481 del(); 00482 00483 e->accept(); 00484 return; 00485 } 00486 else if ( KStdAccel::deleteWordForward().contains( key ) ) 00487 { 00488 // Workaround for QT bug where 00489 cursorWordForward(true); 00490 if ( hasSelectedText() ) 00491 del(); 00492 00493 e->accept(); 00494 return; 00495 } 00496 else if ( KStdAccel::backwardWord().contains( key ) ) 00497 { 00498 cursorWordBackward(false); 00499 e->accept(); 00500 return; 00501 } 00502 else if ( KStdAccel::forwardWord().contains( key ) ) 00503 { 00504 cursorWordForward(false); 00505 e->accept(); 00506 return; 00507 } 00508 else if ( KStdAccel::beginningOfLine().contains( key ) ) 00509 { 00510 home(false); 00511 e->accept(); 00512 return; 00513 } 00514 else if ( KStdAccel::endOfLine().contains( key ) ) 00515 { 00516 end(false); 00517 e->accept(); 00518 return; 00519 } 00520 00521 00522 // Filter key-events if EchoMode is normal and 00523 // completion mode is not set to CompletionNone 00524 if ( echoMode() == QLineEdit::Normal && 00525 completionMode() != KGlobalSettings::CompletionNone ) 00526 { 00527 KeyBindingMap keys = getKeyBindings(); 00528 KGlobalSettings::Completion mode = completionMode(); 00529 bool noModifier = (e->state() == NoButton || 00530 e->state() == ShiftButton || 00531 e->state() == Keypad); 00532 00533 if ( (mode == KGlobalSettings::CompletionAuto || 00534 mode == KGlobalSettings::CompletionPopupAuto || 00535 mode == KGlobalSettings::CompletionMan) && noModifier ) 00536 { 00537 if ( !d->userSelection && hasSelectedText() && 00538 ( e->key() == Key_Right || e->key() == Key_Left ) && 00539 e->state()==NoButton ) 00540 { 00541 QString old_txt = text(); 00542 d->disableRestoreSelection = true; 00543 int start,end; 00544 getSelection(&start, &end); 00545 00546 deselect(); 00547 QLineEdit::keyPressEvent ( e ); 00548 int cPosition=cursorPosition(); 00549 if (e->key() ==Key_Right && cPosition > start ) 00550 validateAndSet(old_txt, cPosition, cPosition, old_txt.length()); 00551 else 00552 validateAndSet(old_txt, cPosition, start, old_txt.length()); 00553 00554 d->disableRestoreSelection = false; 00555 return; 00556 } 00557 00558 if ( e->key() == Key_Escape ) 00559 { 00560 if (hasSelectedText() && !d->userSelection ) 00561 { 00562 del(); 00563 setUserSelection(true); 00564 } 00565 00566 // Don't swallow the Escape press event for the case 00567 // of dialogs, which have Escape associated to Cancel 00568 e->ignore(); 00569 return; 00570 } 00571 00572 } 00573 00574 if ( (mode == KGlobalSettings::CompletionAuto || 00575 mode == KGlobalSettings::CompletionMan) && noModifier ) 00576 { 00577 QString keycode = e->text(); 00578 if ( !keycode.isEmpty() && (keycode.unicode()->isPrint() || 00579 e->key() == Key_Backspace || e->key() == Key_Delete ) ) 00580 { 00581 bool hasUserSelection=d->userSelection; 00582 bool hadSelection=hasSelectedText(); 00583 00584 bool cursorNotAtEnd=false; 00585 00586 int start,end; 00587 getSelection(&start, &end); 00588 int cPos = cursorPosition(); 00589 00590 // When moving the cursor, we want to keep the autocompletion as an 00591 // autocompletion, so we want to process events at the cursor position 00592 // as if there was no selection. After processing the key event, we 00593 // can set the new autocompletion again. 00594 if ( hadSelection && !hasUserSelection && start>cPos ) 00595 { 00596 del(); 00597 setCursorPosition(cPos); 00598 cursorNotAtEnd=true; 00599 } 00600 00601 d->disableRestoreSelection = true; 00602 QLineEdit::keyPressEvent ( e ); 00603 d->disableRestoreSelection = false; 00604 00605 QString txt = text(); 00606 int len = txt.length(); 00607 if ( !hasSelectedText() && len /*&& cursorPosition() == len */) 00608 { 00609 if ( e->key() == Key_Backspace ) 00610 { 00611 if ( hadSelection && !hasUserSelection && !cursorNotAtEnd ) 00612 { 00613 backspace(); 00614 txt = text(); 00615 len = txt.length(); 00616 } 00617 00618 if ( !d->backspacePerformsCompletion || !len ) 00619 d->autoSuggest = false; 00620 } 00621 00622 if (e->key() == Key_Delete ) 00623 d->autoSuggest=false; 00624 00625 if ( emitSignals() ) 00626 emit completion( txt ); 00627 00628 if ( handleSignals() ) 00629 makeCompletion( txt ); 00630 00631 if( (e->key() == Key_Backspace || e->key() == Key_Delete) ) 00632 d->autoSuggest=true; 00633 00634 e->accept(); 00635 } 00636 00637 return; 00638 } 00639 00640 } 00641 00642 else if (( mode == KGlobalSettings::CompletionPopup || 00643 mode == KGlobalSettings::CompletionPopupAuto ) && 00644 noModifier && !e->text().isEmpty() ) 00645 { 00646 QString old_txt = text(); 00647 bool hasUserSelection=d->userSelection; 00648 bool hadSelection=hasSelectedText(); 00649 bool cursorNotAtEnd=false; 00650 00651 int start,end; 00652 getSelection(&start, &end); 00653 int cPos = cursorPosition(); 00654 QString keycode = e->text(); 00655 00656 // When moving the cursor, we want to keep the autocompletion as an 00657 // autocompletion, so we want to process events at the cursor position 00658 // as if there was no selection. After processing the key event, we 00659 // can set the new autocompletion again. 00660 if (hadSelection && !hasUserSelection && start>cPos && 00661 ( (!keycode.isEmpty() && keycode.unicode()->isPrint()) || 00662 e->key() == Key_Backspace || e->key() == Key_Delete ) ) 00663 { 00664 del(); 00665 setCursorPosition(cPos); 00666 cursorNotAtEnd=true; 00667 } 00668 00669 uint selectedLength=selectedText().length(); 00670 00671 d->disableRestoreSelection = true; 00672 QLineEdit::keyPressEvent ( e ); 00673 d->disableRestoreSelection = false; 00674 00675 if (( selectedLength != selectedText().length() ) && !hasUserSelection ) 00676 slotRestoreSelectionColors(); // and set userSelection to true 00677 00678 QString txt = text(); 00679 int len = txt.length(); 00680 00681 if ( txt != old_txt && len/* && ( cursorPosition() == len || force )*/ && 00682 ( (!keycode.isEmpty() && keycode.unicode()->isPrint()) || 00683 e->key() == Key_Backspace || e->key() == Key_Delete) ) 00684 { 00685 if ( e->key() == Key_Backspace ) 00686 { 00687 if ( hadSelection && !hasUserSelection && !cursorNotAtEnd ) 00688 { 00689 backspace(); 00690 txt = text(); 00691 len = txt.length(); 00692 } 00693 00694 if ( !d->backspacePerformsCompletion ) 00695 d->autoSuggest = false; 00696 } 00697 00698 if (e->key() == Key_Delete ) 00699 d->autoSuggest=false; 00700 00701 if ( d->completionBox ) 00702 d->completionBox->setCancelledText( txt ); 00703 00704 if ( emitSignals() ) 00705 emit completion( txt ); // emit when requested... 00706 00707 if ( handleSignals() ) { 00708 makeCompletion( txt ); // handle when requested... 00709 } 00710 00711 if ( (e->key() == Key_Backspace || e->key() == Key_Delete ) && 00712 mode == KGlobalSettings::CompletionPopupAuto ) 00713 d->autoSuggest=true; 00714 00715 e->accept(); 00716 } 00717 else if (!len && d->completionBox && d->completionBox->isVisible()) 00718 d->completionBox->hide(); 00719 00720 return; 00721 } 00722 00723 else if ( mode == KGlobalSettings::CompletionShell ) 00724 { 00725 // Handles completion. 00726 KShortcut cut; 00727 if ( keys[TextCompletion].isNull() ) 00728 cut = KStdAccel::shortcut(KStdAccel::TextCompletion); 00729 else 00730 cut = keys[TextCompletion]; 00731 00732 if ( cut.contains( key ) ) 00733 { 00734 // Emit completion if the completion mode is CompletionShell 00735 // and the cursor is at the end of the string. 00736 QString txt = text(); 00737 int len = txt.length(); 00738 if ( cursorPosition() == len && len != 0 ) 00739 { 00740 if ( emitSignals() ) 00741 emit completion( txt ); 00742 if ( handleSignals() ) 00743 makeCompletion( txt ); 00744 return; 00745 } 00746 } 00747 else if ( d->completionBox ) 00748 d->completionBox->hide(); 00749 } 00750 00751 // handle rotation 00752 if ( mode != KGlobalSettings::CompletionNone ) 00753 { 00754 // Handles previous match 00755 KShortcut cut; 00756 if ( keys[PrevCompletionMatch].isNull() ) 00757 cut = KStdAccel::shortcut(KStdAccel::PrevCompletion); 00758 else 00759 cut = keys[PrevCompletionMatch]; 00760 00761 if ( cut.contains( key ) ) 00762 { 00763 if ( emitSignals() ) 00764 emit textRotation( KCompletionBase::PrevCompletionMatch ); 00765 if ( handleSignals() ) 00766 rotateText( KCompletionBase::PrevCompletionMatch ); 00767 return; 00768 } 00769 00770 // Handles next match 00771 if ( keys[NextCompletionMatch].isNull() ) 00772 cut = KStdAccel::shortcut(KStdAccel::NextCompletion); 00773 else 00774 cut = keys[NextCompletionMatch]; 00775 00776 if ( cut.contains( key ) ) 00777 { 00778 if ( emitSignals() ) 00779 emit textRotation( KCompletionBase::NextCompletionMatch ); 00780 if ( handleSignals() ) 00781 rotateText( KCompletionBase::NextCompletionMatch ); 00782 return; 00783 } 00784 } 00785 00786 // substring completion 00787 if ( compObj() ) 00788 { 00789 KShortcut cut; 00790 if ( keys[SubstringCompletion].isNull() ) 00791 cut = KStdAccel::shortcut(KStdAccel::SubstringCompletion); 00792 else 00793 cut = keys[SubstringCompletion]; 00794 00795 if ( cut.contains( key ) ) 00796 { 00797 if ( emitSignals() ) 00798 emit substringCompletion( text() ); 00799 if ( handleSignals() ) 00800 { 00801 setCompletedItems( compObj()->substringCompletion(text())); 00802 e->accept(); 00803 } 00804 return; 00805 } 00806 } 00807 } 00808 00809 uint selectedLength = selectedText().length(); 00810 00811 // Let QLineEdit handle any other keys events. 00812 QLineEdit::keyPressEvent ( e ); 00813 00814 if ( selectedLength != selectedText().length() ) 00815 slotRestoreSelectionColors(); // and set userSelection to true 00816 } 00817 00818 void KLineEdit::mouseDoubleClickEvent( QMouseEvent* e ) 00819 { 00820 if ( e->button() == Qt::LeftButton ) 00821 { 00822 possibleTripleClick=true; 00823 QTimer::singleShot( QApplication::doubleClickInterval(),this, 00824 SLOT(tripleClickTimeout()) ); 00825 } 00826 QLineEdit::mouseDoubleClickEvent( e ); 00827 } 00828 00829 void KLineEdit::mousePressEvent( QMouseEvent* e ) 00830 { 00831 if ( possibleTripleClick && e->button() == Qt::LeftButton ) 00832 { 00833 selectAll(); 00834 e->accept(); 00835 return; 00836 } 00837 QLineEdit::mousePressEvent( e ); 00838 } 00839 00840 void KLineEdit::mouseReleaseEvent( QMouseEvent* e ) 00841 { 00842 QLineEdit::mouseReleaseEvent( e ); 00843 if (QApplication::clipboard()->supportsSelection() ) { 00844 if ( e->button() == LeftButton ) { 00845 // Fix copying of squeezed text if needed 00846 copySqueezedText( false ); 00847 } 00848 } 00849 } 00850 00851 void KLineEdit::tripleClickTimeout() 00852 { 00853 possibleTripleClick=false; 00854 } 00855 00856 void KLineEdit::contextMenuEvent( QContextMenuEvent * e ) 00857 { 00858 if ( m_bEnableMenu ) 00859 QLineEdit::contextMenuEvent( e ); 00860 } 00861 00862 QPopupMenu *KLineEdit::createPopupMenu() 00863 { 00864 enum { IdUndo, IdRedo, IdSep1, IdCut, IdCopy, IdPaste, IdClear, IdSep2, IdSelectAll }; 00865 00866 QPopupMenu *popup = QLineEdit::createPopupMenu(); 00867 00868 int id = popup->idAt(0); 00869 popup->changeItem( id - IdUndo, SmallIconSet("undo"), popup->text( id - IdUndo) ); 00870 popup->changeItem( id - IdRedo, SmallIconSet("redo"), popup->text( id - IdRedo) ); 00871 popup->changeItem( id - IdCut, SmallIconSet("editcut"), popup->text( id - IdCut) ); 00872 popup->changeItem( id - IdCopy, SmallIconSet("editcopy"), popup->text( id - IdCopy) ); 00873 popup->changeItem( id - IdPaste, SmallIconSet("editpaste"), popup->text( id - IdPaste) ); 00874 popup->changeItem( id - IdClear, SmallIconSet("editclear"), popup->text( id - IdClear) ); 00875 00876 // If a completion object is present and the input 00877 // widget is not read-only, show the Text Completion 00878 // menu item. 00879 if ( compObj() && !isReadOnly() && kapp->authorize("lineedit_text_completion") ) 00880 { 00881 QPopupMenu *subMenu = new QPopupMenu( popup ); 00882 connect( subMenu, SIGNAL( activated( int ) ), 00883 this, SLOT( completionMenuActivated( int ) ) ); 00884 00885 popup->insertSeparator(); 00886 popup->insertItem( SmallIconSet("completion"), i18n("Text Completion"), 00887 subMenu ); 00888 00889 subMenu->insertItem( i18n("None"), NoCompletion ); 00890 subMenu->insertItem( i18n("Manual"), ShellCompletion ); 00891 subMenu->insertItem( i18n("Automatic"), AutoCompletion ); 00892 subMenu->insertItem( i18n("Dropdown List"), PopupCompletion ); 00893 subMenu->insertItem( i18n("Short Automatic"), ShortAutoCompletion ); 00894 subMenu->insertItem( i18n("Dropdown List && Automatic"), PopupAutoCompletion ); 00895 00896 subMenu->setAccel( KStdAccel::completion(), ShellCompletion ); 00897 00898 KGlobalSettings::Completion mode = completionMode(); 00899 subMenu->setItemChecked( NoCompletion, 00900 mode == KGlobalSettings::CompletionNone ); 00901 subMenu->setItemChecked( ShellCompletion, 00902 mode == KGlobalSettings::CompletionShell ); 00903 subMenu->setItemChecked( PopupCompletion, 00904 mode == KGlobalSettings::CompletionPopup ); 00905 subMenu->setItemChecked( AutoCompletion, 00906 mode == KGlobalSettings::CompletionAuto ); 00907 subMenu->setItemChecked( ShortAutoCompletion, 00908 mode == KGlobalSettings::CompletionMan ); 00909 subMenu->setItemChecked( PopupAutoCompletion, 00910 mode == KGlobalSettings::CompletionPopupAuto ); 00911 if ( mode != KGlobalSettings::completionMode() ) 00912 { 00913 subMenu->insertSeparator(); 00914 subMenu->insertItem( i18n("Default"), Default ); 00915 } 00916 } 00917 00918 // ### do we really need this? Yes, Please do not remove! This 00919 // allows applications to extend the popup menu without having to 00920 // inherit from this class! (DA) 00921 emit aboutToShowContextMenu( popup ); 00922 00923 return popup; 00924 } 00925 00926 void KLineEdit::completionMenuActivated( int id ) 00927 { 00928 KGlobalSettings::Completion oldMode = completionMode(); 00929 00930 switch ( id ) 00931 { 00932 case Default: 00933 setCompletionMode( KGlobalSettings::completionMode() ); 00934 break; 00935 case NoCompletion: 00936 setCompletionMode( KGlobalSettings::CompletionNone ); 00937 break; 00938 case AutoCompletion: 00939 setCompletionMode( KGlobalSettings::CompletionAuto ); 00940 break; 00941 case ShortAutoCompletion: 00942 setCompletionMode( KGlobalSettings::CompletionMan ); 00943 break; 00944 case ShellCompletion: 00945 setCompletionMode( KGlobalSettings::CompletionShell ); 00946 break; 00947 case PopupCompletion: 00948 setCompletionMode( KGlobalSettings::CompletionPopup ); 00949 break; 00950 case PopupAutoCompletion: 00951 setCompletionMode( KGlobalSettings::CompletionPopupAuto ); 00952 break; 00953 default: 00954 return; 00955 } 00956 00957 if ( oldMode != completionMode() ) 00958 { 00959 if ( (oldMode == KGlobalSettings::CompletionPopup || 00960 oldMode == KGlobalSettings::CompletionPopupAuto ) && 00961 d->completionBox && d->completionBox->isVisible() ) 00962 d->completionBox->hide(); 00963 emit completionModeChanged( completionMode() ); 00964 } 00965 } 00966 00967 void KLineEdit::drawContents( QPainter *p ) 00968 { 00969 QLineEdit::drawContents( p ); 00970 00971 if ( d->drawClickMsg && !hasFocus() ) { 00972 QPen tmp = p->pen(); 00973 p->setPen( palette().color( QPalette::Disabled, QColorGroup::Text ) ); 00974 QRect cr = contentsRect(); 00975 00976 // Add two pixel margin on the left side 00977 cr.rLeft() += 3; 00978 p->drawText( cr, AlignAuto | AlignVCenter, d->clickMessage ); 00979 p->setPen( tmp ); 00980 } 00981 } 00982 00983 void KLineEdit::dropEvent(QDropEvent *e) 00984 { 00985 d->drawClickMsg = false; 00986 KURL::List urlList; 00987 if( d->handleURLDrops && KURLDrag::decode( e, urlList ) ) 00988 { 00989 QString dropText = text(); 00990 KURL::List::ConstIterator it; 00991 for( it = urlList.begin() ; it != urlList.end() ; ++it ) 00992 { 00993 if(!dropText.isEmpty()) 00994 dropText+=' '; 00995 00996 dropText += (*it).prettyURL(); 00997 } 00998 00999 validateAndSet( dropText, dropText.length(), 0, 0); 01000 01001 e->accept(); 01002 } 01003 else 01004 QLineEdit::dropEvent(e); 01005 } 01006 01007 bool KLineEdit::eventFilter( QObject* o, QEvent* ev ) 01008 { 01009 if( o == this ) 01010 { 01011 KCursor::autoHideEventFilter( this, ev ); 01012 if ( ev->type() == QEvent::AccelOverride ) 01013 { 01014 QKeyEvent *e = static_cast<QKeyEvent *>( ev ); 01015 if (overrideAccel (e)) 01016 { 01017 e->accept(); 01018 return true; 01019 } 01020 } 01021 else if( ev->type() == QEvent::KeyPress ) 01022 { 01023 QKeyEvent *e = static_cast<QKeyEvent *>( ev ); 01024 01025 if( e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter ) 01026 { 01027 bool trap = d->completionBox && d->completionBox->isVisible(); 01028 01029 bool stopEvent = trap || (d->grabReturnKeyEvents && 01030 (e->state() == NoButton || 01031 e->state() == Keypad)); 01032 01033 // Qt will emit returnPressed() itself if we return false 01034 if ( stopEvent ) 01035 { 01036 emit QLineEdit::returnPressed(); 01037 e->accept (); 01038 } 01039 01040 emit returnPressed( displayText() ); 01041 01042 if ( trap ) 01043 { 01044 d->completionBox->hide(); 01045 deselect(); 01046 setCursorPosition(text().length()); 01047 } 01048 01049 // Eat the event if the user asked for it, or if a completionbox was visible 01050 return stopEvent; 01051 } 01052 } 01053 } 01054 return QLineEdit::eventFilter( o, ev ); 01055 } 01056 01057 01058 void KLineEdit::setURLDropsEnabled(bool enable) 01059 { 01060 d->handleURLDrops=enable; 01061 } 01062 01063 bool KLineEdit::isURLDropsEnabled() const 01064 { 01065 return d->handleURLDrops; 01066 } 01067 01068 void KLineEdit::setTrapReturnKey( bool grab ) 01069 { 01070 d->grabReturnKeyEvents = grab; 01071 } 01072 01073 bool KLineEdit::trapReturnKey() const 01074 { 01075 return d->grabReturnKeyEvents; 01076 } 01077 01078 void KLineEdit::setURL( const KURL& url ) 01079 { 01080 setText( url.prettyURL() ); 01081 } 01082 01083 void KLineEdit::setCompletionBox( KCompletionBox *box ) 01084 { 01085 if ( d->completionBox ) 01086 return; 01087 01088 d->completionBox = box; 01089 if ( handleSignals() ) 01090 { 01091 connect( d->completionBox, SIGNAL(highlighted( const QString& )), 01092 SLOT(setTextWorkaround( const QString& )) ); 01093 connect( d->completionBox, SIGNAL(userCancelled( const QString& )), 01094 SLOT(userCancelled( const QString& )) ); 01095 01096 // TODO: we need our own slot, and to call setModified(true) if Qt4 has that. 01097 connect( d->completionBox, SIGNAL( activated( const QString& )), 01098 SIGNAL(completionBoxActivated( const QString& )) ); 01099 } 01100 } 01101 01102 void KLineEdit::userCancelled(const QString & cancelText) 01103 { 01104 if ( completionMode() != KGlobalSettings::CompletionPopupAuto ) 01105 { 01106 // TODO: this sets modified==false. But maybe it was true before... 01107 setText(cancelText); 01108 } 01109 else if (hasSelectedText() ) 01110 { 01111 if (d->userSelection) 01112 deselect(); 01113 else 01114 { 01115 d->autoSuggest=false; 01116 int start,end; 01117 getSelection(&start, &end); 01118 QString s=text().remove(start, end-start+1); 01119 validateAndSet(s,start,s.length(),s.length()); 01120 d->autoSuggest=true; 01121 } 01122 } 01123 } 01124 01125 bool KLineEdit::overrideAccel (const QKeyEvent* e) 01126 { 01127 KShortcut scKey; 01128 01129 KKey key( e ); 01130 KeyBindingMap keys = getKeyBindings(); 01131 01132 if (keys[TextCompletion].isNull()) 01133 scKey = KStdAccel::shortcut(KStdAccel::TextCompletion); 01134 else 01135 scKey = keys[TextCompletion]; 01136 01137 if (scKey.contains( key )) 01138 return true; 01139 01140 if (keys[NextCompletionMatch].isNull()) 01141 scKey = KStdAccel::shortcut(KStdAccel::NextCompletion); 01142 else 01143 scKey = keys[NextCompletionMatch]; 01144 01145 if (scKey.contains( key )) 01146 return true; 01147 01148 if (keys[PrevCompletionMatch].isNull()) 01149 scKey = KStdAccel::shortcut(KStdAccel::PrevCompletion); 01150 else 01151 scKey = keys[PrevCompletionMatch]; 01152 01153 if (scKey.contains( key )) 01154 return true; 01155 01156 // Override all the text manupilation accelerators... 01157 if ( KStdAccel::copy().contains( key ) ) 01158 return true; 01159 else if ( KStdAccel::paste().contains( key ) ) 01160 return true; 01161 else if ( KStdAccel::cut().contains( key ) ) 01162 return true; 01163 else if ( KStdAccel::undo().contains( key ) ) 01164 return true; 01165 else if ( KStdAccel::redo().contains( key ) ) 01166 return true; 01167 else if (KStdAccel::deleteWordBack().contains( key )) 01168 return true; 01169 else if (KStdAccel::deleteWordForward().contains( key )) 01170 return true; 01171 else if (KStdAccel::forwardWord().contains( key )) 01172 return true; 01173 else if (KStdAccel::backwardWord().contains( key )) 01174 return true; 01175 else if (KStdAccel::beginningOfLine().contains( key )) 01176 return true; 01177 else if (KStdAccel::endOfLine().contains( key )) 01178 return true; 01179 01180 if (d->completionBox && d->completionBox->isVisible ()) 01181 { 01182 int key = e->key(); 01183 ButtonState state = e->state(); 01184 if ((key == Key_Backtab || key == Key_Tab) && 01185 (state == NoButton || (state & ShiftButton))) 01186 { 01187 return true; 01188 } 01189 } 01190 01191 01192 return false; 01193 } 01194 01195 void KLineEdit::setCompletedItems( const QStringList& items ) 01196 { 01197 setCompletedItems( items, true ); 01198 } 01199 01200 void KLineEdit::setCompletedItems( const QStringList& items, bool autoSuggest ) 01201 { 01202 QString txt; 01203 if ( d->completionBox && d->completionBox->isVisible() ) { 01204 // The popup is visible already - do the matching on the initial string, 01205 // not on the currently selected one. 01206 txt = completionBox()->cancelledText(); 01207 } else { 01208 txt = text(); 01209 } 01210 01211 if ( !items.isEmpty() && 01212 !(items.count() == 1 && txt == items.first()) ) 01213 { 01214 // create completion box if non-existent 01215 completionBox(); 01216 01217 if ( d->completionBox->isVisible() ) 01218 { 01219 bool wasSelected = d->completionBox->isSelected( d->completionBox->currentItem() ); 01220 const QString currentSelection = d->completionBox->currentText(); 01221 d->completionBox->setItems( items ); 01222 QListBoxItem* item = d->completionBox->findItem( currentSelection, Qt::ExactMatch ); 01223 // If no item is selected, that means the listbox hasn't been manipulated by the user yet, 01224 // because it's not possible otherwise to have no selected item. In such case make 01225 // always the first item current and unselected, so that the current item doesn't jump. 01226 if( !item || !wasSelected ) 01227 { 01228 wasSelected = false; 01229 item = d->completionBox->item( 0 ); 01230 } 01231 if ( item ) 01232 { 01233 d->completionBox->blockSignals( true ); 01234 d->completionBox->setCurrentItem( item ); 01235 d->completionBox->setSelected( item, wasSelected ); 01236 d->completionBox->blockSignals( false ); 01237 } 01238 } 01239 else // completion box not visible yet -> show it 01240 { 01241 if ( !txt.isEmpty() ) 01242 d->completionBox->setCancelledText( txt ); 01243 d->completionBox->setItems( items ); 01244 d->completionBox->popup(); 01245 } 01246 01247 if ( d->autoSuggest && autoSuggest ) 01248 { 01249 int index = items.first().find( txt ); 01250 QString newText = items.first().mid( index ); 01251 setUserSelection(false); 01252 setCompletedText(newText,true); 01253 } 01254 } 01255 else 01256 { 01257 if ( d->completionBox && d->completionBox->isVisible() ) 01258 d->completionBox->hide(); 01259 } 01260 } 01261 01262 KCompletionBox * KLineEdit::completionBox( bool create ) 01263 { 01264 if ( create && !d->completionBox ) { 01265 setCompletionBox( new KCompletionBox( this, "completion box" ) ); 01266 d->completionBox->setFont(font()); 01267 } 01268 01269 return d->completionBox; 01270 } 01271 01272 void KLineEdit::setCompletionObject( KCompletion* comp, bool hsig ) 01273 { 01274 KCompletion *oldComp = compObj(); 01275 if ( oldComp && handleSignals() ) 01276 disconnect( oldComp, SIGNAL( matches( const QStringList& )), 01277 this, SLOT( setCompletedItems( const QStringList& ))); 01278 01279 if ( comp && hsig ) 01280 connect( comp, SIGNAL( matches( const QStringList& )), 01281 this, SLOT( setCompletedItems( const QStringList& ))); 01282 01283 KCompletionBase::setCompletionObject( comp, hsig ); 01284 } 01285 01286 // QWidget::create() turns off mouse-Tracking which would break auto-hiding 01287 void KLineEdit::create( WId id, bool initializeWindow, bool destroyOldWindow ) 01288 { 01289 QLineEdit::create( id, initializeWindow, destroyOldWindow ); 01290 KCursor::setAutoHideCursor( this, true, true ); 01291 } 01292 01293 void KLineEdit::setUserSelection(bool userSelection) 01294 { 01295 QPalette p = palette(); 01296 01297 if (userSelection) 01298 { 01299 p.setColor(QColorGroup::Highlight, d->previousHighlightColor); 01300 p.setColor(QColorGroup::HighlightedText, d->previousHighlightedTextColor); 01301 } 01302 else 01303 { 01304 QColor color=p.color(QPalette::Disabled, QColorGroup::Text); 01305 p.setColor(QColorGroup::HighlightedText, color); 01306 color=p.color(QPalette::Active, QColorGroup::Base); 01307 p.setColor(QColorGroup::Highlight, color); 01308 } 01309 01310 d->userSelection=userSelection; 01311 setPalette(p); 01312 } 01313 01314 void KLineEdit::slotRestoreSelectionColors() 01315 { 01316 if (d->disableRestoreSelection) 01317 return; 01318 01319 setUserSelection(true); 01320 } 01321 01322 void KLineEdit::clear() 01323 { 01324 setText( QString::null ); 01325 } 01326 01327 void KLineEdit::setTextWorkaround( const QString& text ) 01328 { 01329 setText( text ); 01330 end( false ); // force cursor at end 01331 } 01332 01333 QString KLineEdit::originalText() const 01334 { 01335 if ( d->enableSqueezedText && isReadOnly() ) 01336 return d->squeezedText; 01337 01338 return text(); 01339 } 01340 01341 void KLineEdit::focusInEvent( QFocusEvent* ev) 01342 { 01343 if ( d->drawClickMsg ) { 01344 d->drawClickMsg = false; 01345 update(); 01346 } 01347 01348 // Don't selectAll() in QLineEdit::focusInEvent if selection exists 01349 if ( ev->reason() == QFocusEvent::Tab && inputMask().isNull() && hasSelectedText() ) 01350 return; 01351 01352 QLineEdit::focusInEvent(ev); 01353 } 01354 01355 void KLineEdit::focusOutEvent( QFocusEvent* ev) 01356 { 01357 if ( text().isEmpty() && !d->clickMessage.isEmpty() ) { 01358 d->drawClickMsg = true; 01359 update(); 01360 } 01361 QLineEdit::focusOutEvent( ev ); 01362 } 01363 01364 bool KLineEdit::autoSuggest() const 01365 { 01366 return d->autoSuggest; 01367 } 01368 01369 void KLineEdit::setClickMessage( const QString &msg ) 01370 { 01371 d->clickMessage = msg; 01372 update(); 01373 } 01374 01375 QString KLineEdit::clickMessage() const 01376 { 01377 return d->clickMessage; 01378 } 01379 01380 01381 void KLineEdit::virtual_hook( int id, void* data ) 01382 { KCompletionBase::virtual_hook( id, data ); }