• Skip to content
  • Skip to link menu
KDE 4.1 API Reference
  • KDE API Reference
  • kdelibs
  • Sitemap
  • Contact Us
 

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 
00026 #include <QtCore/QEvent>
00027 #include <QtGui/QApplication>
00028 #include <QtGui/QComboBox>
00029 #include <QtGui/QStyle>
00030 #include <QtGui/QScrollBar>
00031 #include <QtGui/QKeyEvent>
00032 
00033 #include <kdebug.h>
00034 #include <kconfig.h>
00035 #include <kglobalsettings.h>
00036 
00037 class KCompletionBox::KCompletionBoxPrivate
00038 {
00039 public:
00040     QWidget *m_parent; // necessary to set the focus back
00041     QString cancelText;
00042     bool tabHandling : 1;
00043     bool down_workaround : 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->down_workaround = false;
00054     d->upwardBox       = false;
00055     d->emitSelected    = true;
00056 
00057     setWindowFlags( Qt::Popup ); // calls setVisible, so must be done after initializations
00058 
00059     setLineWidth( 1 );
00060     setFrameStyle( QFrame::Box | QFrame::Plain );
00061 
00062     if ( parent ) {
00063         setFocusProxy( parent );
00064     } else
00065         setFocusPolicy( Qt::NoFocus );
00066 
00067     setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
00068     setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
00069 
00070     connect( this, SIGNAL( itemDoubleClicked( QListWidgetItem * )),
00071              SLOT( slotActivated( QListWidgetItem * )) );
00072 
00073 #ifdef __GNUC__
00074 #warning "Check if this workaround can be removed in KDE 4"
00075 #endif
00076 
00077     // grmbl, just QListBox workarounds :[ Thanks Volker.
00078     connect( this, SIGNAL( currentItemChanged( QListWidgetItem * , QListWidgetItem * )),
00079              SLOT( slotCurrentChanged() ));
00080     connect( this, SIGNAL( itemClicked( QListWidgetItem * )),
00081              SLOT( slotItemClicked( QListWidgetItem * )) );
00082 }
00083 
00084 KCompletionBox::~KCompletionBox()
00085 {
00086     d->m_parent = 0L;
00087     delete d;
00088 }
00089 
00090 QStringList KCompletionBox::items() const
00091 {
00092     QStringList list;
00093 
00094     for (int i = 0 ; i < count() ; i++)
00095     {
00096         const QListWidgetItem* currItem = item(i);
00097 
00098         list.append(currItem->text());
00099     }
00100 
00101     return list;
00102 }
00103 
00104 void KCompletionBox::slotActivated( QListWidgetItem *item )
00105 {
00106     if ( !item )
00107         return;
00108 
00109     hide();
00110     emit activated( item->text() );
00111 }
00112 
00113 bool KCompletionBox::eventFilter( QObject *o, QEvent *e )
00114 {
00115     int type = e->type();
00116 
00117     if ((o==this) && (type==QEvent::KeyPress)) {
00118     //not sure if this is a work around for a Qt bug or a fix for changed qt behaviour, at least it makes the kcombobox work again for me
00119     QApplication::sendEvent(d->m_parent,e);
00120     return true;
00121     }
00122 
00123     if ( o == d->m_parent ) {
00124         if ( isVisible() ) {
00125             if ( type == QEvent::KeyPress ) {
00126                 QKeyEvent *ev = static_cast<QKeyEvent *>( e );
00127                 switch ( ev->key() ) {
00128                     case Qt::Key_Backtab:
00129                         if ( d->tabHandling && (ev->modifiers() == Qt::NoButton ||
00130                              (ev->modifiers() & Qt::ShiftModifier)) ) {
00131                             up();
00132                             ev->accept();
00133                             return true;
00134                         }
00135                         break;
00136                     case Qt::Key_Tab:
00137                         if ( d->tabHandling && (ev->modifiers() == Qt::NoButton) ) {
00138                             down(); // Only on TAB!!
00139                             ev->accept();
00140                             return true;
00141                         }
00142                         break;
00143                     case Qt::Key_Down:
00144                         down();
00145                         ev->accept();
00146                         return true;
00147                     case Qt::Key_Up:
00148                         // If there is no selected item and we've popped up above
00149                         // our parent, select the first item when they press up.
00150                         if ( !selectedItems().isEmpty() ||
00151                              mapToGlobal( QPoint( 0, 0 ) ).y() >
00152                              d->m_parent->mapToGlobal( QPoint( 0, 0 ) ).y() )
00153                             up();
00154                         else
00155                             down();
00156                         ev->accept();
00157                         return true;
00158                     case Qt::Key_PageUp:
00159                         pageUp();
00160                         ev->accept();
00161                         return true;
00162                     case Qt::Key_PageDown:
00163                         pageDown();
00164                         ev->accept();
00165                         return true;
00166                     case Qt::Key_Escape:
00167                         canceled();
00168                         ev->accept();
00169                         return true;
00170                     case Qt::Key_Enter:
00171                     case Qt::Key_Return:
00172                         if ( ev->modifiers() & Qt::ShiftModifier ) {
00173                             hide();
00174                             ev->accept();  // Consume the Enter event
00175                             return true;
00176                         }
00177                         break;
00178                     case Qt::Key_End:
00179                         if ( ev->modifiers() & Qt::ControlModifier )
00180                         {
00181                             end();
00182                             ev->accept();
00183                             return true;
00184                         }
00185                     case Qt::Key_Home:
00186                         if ( ev->modifiers() & Qt::ControlModifier )
00187                         {
00188                             home();
00189                             ev->accept();
00190                             return true;
00191                         }
00192                     default:
00193                         break;
00194                 }
00195             }
00196             else if ( type == QEvent::ShortcutOverride ) {
00197                 // Override any accelerators that match
00198                 // the key sequences we use here...
00199                 QKeyEvent *ev = static_cast<QKeyEvent *>( e );
00200                 switch ( ev->key() ) {
00201                     case Qt::Key_Down:
00202                     case Qt::Key_Up:
00203                     case Qt::Key_PageUp:
00204                     case Qt::Key_PageDown:
00205                     case Qt::Key_Escape:
00206                     case Qt::Key_Enter:
00207                     case Qt::Key_Return:
00208                       ev->accept();
00209                       return true;
00210                       break;
00211                     case Qt::Key_Tab:
00212                     case Qt::Key_Backtab:
00213                         if ( ev->modifiers() == Qt::NoButton ||
00214                             (ev->modifiers() & Qt::ShiftModifier))
00215                         {
00216                             ev->accept();
00217                             return true;
00218                         }
00219                         break;
00220                     case Qt::Key_Home:
00221                     case Qt::Key_End:
00222                         if ( ev->modifiers() & Qt::ControlModifier )
00223                         {
00224                             ev->accept();
00225                             return true;
00226                         }
00227                         break;
00228                     default:
00229                         break;
00230                 }
00231             }
00232 
00233             // parent gets a click -> we hide
00234             else if ( type == QEvent::Resize ||
00235                       type == QEvent::Close || type == QEvent::Hide ) {
00236                 hide();
00237             }
00238             
00239             else if ( type == QEvent::FocusOut ) {
00240                   QFocusEvent* event = static_cast<QFocusEvent*>( e );
00241                   if (event->reason() != Qt::PopupFocusReason)
00242                     hide();
00243             }
00244             
00245         }
00246     }
00247 
00248     // any mouse-click on something else than "this" makes us hide
00249     else if ( type == QEvent::MouseButtonPress ) {
00250         QMouseEvent *ev = static_cast<QMouseEvent *>( e );
00251         if ( !rect().contains( ev->pos() )) // this widget
00252             hide();
00253 
00254         if ( !d->emitSelected && currentItem() && !qobject_cast<QScrollBar*>(o) )
00255         {
00256           Q_ASSERT( currentItem() );
00257 
00258           emit currentTextChanged( currentItem()->text() );
00259           hide();
00260           ev->accept();  // Consume the mouse click event...
00261           return true;
00262         }
00263     }
00264 
00265     return KListWidget::eventFilter( o, e );
00266 }
00267 
00268 
00269 void KCompletionBox::popup()
00270 {
00271     if ( count() == 0 )
00272         hide();
00273     else {
00274         //TODO KDE 4 - Port: ensureCurrentVisible();
00275         bool block = signalsBlocked();
00276         blockSignals( true );
00277         setCurrentItem( 0 );
00278         blockSignals( block );
00279         clearSelection();
00280         if ( !isVisible() )
00281             show();
00282         else if ( size().height() != sizeHint().height() )
00283             sizeAndPosition();
00284     }
00285 
00286     // Make sure we give the focus back to the parent widget (ereslibre)
00287     if ( parentWidget() ) {
00288         QFocusEvent focusEvent( QEvent::FocusIn );
00289         QApplication::sendEvent( parentWidget(), &focusEvent );
00290     }
00291 }
00292 
00293 void KCompletionBox::sizeAndPosition()
00294 {
00295     int currentGeom = height();
00296     QPoint currentPos = pos();
00297     QRect geom = calculateGeometry();
00298     resize( geom.size() );
00299 
00300     int x = currentPos.x(), y = currentPos.y();
00301     if ( d->m_parent ) {
00302       if ( !isVisible() ) {
00303         QRect screenSize = KGlobalSettings::desktopGeometry(d->m_parent);
00304 
00305         QPoint orig = globalPositionHint();
00306         x = orig.x() + geom.x();
00307         y = orig.y() + geom.y();
00308 
00309         if ( x + width() > screenSize.right() )
00310             x = screenSize.right() - width();
00311         if (y + height() > screenSize.bottom() ) {
00312             y = y - height() - d->m_parent->height();
00313             d->upwardBox = true;
00314         }
00315       }
00316       else {
00317         // Are we above our parent? If so we must keep bottom edge anchored.
00318         if (d->upwardBox)
00319           y += (currentGeom-height());
00320       }
00321       move( x, y);
00322     }
00323 }
00324 
00325 QPoint KCompletionBox::globalPositionHint() const
00326 {
00327     if (!d->m_parent)
00328         return QPoint();
00329     return d->m_parent->mapToGlobal( QPoint(0, d->m_parent->height()) );
00330 }
00331 
00332 void KCompletionBox::setVisible( bool visible )
00333 {
00334     if (visible) {
00335         d->upwardBox = false;
00336         if ( d->m_parent ) {
00337             sizeAndPosition();
00338             qApp->installEventFilter( this );
00339         }
00340 
00341         // ### we shouldn't need to call this, but without this, the scrollbars
00342         // are pretty b0rked.
00343         //triggerUpdate( true );
00344 
00345         // Workaround for I'm not sure whose bug - if this KCompletionBox' parent
00346         // is in a layout, that layout will detect inserting new child (posted
00347         // ChildInserted event), and will trigger relayout (post LayoutHint event).
00348         // QWidget::show() sends also posted ChildInserted events for the parent,
00349         // and later all LayoutHint events, which causes layout updating.
00350         // The problem is, KCompletionBox::eventFilter() detects resizing
00351         // of the parent, and calls hide() - and this hide() happen in the middle
00352         // of show(), causing inconsistent state. I'll try to submit a Qt patch too.
00353         qApp->sendPostedEvents();
00354     } else {
00355         if ( d->m_parent )
00356             qApp->removeEventFilter( this );
00357         d->cancelText.clear();
00358     }
00359   
00360     KListWidget::setVisible(visible);
00361 }
00362 
00363 QRect KCompletionBox::calculateGeometry() const
00364 {
00365     if (count() == 0)
00366         return QRect();
00367 
00368     int x = 0, y = 0;
00369 
00370     Q_ASSERT( visualItemRect(item(0)).isValid() );
00371 
00372     int ih = visualItemRect(item(0)).height();
00373     int h = qMin( 15 * ih, (int) count() * ih ) + 2*frameWidth();
00374 
00375     int w = (d->m_parent) ? d->m_parent->width() : KListWidget::minimumSizeHint().width();
00376     w = qMax( KListWidget::minimumSizeHint().width(), w );
00377 
00378   //### M.O.: Qt4 doesn't actually honor SC_ComboBoxListBoxPopup ???
00379 #if 0
00380     //If we're inside a combox, Qt by default makes the dropdown
00381     // as wide as the combo, and gives the style a chance
00382     // to adjust it. Do that here as well, for consistency
00383     const QObject* combo;
00384     if ( d->m_parent && (combo = d->m_parent->parent() ) &&
00385         qobject_cast<QComboBox*>(combo) )
00386     {
00387         const QComboBox* cb = static_cast<const QComboBox*>(combo);
00388 
00389         //Expand to the combo width
00390         w = qMax( w, cb->width() );
00391 
00392         QPoint parentCorner = d->m_parent->mapToGlobal(QPoint(0, 0));
00393         QPoint comboCorner  = cb->mapToGlobal(QPoint(0, 0));
00394 
00395         //We need to adjust our horizontal position to also be WRT to the combo
00396         x += comboCorner.x() -  parentCorner.x();
00397 
00398         //The same with vertical one
00399         y += cb->height() - d->m_parent->height() +
00400              comboCorner.y() - parentCorner.y();
00401 
00402         //Ask the style to refine this a bit
00403         QRect styleAdj = style().querySubControlMetrics(QStyle::CC_ComboBox,
00404                                     cb, QStyle::SC_ComboBoxListBoxPopup,
00405                                     QStyleOption(x, y, w, h));
00406         //QCommonStyle returns QRect() by default, so this is what we get if the
00407         //style doesn't implement this
00408         if (!styleAdj.isNull())
00409             return styleAdj;
00410 
00411     }
00412 #endif
00413     return QRect(x, y, w, h);
00414 }
00415 
00416 QSize KCompletionBox::sizeHint() const
00417 {
00418     return calculateGeometry().size();
00419 }
00420 
00421 void KCompletionBox::down()
00422 {
00423     int i = currentRow();
00424 
00425     if ( i == 0 && d->down_workaround ) {
00426         d->down_workaround = false;
00427         setCurrentRow( 0 );
00428         item(0)->setSelected(true);
00429         emit currentTextChanged( currentItem()->text() );
00430     }
00431 
00432     else if ( i < (int) count() - 1 )
00433         setCurrentRow( i + 1 );
00434 }
00435 
00436 void KCompletionBox::up()
00437 {
00438     if ( currentItem() && row(currentItem()) > 0 )
00439         setCurrentItem( item(row(currentItem()) - 1) );
00440 }
00441 
00442 void KCompletionBox::pageDown()
00443 {
00444     //int i = currentItem() + numItemsVisible();
00445     //i = i > (int)count() - 1 ? (int)count() - 1 : i;
00446     //setCurrentRow( i );
00447     moveCursor(QAbstractItemView::MovePageDown , Qt::NoModifier);
00448 }
00449 
00450 void KCompletionBox::pageUp()
00451 {
00452     //int i = currentItem() - numItemsVisible();
00453     //i = i < 0 ? 0 : i;
00454     //setCurrentRow( i );
00455     
00456     moveCursor(QAbstractItemView::MovePageUp , Qt::NoModifier);
00457 }
00458 
00459 void KCompletionBox::home()
00460 {
00461     setCurrentItem( 0 );
00462 }
00463 
00464 void KCompletionBox::end()
00465 {
00466     setCurrentRow( count() -1 );
00467 }
00468 
00469 void KCompletionBox::setTabHandling( bool enable )
00470 {
00471     d->tabHandling = enable;
00472 }
00473 
00474 bool KCompletionBox::isTabHandling() const
00475 {
00476     return d->tabHandling;
00477 }
00478 
00479 void KCompletionBox::setCancelledText( const QString& text )
00480 {
00481     d->cancelText = text;
00482 }
00483 
00484 QString KCompletionBox::cancelledText() const
00485 {
00486     return d->cancelText;
00487 }
00488 
00489 void KCompletionBox::canceled()
00490 {
00491     if ( !d->cancelText.isNull() )
00492         emit userCancelled( d->cancelText );
00493     if ( isVisible() )
00494         hide();
00495 }
00496 
00497 class KCompletionBoxItem : public QListWidgetItem
00498 {
00499 public:
00500     //Returns true if dirty.
00501     bool reuse( const QString& newText )
00502     {
00503         if ( text() == newText )
00504             return false;
00505         setText( newText );
00506         return true;
00507     }
00508 };
00509 
00510 
00511 void KCompletionBox::insertItems( const QStringList& items, int index )
00512 {
00513     bool block = signalsBlocked();
00514     blockSignals( true );
00515     KListWidget::insertItems( index, items );
00516     blockSignals( block );
00517     d->down_workaround = true;
00518 }
00519 
00520 void KCompletionBox::setItems( const QStringList& items )
00521 {
00522     bool block = signalsBlocked();
00523     blockSignals( true );
00524 
00525     int rowIndex = 0;
00526 
00527     if ( rowIndex >= count() ) {
00528         addItems( items );
00529     }
00530     else {
00531         //Keep track of whether we need to change anything,
00532         //so we can avoid a repaint for identical updates,
00533         //to reduce flicker
00534         bool dirty = false;
00535 
00536         QStringList::ConstIterator it = items.constBegin();
00537         const QStringList::ConstIterator itEnd = items.constEnd();
00538 
00539         for ( ; it != itEnd; ++it) {
00540             if ( rowIndex < count() ) {
00541                 const bool changed = ((KCompletionBoxItem*)item(rowIndex))->reuse( *it );
00542                 dirty = dirty || changed;
00543                 
00544                 rowIndex++;
00545             }
00546             else {
00547                 dirty = true;
00548                 //Inserting an item is a way of making this dirty
00549                 addItem( *it );
00550             }
00551         }
00552 
00553         //If there is an unused item, mark as dirty -> less items now
00554         if ( rowIndex < count() ) {
00555             dirty = true;
00556         }
00557 
00558         // remove unused items with an index >= rowIndex
00559         for ( ; rowIndex < count() ; ) {
00560             QListWidgetItem* item = takeItem(rowIndex);
00561             
00562             Q_ASSERT(item);
00563 
00564             delete item;
00565         }
00566         
00567         //TODO KDE4 : Port me
00568         //if (dirty)
00569         //    triggerUpdate( false );
00570     }
00571 
00572     if ( isVisible() && size().height() != sizeHint().height() )
00573         sizeAndPosition();
00574 
00575     blockSignals( block );
00576     d->down_workaround = true;
00577 }
00578 
00579 void KCompletionBox::slotSetCurrentItem( QListWidgetItem *i )
00580 {
00581     setCurrentItem( i ); // grrr
00582 }
00583 
00584 void KCompletionBox::slotCurrentChanged()
00585 {
00586     if (currentItem())
00587       emit currentTextChanged(currentItem()->text());
00588     d->down_workaround = false;
00589 }
00590 
00591 void KCompletionBox::slotItemClicked( QListWidgetItem *item )
00592 {
00593     if ( item )
00594     {
00595         if ( d->down_workaround ) {
00596             d->down_workaround = false;
00597             emit currentTextChanged( item->text() );
00598         }
00599 
00600         hide();
00601         emit currentTextChanged( item->text() );
00602         emit activated( item->text() );
00603     }
00604 }
00605 
00606 void KCompletionBox::setActivateOnSelect(bool state)
00607 {
00608     d->emitSelected = state;
00609 }
00610 
00611 bool KCompletionBox::activateOnSelect() const
00612 {
00613     return d->emitSelected;
00614 }
00615 
00616 #include "kcompletionbox.moc"

KDEUI

Skip menu "KDEUI"
  • Main Page
  • Modules
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • KIO
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • Kross
  • KUtils
  • Nepomuk
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.5.6
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal