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 case Qt::Key_Tab: 00227 case Qt::Key_Backtab: 00228 if ( ev->modifiers() == Qt::NoButton || 00229 (ev->modifiers() & Qt::ShiftModifier)) 00230 { 00231 ev->accept(); 00232 return true; 00233 } 00234 break; 00235 case Qt::Key_Home: 00236 case Qt::Key_End: 00237 if ( ev->modifiers() & Qt::ControlModifier ) 00238 { 00239 ev->accept(); 00240 return true; 00241 } 00242 break; 00243 default: 00244 break; 00245 } 00246 } else if ( type == QEvent::FocusOut ) { 00247 QFocusEvent* event = static_cast<QFocusEvent*>( e ); 00248 if (event->reason() != Qt::PopupFocusReason 00249 #ifdef Q_WS_WIN 00250 && (event->reason() != Qt::ActiveWindowFocusReason || QApplication::activeWindow() != this) 00251 #endif 00252 ) 00253 hide(); 00254 } 00255 } 00256 00257 return KListWidget::eventFilter( o, e ); 00258 } 00259 00260 void KCompletionBox::popup() 00261 { 00262 if ( count() == 0 ) 00263 hide(); 00264 else { 00265 bool block = signalsBlocked(); 00266 blockSignals( true ); 00267 setCurrentRow( -1 ); 00268 blockSignals( block ); 00269 clearSelection(); 00270 if ( !isVisible() ) 00271 show(); 00272 else if ( size().height() != sizeHint().height() ) 00273 sizeAndPosition(); 00274 } 00275 } 00276 00277 void KCompletionBox::sizeAndPosition() 00278 { 00279 int currentGeom = height(); 00280 QPoint currentPos = pos(); 00281 QRect geom = calculateGeometry(); 00282 resize( geom.size() ); 00283 00284 int x = currentPos.x(), y = currentPos.y(); 00285 if ( d->m_parent ) { 00286 if ( !isVisible() ) { 00287 QPoint orig = globalPositionHint(); 00288 QRect screenSize = KGlobalSettings::desktopGeometry(orig); 00289 00290 x = orig.x() + geom.x(); 00291 y = orig.y() + geom.y(); 00292 00293 if ( x + width() > screenSize.right() ) 00294 x = screenSize.right() - width(); 00295 if (y + height() > screenSize.bottom() ) { 00296 y = y - height() - d->m_parent->height(); 00297 d->upwardBox = true; 00298 } 00299 } 00300 else { 00301 // Are we above our parent? If so we must keep bottom edge anchored. 00302 if (d->upwardBox) 00303 y += (currentGeom-height()); 00304 } 00305 move( x, y); 00306 } 00307 } 00308 00309 QPoint KCompletionBox::globalPositionHint() const 00310 { 00311 if (!d->m_parent) 00312 return QPoint(); 00313 return d->m_parent->mapToGlobal( QPoint(0, d->m_parent->height()) ); 00314 } 00315 00316 void KCompletionBox::setVisible( bool visible ) 00317 { 00318 if (visible) { 00319 d->upwardBox = false; 00320 if ( d->m_parent ) { 00321 sizeAndPosition(); 00322 qApp->installEventFilter( this ); 00323 } 00324 00325 // ### we shouldn't need to call this, but without this, the scrollbars 00326 // are pretty b0rked. 00327 //triggerUpdate( true ); 00328 00329 // Workaround for I'm not sure whose bug - if this KCompletionBox' parent 00330 // is in a layout, that layout will detect inserting new child (posted 00331 // ChildInserted event), and will trigger relayout (post LayoutHint event). 00332 // QWidget::show() sends also posted ChildInserted events for the parent, 00333 // and later all LayoutHint events, which causes layout updating. 00334 // The problem is, KCompletionBox::eventFilter() detects resizing 00335 // of the parent, and calls hide() - and this hide() happen in the middle 00336 // of show(), causing inconsistent state. I'll try to submit a Qt patch too. 00337 qApp->sendPostedEvents(); 00338 } else { 00339 if ( d->m_parent ) 00340 qApp->removeEventFilter( this ); 00341 d->cancelText.clear(); 00342 } 00343 00344 KListWidget::setVisible(visible); 00345 } 00346 00347 QRect KCompletionBox::calculateGeometry() const 00348 { 00349 QRect visualRect; 00350 if (count() == 0 || !(visualRect = visualItemRect(item(0))).isValid()) 00351 return QRect(); 00352 00353 int x = 0, y = 0; 00354 int ih = visualRect.height(); 00355 int h = qMin( 15 * ih, (int) count() * ih ) + 2*frameWidth(); 00356 00357 int w = (d->m_parent) ? d->m_parent->width() : KListWidget::minimumSizeHint().width(); 00358 w = qMax( KListWidget::minimumSizeHint().width(), w ); 00359 00360 //### M.O.: Qt4 doesn't actually honor SC_ComboBoxListBoxPopup ??? 00361 #if 0 00362 //If we're inside a combox, Qt by default makes the dropdown 00363 // as wide as the combo, and gives the style a chance 00364 // to adjust it. Do that here as well, for consistency 00365 const QObject* combo; 00366 if ( d->m_parent && (combo = d->m_parent->parent() ) && 00367 qobject_cast<QComboBox*>(combo) ) 00368 { 00369 const QComboBox* cb = static_cast<const QComboBox*>(combo); 00370 00371 //Expand to the combo width 00372 w = qMax( w, cb->width() ); 00373 00374 QPoint parentCorner = d->m_parent->mapToGlobal(QPoint(0, 0)); 00375 QPoint comboCorner = cb->mapToGlobal(QPoint(0, 0)); 00376 00377 //We need to adjust our horizontal position to also be WRT to the combo 00378 x += comboCorner.x() - parentCorner.x(); 00379 00380 //The same with vertical one 00381 y += cb->height() - d->m_parent->height() + 00382 comboCorner.y() - parentCorner.y(); 00383 00384 //Ask the style to refine this a bit 00385 QRect styleAdj = style().querySubControlMetrics(QStyle::CC_ComboBox, 00386 cb, QStyle::SC_ComboBoxListBoxPopup, 00387 QStyleOption(x, y, w, h)); 00388 //QCommonStyle returns QRect() by default, so this is what we get if the 00389 //style doesn't implement this 00390 if (!styleAdj.isNull()) 00391 return styleAdj; 00392 00393 } 00394 #endif 00395 return QRect(x, y, w, h); 00396 } 00397 00398 QSize KCompletionBox::sizeHint() const 00399 { 00400 return calculateGeometry().size(); 00401 } 00402 00403 void KCompletionBox::down() 00404 { 00405 const int row = currentRow(); 00406 const int lastRow = count() - 1; 00407 if (row < lastRow) { 00408 setCurrentRow(row + 1); 00409 return; 00410 } 00411 00412 if (lastRow > -1) { 00413 setCurrentRow(0); 00414 } 00415 } 00416 00417 void KCompletionBox::up() 00418 { 00419 const int row = currentRow(); 00420 if (row > 0) { 00421 setCurrentRow(row - 1); 00422 return; 00423 } 00424 00425 const int lastRow = count() - 1; 00426 if (lastRow > 0) { 00427 setCurrentRow(lastRow); 00428 } 00429 } 00430 00431 void KCompletionBox::pageDown() 00432 { 00433 //int i = currentItem() + numItemsVisible(); 00434 //i = i > (int)count() - 1 ? (int)count() - 1 : i; 00435 //setCurrentRow( i ); 00436 moveCursor(QAbstractItemView::MovePageDown , Qt::NoModifier); 00437 } 00438 00439 void KCompletionBox::pageUp() 00440 { 00441 //int i = currentItem() - numItemsVisible(); 00442 //i = i < 0 ? 0 : i; 00443 //setCurrentRow( i ); 00444 00445 moveCursor(QAbstractItemView::MovePageUp , Qt::NoModifier); 00446 } 00447 00448 void KCompletionBox::home() 00449 { 00450 setCurrentRow( 0 ); 00451 } 00452 00453 void KCompletionBox::end() 00454 { 00455 setCurrentRow( count() -1 ); 00456 } 00457 00458 void KCompletionBox::setTabHandling( bool enable ) 00459 { 00460 d->tabHandling = enable; 00461 } 00462 00463 bool KCompletionBox::isTabHandling() const 00464 { 00465 return d->tabHandling; 00466 } 00467 00468 void KCompletionBox::setCancelledText( const QString& text ) 00469 { 00470 d->cancelText = text; 00471 } 00472 00473 QString KCompletionBox::cancelledText() const 00474 { 00475 return d->cancelText; 00476 } 00477 00478 void KCompletionBox::canceled() 00479 { 00480 if ( !d->cancelText.isNull() ) 00481 emit userCancelled( d->cancelText ); 00482 if ( isVisible() ) 00483 hide(); 00484 } 00485 00486 class KCompletionBoxItem : public QListWidgetItem 00487 { 00488 public: 00489 //Returns true if dirty. 00490 bool reuse( const QString& newText ) 00491 { 00492 if ( text() == newText ) 00493 return false; 00494 setText( newText ); 00495 return true; 00496 } 00497 }; 00498 00499 00500 void KCompletionBox::insertItems( const QStringList& items, int index ) 00501 { 00502 bool block = signalsBlocked(); 00503 blockSignals( true ); 00504 KListWidget::insertItems( index, items ); 00505 blockSignals( block ); 00506 setCurrentRow(-1); 00507 } 00508 00509 void KCompletionBox::setItems( const QStringList& items ) 00510 { 00511 bool block = signalsBlocked(); 00512 blockSignals( true ); 00513 00514 int rowIndex = 0; 00515 00516 if (!count()) { 00517 addItems(items); 00518 } else { 00519 // Keep track of whether we need to change anything, 00520 // so we can avoid a repaint for identical updates, 00521 // to reduce flicker 00522 bool dirty = false; 00523 00524 QStringList::ConstIterator it = items.constBegin(); 00525 const QStringList::ConstIterator itEnd = items.constEnd(); 00526 00527 for ( ; it != itEnd; ++it) { 00528 if ( rowIndex < count() ) { 00529 const bool changed = ((KCompletionBoxItem*)item(rowIndex))->reuse( *it ); 00530 dirty = dirty || changed; 00531 } else { 00532 dirty = true; 00533 // Inserting an item is a way of making this dirty 00534 addItem(*it); 00535 } 00536 rowIndex++; 00537 } 00538 00539 // If there is an unused item, mark as dirty -> less items now 00540 if (rowIndex < count()) { 00541 dirty = true; 00542 } 00543 00544 // remove unused items with an index >= rowIndex 00545 for ( ; rowIndex < count() ; ) { 00546 QListWidgetItem* item = takeItem(rowIndex); 00547 Q_ASSERT(item); 00548 delete item; 00549 } 00550 00551 //TODO KDE4 : Port me 00552 //if (dirty) 00553 // triggerUpdate( false ); 00554 } 00555 00556 if (isVisible() && size().height() != sizeHint().height()) 00557 sizeAndPosition(); 00558 00559 blockSignals(block); 00560 } 00561 00562 void KCompletionBox::slotItemClicked( QListWidgetItem *item ) 00563 { 00564 if ( item ) 00565 { 00566 hide(); 00567 emit currentTextChanged( item->text() ); 00568 emit activated( item->text() ); 00569 } 00570 } 00571 00572 void KCompletionBox::setActivateOnSelect(bool state) 00573 { 00574 d->emitSelected = state; 00575 } 00576 00577 bool KCompletionBox::activateOnSelect() const 00578 { 00579 return d->emitSelected; 00580 } 00581 00582 #include "kcompletionbox.moc"
This file is part of the KDE documentation.
Documentation copyright © 1996-2012 The KDE developers.
Generated on Wed May 2 2012 17:56:56 by doxygen 1.8.0 written by Dimitri van Heesch, © 1997-2006
Documentation copyright © 1996-2012 The KDE developers.
Generated on Wed May 2 2012 17:56:56 by doxygen 1.8.0 written by Dimitri van Heesch, © 1997-2006
KDE's Doxygen guidelines are available online.