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