kdeui Library API Documentation

kcompletionbox.cpp

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., 59 Temple Place - Suite 330, 00020 Boston, MA 02111-1307, USA. 00021 */ 00022 00023 00024 #include <qapplication.h> 00025 #include <qcombobox.h> 00026 #include <qevent.h> 00027 #include <qstyle.h> 00028 00029 #include <kdebug.h> 00030 #include <kconfig.h> 00031 #include <knotifyclient.h> 00032 #include <kglobalsettings.h> 00033 00034 #include "kcompletionbox.h" 00035 00036 class KCompletionBox::KCompletionBoxPrivate 00037 { 00038 public: 00039 QWidget *m_parent; // necessary to set the focus back 00040 QString cancelText; 00041 bool tabHandling; 00042 bool down_workaround; 00043 bool upwardBox; 00044 bool emitSelected; 00045 }; 00046 00047 KCompletionBox::KCompletionBox( QWidget *parent, const char *name ) 00048 :KListBox( parent, name, WType_Popup ), d(new KCompletionBoxPrivate) 00049 { 00050 00051 d->m_parent = parent; 00052 d->tabHandling = true; 00053 d->down_workaround = false; 00054 d->upwardBox = false; 00055 d->emitSelected = true; 00056 00057 setColumnMode( 1 ); 00058 setLineWidth( 1 ); 00059 setFrameStyle( QFrame::Box | QFrame::Plain ); 00060 00061 if ( parent ) 00062 setFocusProxy( parent ); 00063 else 00064 setFocusPolicy( NoFocus ); 00065 00066 setVScrollBarMode( Auto ); 00067 setHScrollBarMode( AlwaysOff ); 00068 00069 connect( this, SIGNAL( doubleClicked( QListBoxItem * )), 00070 SLOT( slotActivated( QListBoxItem * )) ); 00071 00072 // grmbl, just QListBox workarounds :[ Thanks Volker. 00073 connect( this, SIGNAL( currentChanged( QListBoxItem * )), 00074 SLOT( slotCurrentChanged() )); 00075 connect( this, SIGNAL( clicked( QListBoxItem * )), 00076 SLOT( slotItemClicked( QListBoxItem * )) ); 00077 } 00078 00079 KCompletionBox::~KCompletionBox() 00080 { 00081 d->m_parent = 0L; 00082 delete d; 00083 } 00084 00085 QStringList KCompletionBox::items() const 00086 { 00087 QStringList list; 00088 00089 const QListBoxItem* currItem = firstItem(); 00090 00091 while (currItem) { 00092 list.append(currItem->text()); 00093 currItem = currItem->next(); 00094 } 00095 00096 return list; 00097 } 00098 00099 void KCompletionBox::slotActivated( QListBoxItem *item ) 00100 { 00101 if ( !item ) 00102 return; 00103 00104 hide(); 00105 emit activated( item->text() ); 00106 } 00107 00108 bool KCompletionBox::eventFilter( QObject *o, QEvent *e ) 00109 { 00110 int type = e->type(); 00111 00112 if ( o == d->m_parent ) { 00113 if ( isVisible() ) { 00114 if ( type == QEvent::KeyPress ) { 00115 QKeyEvent *ev = static_cast<QKeyEvent *>( e ); 00116 switch ( ev->key() ) { 00117 case Key_BackTab: 00118 if ( d->tabHandling && (ev->state() == NoButton || 00119 (ev->state() & ShiftButton)) ) { 00120 up(); 00121 ev->accept(); 00122 return true; 00123 } 00124 break; 00125 case Key_Tab: 00126 if ( d->tabHandling && (ev->state() == NoButton) ) { 00127 down(); // Only on TAB!! 00128 ev->accept(); 00129 return true; 00130 } 00131 break; 00132 case Key_Down: 00133 down(); 00134 ev->accept(); 00135 return true; 00136 case Key_Up: 00137 // If there is no selected item and we've popped up above 00138 // our parent, select the first item when they press up. 00139 if ( selectedItem() || 00140 mapToGlobal( QPoint( 0, 0 ) ).y() > 00141 d->m_parent->mapToGlobal( QPoint( 0, 0 ) ).y() ) 00142 up(); 00143 else 00144 down(); 00145 ev->accept(); 00146 return true; 00147 case Key_Prior: 00148 pageUp(); 00149 ev->accept(); 00150 return true; 00151 case Key_Next: 00152 pageDown(); 00153 ev->accept(); 00154 return true; 00155 case Key_Escape: 00156 canceled(); 00157 ev->accept(); 00158 return true; 00159 case Key_Enter: 00160 case Key_Return: 00161 if ( ev->state() & ShiftButton ) { 00162 hide(); 00163 ev->accept(); // Consume the Enter event 00164 return true; 00165 } 00166 break; 00167 case Key_End: 00168 if ( ev->state() & ControlButton ) 00169 { 00170 end(); 00171 ev->accept(); 00172 return true; 00173 } 00174 case Key_Home: 00175 if ( ev->state() & ControlButton ) 00176 { 00177 home(); 00178 ev->accept(); 00179 return true; 00180 } 00181 default: 00182 break; 00183 } 00184 } 00185 else if ( type == QEvent::AccelOverride ) { 00186 // Override any acceleartors that match 00187 // the key sequences we use here... 00188 QKeyEvent *ev = static_cast<QKeyEvent *>( e ); 00189 switch ( ev->key() ) { 00190 case Key_Down: 00191 case Key_Up: 00192 case Key_Prior: 00193 case Key_Next: 00194 case Key_Escape: 00195 case Key_Enter: 00196 case Key_Return: 00197 ev->accept(); 00198 return true; 00199 break; 00200 case Key_Tab: 00201 case Key_BackTab: 00202 if ( ev->state() == NoButton || 00203 (ev->state() & ShiftButton)) 00204 { 00205 ev->accept(); 00206 return true; 00207 } 00208 break; 00209 case Key_Home: 00210 case Key_End: 00211 if ( ev->state() & ControlButton ) 00212 { 00213 ev->accept(); 00214 return true; 00215 } 00216 break; 00217 default: 00218 break; 00219 } 00220 } 00221 00222 // parent loses focus or gets a click -> we hide 00223 else if ( type == QEvent::FocusOut || type == QEvent::Resize || 00224 type == QEvent::Close || type == QEvent::Hide || 00225 type == QEvent::Move ) { 00226 hide(); 00227 } 00228 } 00229 } 00230 00231 // any mouse-click on something else than "this" makes us hide 00232 else if ( type == QEvent::MouseButtonPress ) { 00233 QMouseEvent *ev = static_cast<QMouseEvent *>( e ); 00234 if ( !rect().contains( ev->pos() )) // this widget 00235 hide(); 00236 00237 if ( !d->emitSelected && currentItem() ) 00238 { 00239 emit highlighted( currentText() ); 00240 hide(); 00241 ev->accept(); // Consume the mouse click event... 00242 return true; 00243 } 00244 } 00245 00246 return KListBox::eventFilter( o, e ); 00247 } 00248 00249 00250 void KCompletionBox::popup() 00251 { 00252 if ( count() == 0 ) 00253 hide(); 00254 else { 00255 ensureCurrentVisible(); 00256 bool block = signalsBlocked(); 00257 blockSignals( true ); 00258 setCurrentItem( 0 ); 00259 blockSignals( block ); 00260 clearSelection(); 00261 if ( !isVisible() ) 00262 show(); 00263 else if ( size().height() != sizeHint().height() ) 00264 sizeAndPosition(); 00265 } 00266 } 00267 00268 void KCompletionBox::sizeAndPosition() 00269 { 00270 int currentGeom = height(); 00271 QPoint currentPos = pos(); 00272 QRect geom = calculateGeometry(); 00273 resize( geom.size() ); 00274 00275 int x = currentPos.x(), y = currentPos.y(); 00276 if ( d->m_parent ) { 00277 if ( !isVisible() ) { 00278 QRect screenSize = KGlobalSettings::desktopGeometry(d->m_parent); 00279 00280 QPoint orig = d->m_parent->mapToGlobal( QPoint(0, d->m_parent->height()) ); 00281 x = orig.x() + geom.x(); 00282 y = orig.y() + geom.y(); 00283 00284 if ( x + width() > screenSize.right() ) 00285 x = screenSize.right() - width(); 00286 if (y + height() > screenSize.bottom() ) { 00287 y = y - height() - d->m_parent->height(); 00288 d->upwardBox = true; 00289 } 00290 } 00291 else { 00292 // Are we above our parent? If so we must keep bottom edge anchored. 00293 if (d->upwardBox) 00294 y += (currentGeom-height()); 00295 } 00296 move( x, y); 00297 } 00298 } 00299 00300 void KCompletionBox::show() 00301 { 00302 d->upwardBox = false; 00303 if ( d->m_parent ) { 00304 sizeAndPosition(); 00305 qApp->installEventFilter( this ); 00306 } 00307 00308 // ### we shouldn't need to call this, but without this, the scrollbars 00309 // are pretty b0rked. 00310 //triggerUpdate( true ); 00311 00312 // Workaround for I'm not sure whose bug - if this KCompletionBox' parent 00313 // is in a layout, that layout will detect inserting new child (posted 00314 // ChildInserted event), and will trigger relayout (post LayoutHint event). 00315 // QWidget::show() sends also posted ChildInserted events for the parent, 00316 // and later all LayoutHint events, which causes layout updating. 00317 // The problem is, KCompletionBox::eventFilter() detects resizing 00318 // of the parent, and calls hide() - and this hide() happen in the middle 00319 // of show(), causing inconsistent state. I'll try to submit a Qt patch too. 00320 qApp->sendPostedEvents(); 00321 KListBox::show(); 00322 } 00323 00324 void KCompletionBox::hide() 00325 { 00326 if ( d->m_parent ) 00327 qApp->removeEventFilter( this ); 00328 d->cancelText = QString::null; 00329 KListBox::hide(); 00330 } 00331 00332 QRect KCompletionBox::calculateGeometry() const 00333 { 00334 int x = 0, y = 0; 00335 int ih = itemHeight(); 00336 int h = QMIN( 15 * ih, (int) count() * ih ) + 2*frameWidth(); 00337 00338 int w = (d->m_parent) ? d->m_parent->width() : KListBox::minimumSizeHint().width(); 00339 w = QMAX( KListBox::minimumSizeHint().width(), w ); 00340 00341 //If we're inside a combox, Qt by default makes the dropdown 00342 // as wide as the combo, and gives the style a chance 00343 // to adjust it. Do that here as well, for consistency 00344 const QObject* combo; 00345 if ( d->m_parent && (combo = d->m_parent->parent() ) && 00346 combo->inherits("QComboBox") ) 00347 { 00348 const QComboBox* cb = static_cast<const QComboBox*>(combo); 00349 00350 //Expand to the combo width 00351 w = QMAX( w, cb->width() ); 00352 00353 QPoint parentCorner = d->m_parent->mapToGlobal(QPoint(0, 0)); 00354 QPoint comboCorner = cb->mapToGlobal(QPoint(0, 0)); 00355 00356 //We need to adjust our horizontal position to also be WRT to the combo 00357 x += comboCorner.x() - parentCorner.x(); 00358 00359 //The same with vertical one 00360 y += cb->height() - d->m_parent->height() + 00361 comboCorner.y() - parentCorner.y(); 00362 00363 //Ask the style to refine this a bit 00364 QRect styleAdj = style().querySubControlMetrics(QStyle::CC_ComboBox, 00365 cb, QStyle::SC_ComboBoxListBoxPopup, 00366 QStyleOption(x, y, w, h)); 00367 //QCommonStyle returns QRect() by default, so this is what we get if the 00368 //style doesn't implement this 00369 if (!styleAdj.isNull()) 00370 return styleAdj; 00371 00372 } 00373 return QRect(x, y, w, h); 00374 } 00375 00376 QSize KCompletionBox::sizeHint() const 00377 { 00378 return calculateGeometry().size(); 00379 } 00380 00381 void KCompletionBox::down() 00382 { 00383 int i = currentItem(); 00384 00385 if ( i == 0 && d->down_workaround ) { 00386 d->down_workaround = false; 00387 setCurrentItem( 0 ); 00388 setSelected( 0, true ); 00389 emit highlighted( currentText() ); 00390 } 00391 00392 else if ( i < (int) count() - 1 ) 00393 setCurrentItem( i + 1 ); 00394 } 00395 00396 void KCompletionBox::up() 00397 { 00398 if ( currentItem() > 0 ) 00399 setCurrentItem( currentItem() - 1 ); 00400 } 00401 00402 void KCompletionBox::pageDown() 00403 { 00404 int i = currentItem() + numItemsVisible(); 00405 i = i > (int)count() - 1 ? (int)count() - 1 : i; 00406 setCurrentItem( i ); 00407 } 00408 00409 void KCompletionBox::pageUp() 00410 { 00411 int i = currentItem() - numItemsVisible(); 00412 i = i < 0 ? 0 : i; 00413 setCurrentItem( i ); 00414 } 00415 00416 void KCompletionBox::home() 00417 { 00418 setCurrentItem( 0 ); 00419 } 00420 00421 void KCompletionBox::end() 00422 { 00423 setCurrentItem( count() -1 ); 00424 } 00425 00426 void KCompletionBox::setTabHandling( bool enable ) 00427 { 00428 d->tabHandling = enable; 00429 } 00430 00431 bool KCompletionBox::isTabHandling() const 00432 { 00433 return d->tabHandling; 00434 } 00435 00436 void KCompletionBox::setCancelledText( const QString& text ) 00437 { 00438 d->cancelText = text; 00439 } 00440 00441 QString KCompletionBox::cancelledText() const 00442 { 00443 return d->cancelText; 00444 } 00445 00446 void KCompletionBox::canceled() 00447 { 00448 if ( !d->cancelText.isNull() ) 00449 emit userCancelled( d->cancelText ); 00450 if ( isVisible() ) 00451 hide(); 00452 } 00453 00454 class KCompletionBoxItem : public QListBoxItem 00455 { 00456 public: 00457 //Returns true if dirty. 00458 bool reuse( const QString& newText ) 00459 { 00460 if ( text() == newText ) 00461 return false; 00462 setText( newText ); 00463 return true; 00464 } 00465 }; 00466 00467 00468 void KCompletionBox::insertItems( const QStringList& items, int index ) 00469 { 00470 bool block = signalsBlocked(); 00471 blockSignals( true ); 00472 insertStringList( items, index ); 00473 blockSignals( block ); 00474 d->down_workaround = true; 00475 } 00476 00477 void KCompletionBox::setItems( const QStringList& items ) 00478 { 00479 bool block = signalsBlocked(); 00480 blockSignals( true ); 00481 00482 QListBoxItem* item = firstItem(); 00483 if ( !item ) { 00484 insertStringList( items ); 00485 } 00486 else { 00487 //Keep track of whether we need to change anything, 00488 //so we can avoid a repaint for identical updates, 00489 //to reduce flicker 00490 bool dirty = false; 00491 00492 QStringList::ConstIterator it = items.constBegin(); 00493 const QStringList::ConstIterator itEnd = items.constEnd(); 00494 00495 for ( ; it != itEnd; ++it) { 00496 if ( item ) { 00497 const bool changed = ((KCompletionBoxItem*)item)->reuse( *it ); 00498 dirty = dirty || changed; 00499 item = item->next(); 00500 } 00501 else { 00502 dirty = true; 00503 //Inserting an item is a way of making this dirty 00504 insertItem( new QListBoxText( *it ) ); 00505 } 00506 } 00507 00508 //If there is an unused item, mark as dirty -> less items now 00509 if ( item ) { 00510 dirty = true; 00511 } 00512 00513 QListBoxItem* tmp = item; 00514 while ( (item = tmp ) ) { 00515 tmp = item->next(); 00516 delete item; 00517 } 00518 00519 if (dirty) 00520 triggerUpdate( false ); 00521 } 00522 00523 if ( isVisible() && size().height() != sizeHint().height() ) 00524 sizeAndPosition(); 00525 00526 blockSignals( block ); 00527 d->down_workaround = true; 00528 } 00529 00530 void KCompletionBox::slotCurrentChanged() 00531 { 00532 d->down_workaround = false; 00533 } 00534 00535 void KCompletionBox::slotItemClicked( QListBoxItem *item ) 00536 { 00537 if ( item ) 00538 { 00539 if ( d->down_workaround ) { 00540 d->down_workaround = false; 00541 emit highlighted( item->text() ); 00542 } 00543 00544 hide(); 00545 emit activated( item->text() ); 00546 } 00547 } 00548 00549 void KCompletionBox::setActivateOnSelect(bool state) 00550 { 00551 d->emitSelected = state; 00552 } 00553 00554 bool KCompletionBox::activateOnSelect() const 00555 { 00556 return d->emitSelected; 00557 } 00558 00559 void KCompletionBox::virtual_hook( int id, void* data ) 00560 { KListBox::virtual_hook( id, data ); } 00561 00562 #include "kcompletionbox.moc"
KDE Logo
This file is part of the documentation for kdeui Library Version 3.4.0.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Tue Apr 12 22:56:21 2005 by doxygen 1.3.7 written by Dimitri van Heesch, © 1997-2003