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