KDEUI
kcompletionbox.cpp
Go to the documentation of this file.
00001 /* This file is part of the KDE libraries 00002 00003 Copyright (c) 2000,2001,2002 Carsten Pfeiffer <pfeiffer@kde.org> 00004 Copyright (c) 2000 Stefan Schimanski <1Stein@gmx.de> 00005 Copyright (c) 2000,2001,2002,2003,2004 Dawit Alemayehu <adawit@kde.org> 00006 00007 This library is free software; you can redistribute it and/or 00008 modify it under the terms of the GNU Library General Public 00009 License (LGPL) as published by the Free Software Foundation; either 00010 version 2 of the License, or (at your option) any later version. 00011 00012 This library is distributed in the hope that it will be useful, 00013 but WITHOUT ANY WARRANTY; without even the implied warranty of 00014 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00015 Library General Public License for more details. 00016 00017 You should have received a copy of the GNU Library General Public License 00018 along with this library; see the file COPYING.LIB. If not, write to 00019 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00020 Boston, MA 02110-1301, USA. 00021 */ 00022 00023 00024 #include "kcompletionbox.h" 00025 #include "klineedit.h" 00026 00027 #include <QtCore/QEvent> 00028 #include <QtGui/QApplication> 00029 #include <QtGui/QComboBox> 00030 #include <QtGui/QStyle> 00031 #include <QtGui/QScrollBar> 00032 #include <QtGui/QKeyEvent> 00033 00034 #include <kdebug.h> 00035 #include <kconfig.h> 00036 #include <kglobalsettings.h> 00037 00038 class KCompletionBox::KCompletionBoxPrivate 00039 { 00040 public: 00041 QWidget *m_parent; // necessary to set the focus back 00042 QString cancelText; 00043 bool tabHandling : 1; 00044 bool upwardBox : 1; 00045 bool emitSelected : 1; 00046 }; 00047 00048 KCompletionBox::KCompletionBox( QWidget *parent ) 00049 :KListWidget( parent), d(new KCompletionBoxPrivate) 00050 { 00051 d->m_parent = parent; 00052 d->tabHandling = true; 00053 d->upwardBox = false; 00054 d->emitSelected = true; 00055 00056 setWindowFlags( Qt::ToolTip ); // calls setVisible, so must be done after initializations 00057 00058 setLineWidth( 1 ); 00059 setFrameStyle( QFrame::Box | QFrame::Plain ); 00060 00061 setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); 00062 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 00063 00064 connect( this, SIGNAL( itemDoubleClicked( QListWidgetItem * )), 00065 SLOT( slotActivated( QListWidgetItem * )) ); 00066 connect( this, SIGNAL( itemClicked( QListWidgetItem * )), 00067 SLOT( slotItemClicked( QListWidgetItem * )) ); 00068 } 00069 00070 KCompletionBox::~KCompletionBox() 00071 { 00072 d->m_parent = 0L; 00073 delete d; 00074 } 00075 00076 QStringList KCompletionBox::items() const 00077 { 00078 QStringList list; 00079 00080 for (int i = 0 ; i < count() ; i++) 00081 { 00082 const QListWidgetItem* currItem = item(i); 00083 00084 list.append(currItem->text()); 00085 } 00086 00087 return list; 00088 } 00089 00090 void KCompletionBox::slotActivated( QListWidgetItem *item ) 00091 { 00092 if ( !item ) 00093 return; 00094 00095 hide(); 00096 emit activated( item->text() ); 00097 } 00098 00099 bool KCompletionBox::eventFilter( QObject *o, QEvent *e ) 00100 { 00101 int type = e->type(); 00102 QWidget *wid = qobject_cast<QWidget*>(o); 00103 00104 if (o == this) { 00105 return false; 00106 } 00107 00108 if (wid && wid == d->m_parent && 00109 (type == QEvent::Move || type == QEvent::Resize)) { 00110 hide(); 00111 return false; 00112 } 00113 00114 if (wid && (wid->windowFlags() & Qt::Window) && 00115 type == QEvent::Move && wid == d->m_parent->window()) { 00116 hide(); 00117 return false; 00118 } 00119 00120 if (type == QEvent::MouseButtonPress && (wid && !isAncestorOf(wid))) { 00121 if (!d->emitSelected && currentItem() && !qobject_cast<QScrollBar*>(o)) { 00122 Q_ASSERT(currentItem()); 00123 emit currentTextChanged(currentItem()->text() ); 00124 } 00125 hide(); 00126 e->accept(); 00127 return true; 00128 } 00129 00130 if (wid && wid->isAncestorOf(d->m_parent) && isVisible()) { 00131 if ( type == QEvent::KeyPress ) { 00132 QKeyEvent *ev = static_cast<QKeyEvent *>( e ); 00133 switch ( ev->key() ) { 00134 case Qt::Key_Backtab: 00135 if ( d->tabHandling && (ev->modifiers() == Qt::NoButton || 00136 (ev->modifiers() & Qt::ShiftModifier)) ) { 00137 up(); 00138 ev->accept(); 00139 return true; 00140 } 00141 break; 00142 case Qt::Key_Tab: 00143 if ( d->tabHandling && (ev->modifiers() == Qt::NoButton) ) { 00144 down(); 00145 // #65877: Key_Tab should complete using the first 00146 // (or selected) item, and then offer completions again 00147 if (count() == 1) { 00148 KLineEdit* parent = qobject_cast<KLineEdit*>(d->m_parent); 00149 if (parent) { 00150 parent->doCompletion(currentItem()->text()); 00151 } else { 00152 hide(); 00153 } 00154 } 00155 ev->accept(); 00156 return true; 00157 } 00158 break; 00159 case Qt::Key_Down: 00160 down(); 00161 ev->accept(); 00162 return true; 00163 case Qt::Key_Up: 00164 // If there is no selected item and we've popped up above 00165 // our parent, select the first item when they press up. 00166 if ( !selectedItems().isEmpty() || 00167 mapToGlobal( QPoint( 0, 0 ) ).y() > 00168 d->m_parent->mapToGlobal( QPoint( 0, 0 ) ).y() ) 00169 up(); 00170 else 00171 down(); 00172 ev->accept(); 00173 return true; 00174 case Qt::Key_PageUp: 00175 pageUp(); 00176 ev->accept(); 00177 return true; 00178 case Qt::Key_PageDown: 00179 pageDown(); 00180 ev->accept(); 00181 return true; 00182 case Qt::Key_Escape: 00183 canceled(); 00184 ev->accept(); 00185 return true; 00186 case Qt::Key_Enter: 00187 case Qt::Key_Return: 00188 if ( ev->modifiers() & Qt::ShiftModifier ) { 00189 hide(); 00190 ev->accept(); // Consume the Enter event 00191 return true; 00192 } 00193 break; 00194 case Qt::Key_End: 00195 if ( ev->modifiers() & Qt::ControlModifier ) 00196 { 00197 end(); 00198 ev->accept(); 00199 return true; 00200 } 00201 break; 00202 case Qt::Key_Home: 00203 if ( ev->modifiers() & Qt::ControlModifier ) 00204 { 00205 home(); 00206 ev->accept(); 00207 return true; 00208 } 00209 default: 00210 break; 00211 } 00212 } else if ( type == QEvent::ShortcutOverride ) { 00213 // Override any accelerators that match 00214 // the key sequences we use here... 00215 QKeyEvent *ev = static_cast<QKeyEvent *>( e ); 00216 switch ( ev->key() ) { 00217 case Qt::Key_Down: 00218 case Qt::Key_Up: 00219 case Qt::Key_PageUp: 00220 case Qt::Key_PageDown: 00221 case Qt::Key_Escape: 00222 case Qt::Key_Enter: 00223 case Qt::Key_Return: 00224 ev->accept(); 00225 return true; 00226 break; 00227 case Qt::Key_Tab: 00228 case Qt::Key_Backtab: 00229 if ( ev->modifiers() == Qt::NoButton || 00230 (ev->modifiers() & Qt::ShiftModifier)) 00231 { 00232 ev->accept(); 00233 return true; 00234 } 00235 break; 00236 case Qt::Key_Home: 00237 case Qt::Key_End: 00238 if ( ev->modifiers() & Qt::ControlModifier ) 00239 { 00240 ev->accept(); 00241 return true; 00242 } 00243 break; 00244 default: 00245 break; 00246 } 00247 } else if ( type == QEvent::FocusOut ) { 00248 QFocusEvent* event = static_cast<QFocusEvent*>( e ); 00249 if (event->reason() != Qt::PopupFocusReason 00250 #ifdef Q_WS_WIN 00251 && (event->reason() != Qt::ActiveWindowFocusReason || QApplication::activeWindow() != this) 00252 #endif 00253 ) 00254 hide(); 00255 } 00256 } 00257 00258 return KListWidget::eventFilter( o, e ); 00259 } 00260 00261 void KCompletionBox::popup() 00262 { 00263 if ( count() == 0 ) 00264 hide(); 00265 else { 00266 bool block = signalsBlocked(); 00267 blockSignals( true ); 00268 setCurrentRow( -1 ); 00269 blockSignals( block ); 00270 clearSelection(); 00271 if ( !isVisible() ) 00272 show(); 00273 else if ( size().height() != sizeHint().height() ) 00274 sizeAndPosition(); 00275 } 00276 } 00277 00278 void KCompletionBox::sizeAndPosition() 00279 { 00280 int currentGeom = height(); 00281 QPoint currentPos = pos(); 00282 QRect geom = calculateGeometry(); 00283 resize( geom.size() ); 00284 00285 int x = currentPos.x(), y = currentPos.y(); 00286 if ( d->m_parent ) { 00287 if ( !isVisible() ) { 00288 QPoint orig = globalPositionHint(); 00289 QRect screenSize = KGlobalSettings::desktopGeometry(orig); 00290 00291 x = orig.x() + geom.x(); 00292 y = orig.y() + geom.y(); 00293 00294 if ( x + width() > screenSize.right() ) 00295 x = screenSize.right() - width(); 00296 if (y + height() > screenSize.bottom() ) { 00297 y = y - height() - d->m_parent->height(); 00298 d->upwardBox = true; 00299 } 00300 } 00301 else { 00302 // Are we above our parent? If so we must keep bottom edge anchored. 00303 if (d->upwardBox) 00304 y += (currentGeom-height()); 00305 } 00306 move( x, y); 00307 } 00308 } 00309 00310 QPoint KCompletionBox::globalPositionHint() const 00311 { 00312 if (!d->m_parent) 00313 return QPoint(); 00314 return d->m_parent->mapToGlobal( QPoint(0, d->m_parent->height()) ); 00315 } 00316 00317 void KCompletionBox::setVisible( bool visible ) 00318 { 00319 if (visible) { 00320 d->upwardBox = false; 00321 if ( d->m_parent ) { 00322 sizeAndPosition(); 00323 qApp->installEventFilter( this ); 00324 } 00325 00326 // ### we shouldn't need to call this, but without this, the scrollbars 00327 // are pretty b0rked. 00328 //triggerUpdate( true ); 00329 00330 // Workaround for I'm not sure whose bug - if this KCompletionBox' parent 00331 // is in a layout, that layout will detect inserting new child (posted 00332 // ChildInserted event), and will trigger relayout (post LayoutHint event). 00333 // QWidget::show() sends also posted ChildInserted events for the parent, 00334 // and later all LayoutHint events, which causes layout updating. 00335 // The problem is, KCompletionBox::eventFilter() detects resizing 00336 // of the parent, and calls hide() - and this hide() happen in the middle 00337 // of show(), causing inconsistent state. I'll try to submit a Qt patch too. 00338 qApp->sendPostedEvents(); 00339 } else { 00340 if ( d->m_parent ) 00341 qApp->removeEventFilter( this ); 00342 d->cancelText.clear(); 00343 } 00344 00345 KListWidget::setVisible(visible); 00346 } 00347 00348 QRect KCompletionBox::calculateGeometry() const 00349 { 00350 QRect visualRect; 00351 if (count() == 0 || !(visualRect = visualItemRect(item(0))).isValid()) 00352 return QRect(); 00353 00354 int x = 0, y = 0; 00355 int ih = visualRect.height(); 00356 int h = qMin( 15 * ih, (int) count() * ih ) + 2*frameWidth(); 00357 00358 int w = (d->m_parent) ? d->m_parent->width() : KListWidget::minimumSizeHint().width(); 00359 w = qMax( KListWidget::minimumSizeHint().width(), w ); 00360 00361 //### M.O.: Qt4 doesn't actually honor SC_ComboBoxListBoxPopup ??? 00362 #if 0 00363 //If we're inside a combox, Qt by default makes the dropdown 00364 // as wide as the combo, and gives the style a chance 00365 // to adjust it. Do that here as well, for consistency 00366 const QObject* combo; 00367 if ( d->m_parent && (combo = d->m_parent->parent() ) && 00368 qobject_cast<QComboBox*>(combo) ) 00369 { 00370 const QComboBox* cb = static_cast<const QComboBox*>(combo); 00371 00372 //Expand to the combo width 00373 w = qMax( w, cb->width() ); 00374 00375 QPoint parentCorner = d->m_parent->mapToGlobal(QPoint(0, 0)); 00376 QPoint comboCorner = cb->mapToGlobal(QPoint(0, 0)); 00377 00378 //We need to adjust our horizontal position to also be WRT to the combo 00379 x += comboCorner.x() - parentCorner.x(); 00380 00381 //The same with vertical one 00382 y += cb->height() - d->m_parent->height() + 00383 comboCorner.y() - parentCorner.y(); 00384 00385 //Ask the style to refine this a bit 00386 QRect styleAdj = style().querySubControlMetrics(QStyle::CC_ComboBox, 00387 cb, QStyle::SC_ComboBoxListBoxPopup, 00388 QStyleOption(x, y, w, h)); 00389 //QCommonStyle returns QRect() by default, so this is what we get if the 00390 //style doesn't implement this 00391 if (!styleAdj.isNull()) 00392 return styleAdj; 00393 00394 } 00395 #endif 00396 return QRect(x, y, w, h); 00397 } 00398 00399 QSize KCompletionBox::sizeHint() const 00400 { 00401 return calculateGeometry().size(); 00402 } 00403 00404 void KCompletionBox::down() 00405 { 00406 const int i = currentRow(); 00407 if (i < count() - 1) { 00408 setCurrentRow(i + 1); 00409 } 00410 } 00411 00412 void KCompletionBox::up() 00413 { 00414 const int i = currentRow(); 00415 if (i > 0) { 00416 setCurrentRow(i - 1); 00417 } 00418 } 00419 00420 void KCompletionBox::pageDown() 00421 { 00422 //int i = currentItem() + numItemsVisible(); 00423 //i = i > (int)count() - 1 ? (int)count() - 1 : i; 00424 //setCurrentRow( i ); 00425 moveCursor(QAbstractItemView::MovePageDown , Qt::NoModifier); 00426 } 00427 00428 void KCompletionBox::pageUp() 00429 { 00430 //int i = currentItem() - numItemsVisible(); 00431 //i = i < 0 ? 0 : i; 00432 //setCurrentRow( i ); 00433 00434 moveCursor(QAbstractItemView::MovePageUp , Qt::NoModifier); 00435 } 00436 00437 void KCompletionBox::home() 00438 { 00439 setCurrentRow( 0 ); 00440 } 00441 00442 void KCompletionBox::end() 00443 { 00444 setCurrentRow( count() -1 ); 00445 } 00446 00447 void KCompletionBox::setTabHandling( bool enable ) 00448 { 00449 d->tabHandling = enable; 00450 } 00451 00452 bool KCompletionBox::isTabHandling() const 00453 { 00454 return d->tabHandling; 00455 } 00456 00457 void KCompletionBox::setCancelledText( const QString& text ) 00458 { 00459 d->cancelText = text; 00460 } 00461 00462 QString KCompletionBox::cancelledText() const 00463 { 00464 return d->cancelText; 00465 } 00466 00467 void KCompletionBox::canceled() 00468 { 00469 if ( !d->cancelText.isNull() ) 00470 emit userCancelled( d->cancelText ); 00471 if ( isVisible() ) 00472 hide(); 00473 } 00474 00475 class KCompletionBoxItem : public QListWidgetItem 00476 { 00477 public: 00478 //Returns true if dirty. 00479 bool reuse( const QString& newText ) 00480 { 00481 if ( text() == newText ) 00482 return false; 00483 setText( newText ); 00484 return true; 00485 } 00486 }; 00487 00488 00489 void KCompletionBox::insertItems( const QStringList& items, int index ) 00490 { 00491 bool block = signalsBlocked(); 00492 blockSignals( true ); 00493 KListWidget::insertItems( index, items ); 00494 blockSignals( block ); 00495 setCurrentRow(-1); 00496 } 00497 00498 void KCompletionBox::setItems( const QStringList& items ) 00499 { 00500 bool block = signalsBlocked(); 00501 blockSignals( true ); 00502 00503 int rowIndex = 0; 00504 00505 if (!count()) { 00506 addItems(items); 00507 } else { 00508 // Keep track of whether we need to change anything, 00509 // so we can avoid a repaint for identical updates, 00510 // to reduce flicker 00511 bool dirty = false; 00512 00513 QStringList::ConstIterator it = items.constBegin(); 00514 const QStringList::ConstIterator itEnd = items.constEnd(); 00515 00516 for ( ; it != itEnd; ++it) { 00517 if ( rowIndex < count() ) { 00518 const bool changed = ((KCompletionBoxItem*)item(rowIndex))->reuse( *it ); 00519 dirty = dirty || changed; 00520 } else { 00521 dirty = true; 00522 // Inserting an item is a way of making this dirty 00523 addItem(*it); 00524 } 00525 rowIndex++; 00526 } 00527 00528 // If there is an unused item, mark as dirty -> less items now 00529 if (rowIndex < count()) { 00530 dirty = true; 00531 } 00532 00533 // remove unused items with an index >= rowIndex 00534 for ( ; rowIndex < count() ; ) { 00535 QListWidgetItem* item = takeItem(rowIndex); 00536 Q_ASSERT(item); 00537 delete item; 00538 } 00539 00540 //TODO KDE4 : Port me 00541 //if (dirty) 00542 // triggerUpdate( false ); 00543 } 00544 00545 if (isVisible() && size().height() != sizeHint().height()) 00546 sizeAndPosition(); 00547 00548 blockSignals(block); 00549 } 00550 00551 void KCompletionBox::slotItemClicked( QListWidgetItem *item ) 00552 { 00553 if ( item ) 00554 { 00555 hide(); 00556 emit currentTextChanged( item->text() ); 00557 emit activated( item->text() ); 00558 } 00559 } 00560 00561 void KCompletionBox::setActivateOnSelect(bool state) 00562 { 00563 d->emitSelected = state; 00564 } 00565 00566 bool KCompletionBox::activateOnSelect() const 00567 { 00568 return d->emitSelected; 00569 } 00570 00571 #include "kcompletionbox.moc"
KDE 4.6 API Reference