diff options
author | Lars Knoll <lars.knoll@nokia.com> | 2009-03-23 09:18:55 (GMT) |
---|---|---|
committer | Simon Hausmann <simon.hausmann@nokia.com> | 2009-03-23 09:18:55 (GMT) |
commit | e5fcad302d86d316390c6b0f62759a067313e8a9 (patch) | |
tree | c2afbf6f1066b6ce261f14341cf6d310e5595bc1 /src/qt3support/widgets/q3combobox.cpp | |
download | Qt-e5fcad302d86d316390c6b0f62759a067313e8a9.zip Qt-e5fcad302d86d316390c6b0f62759a067313e8a9.tar.gz Qt-e5fcad302d86d316390c6b0f62759a067313e8a9.tar.bz2 |
Long live Qt 4.5!
Diffstat (limited to 'src/qt3support/widgets/q3combobox.cpp')
-rw-r--r-- | src/qt3support/widgets/q3combobox.cpp | 2356 |
1 files changed, 2356 insertions, 0 deletions
diff --git a/src/qt3support/widgets/q3combobox.cpp b/src/qt3support/widgets/q3combobox.cpp new file mode 100644 index 0000000..3bac984 --- /dev/null +++ b/src/qt3support/widgets/q3combobox.cpp @@ -0,0 +1,2356 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "q3combobox.h" +#ifndef QT_NO_COMBOBOX +#include "qpainter.h" +#include "qdrawutil.h" +#include "qpixmap.h" +#include "qtimer.h" +#include "qapplication.h" +#include "qlineedit.h" +#include "qbitmap.h" +#include "qstringlist.h" +#include "qstyle.h" +#include "qevent.h" +#include "qmenu.h" +#include "qmenudata.h" +#include "qstyleoption.h" +#include "qdesktopwidget.h" +#include "q3popupmenu.h" +#include "q3listbox.h" +#include "q3strlist.h" +#include "q3frame.h" +#include <limits.h> +#include <qdebug.h> +#ifndef QT_NO_EFFECTS +# include <private/qeffects_p.h> +#endif +#if defined(QT_ACCESSIBILITY_SUPPORT) +#include "qaccessible.h" +#endif + +QT_BEGIN_NAMESPACE + +/*! + \class Q3ComboBox + \brief The Q3ComboBox widget is a combined button and popup list. + \since 4.1 + \compat + + A combobox is a selection widget which displays the current item + and can pop up a list of items. A combobox may be editable in + which case the user can enter arbitrary strings. + + Comboboxes provide a means of showing the user's current choice + out of a list of options in a way that takes up the minimum amount + of screen space. + + Q3ComboBox supports three different display styles: Aqua/Motif 1.x, + Motif 2.0 and Windows. In Motif 1.x, a combobox was called + XmOptionMenu. In Motif 2.0, OSF introduced an improved combobox + and named that XmComboBox. Q3ComboBox provides both. + + Q3ComboBox provides two different constructors. The simplest + constructor creates an "old-style" combobox in Motif (or Aqua) + style: + \snippet doc/src/snippets/code/src_qt3support_widgets_q3combobox.cpp 0 + + The other constructor creates a new-style combobox in Motif style, + and can create both read-only and editable comboboxes: + \snippet doc/src/snippets/code/src_qt3support_widgets_q3combobox.cpp 1 + + New-style comboboxes use a list box in both Motif and Windows + styles, and both the content size and the on-screen size of the + list box can be limited with sizeLimit() and setMaxCount() + respectively. Old-style comboboxes use a popup in Aqua and Motif + style, and that popup will happily grow larger than the desktop if + you put enough data into it. + + The two constructors create identical-looking comboboxes in + Windows style. + + Comboboxes can contain pixmaps as well as strings; the + insertItem() and changeItem() functions are suitably overloaded. + For editable comboboxes, the function clearEdit() is provided, + to clear the displayed string without changing the combobox's + contents. + + A combobox emits two signals, activated() and highlighted(), when + a new item has been activated (selected) or highlighted (made + current). Both signals exist in two versions, one with a \c + QString argument and one with an \c int argument. If the user + highlights or activates a pixmap, only the \c int signals are + emitted. Whenever the text of an editable combobox is changed the + textChanged() signal is emitted. + + When the user enters a new string in an editable combobox, the + widget may or may not insert it, and it can insert it in several + locations. The default policy is is \c AtBottom but you can change + this using setInsertionPolicy(). + + It is possible to constrain the input to an editable combobox + using QValidator; see setValidator(). By default, any input is + accepted. + + If the combobox is not editable then it has a default + focusPolicy() of \c TabFocus, i.e. it will not grab focus if + clicked. This differs from both Windows and Motif. If the combobox + is editable then it has a default focusPolicy() of \c StrongFocus, + i.e. it will grab focus if clicked. + + A combobox can be populated using the insert functions, + insertStringList() and insertItem() for example. Items can be + changed with changeItem(). An item can be removed with + removeItem() and all items can be removed with clear(). The text + of the current item is returned by currentText(), and the text of + a numbered item is returned with text(). The current item can be + set with setCurrentItem() or setCurrentText(). The number of items + in the combobox is returned by count(); the maximum number of + items can be set with setMaxCount(). You can allow editing using + setEditable(). For editable comboboxes you can set auto-completion + using setAutoCompletion() and whether or not the user can add + duplicates is set with setDuplicatesEnabled(). + + Depending on the style, Q3ComboBox will use a list box or a + popup menu to display the list of items. See setListBox() for + more information. + + \sa QComboBox, QLineEdit, QSpinBox + {GUI Design Handbook}{GUI Design Handbook: Combo Box, Drop-Down List Box} +*/ + + +/*! + \enum Q3ComboBox::Policy + + This enum specifies what the Q3ComboBox should do when a new string + is entered by the user. + + \value NoInsertion the string will not be inserted into the + combobox. + + \value AtTop insert the string as the first item in the combobox. + + \value AtCurrent replace the previously selected item with the + string the user has entered. + + \value AtBottom insert the string as the last item in the + combobox. + + \value AfterCurrent insert the string after the previously + selected item. + + \value BeforeCurrent insert the string before the previously + selected item. + + activated() is always emitted when the string is entered. + + If inserting the new string would cause the combobox to breach its + content size limit, the item at the other end of the list is + deleted. The definition of "other end" is + implementation-dependent. + + \omitvalue NoInsert + \omitvalue InsertAtTop + \omitvalue InsertAtCurrent + \omitvalue InsertAtBottom + \omitvalue InsertAfterCurrent + \omitvalue InsertBeforeCurrent +*/ + + +/*! + \fn void Q3ComboBox::activated( int index ) + + This signal is emitted when a new item has been activated + (selected). The \a index is the position of the item in the + combobox. + + This signal is not emitted if the item is changed + programmatically, e.g. using setCurrentItem(). +*/ + +/*! + \overload + \fn void Q3ComboBox::activated( const QString &string ) + + This signal is emitted when a new item has been activated + (selected). \a string is the selected string. + + You can also use the activated(int) signal, but be aware that its + argument is meaningful only for selected strings, not for user + entered strings. +*/ + +/*! + \fn void Q3ComboBox::highlighted( int index ) + + This signal is emitted when a new item has been set to be the + current item. The \a index is the position of the item in the + combobox. + + This signal is not emitted if the item is changed + programmatically, e.g. using setCurrentItem(). +*/ + +/*! + \overload + \fn void Q3ComboBox::highlighted( const QString &string ) + + This signal is emitted when a new item has been set to be the + current item. \a string is the item's text. + + You can also use the highlighted(int) signal. +*/ + +/*! + \fn void Q3ComboBox::textChanged( const QString &string ) + + This signal is used for editable comboboxes. It is emitted + whenever the contents of the text entry field changes. \a string + contains the new text. +*/ + +/*! + \property Q3ComboBox::autoCompletion + \brief whether auto-completion is enabled + + This property can only be set for editable comboboxes, for + non-editable comboboxes it has no effect. It is false by default. +*/ + +/*! + \property Q3ComboBox::autoResize + \brief whether auto-resize is enabled + \obsolete + + If this property is set to true then the combobox will resize + itself whenever its contents change. The default is false. +*/ + +/*! + \property Q3ComboBox::count + \brief the number of items in the combobox +*/ + +/*! + \property Q3ComboBox::currentItem + \brief the index of the current item in the combobox + + Note that the activated() and highlighted() signals are only + emitted when the user changes the current item, not when it is + changed programmatically. +*/ + +/*! + \property Q3ComboBox::currentText + \brief the text of the combobox's current item +*/ + +/*! + \property Q3ComboBox::duplicatesEnabled + \brief whether duplicates are allowed + + If the combobox is editable and the user enters some text in the + combobox's lineedit and presses Enter (and the insertionPolicy() + is not \c NoInsertion), then what happens is this: + \list + \i If the text is not already in the list, the text is inserted. + \i If the text is in the list and this property is true (the + default), the text is inserted. + \i If the text is in the list and this property is false, the text + is \e not inserted; instead the item which has matching text becomes + the current item. + \endlist + + This property only affects user-interaction. You can use + insertItem() to insert duplicates if you wish regardless of this + setting. +*/ + +/*! + \property Q3ComboBox::editable + \brief whether the combobox is editable + + This property's default is false. Note that the combobox will be + cleared if this property is set to true for a 1.x Motif style + combobox. To avoid this, use setEditable() before inserting any + items. Also note that the 1.x version of Motif didn't have any + editable comboboxes, so the combobox will change its appearance + to a 2.0 style Motif combobox is it is set to be editable. +*/ + +/*! + \property Q3ComboBox::insertionPolicy + \brief the position of the items inserted by the user + + The default insertion policy is \c AtBottom. See \l Policy. +*/ + +/*! + \property Q3ComboBox::maxCount + \brief the maximum number of items allowed in the combobox +*/ + +/*! + \property Q3ComboBox::sizeLimit + \brief the maximum on-screen size of the combobox. + + This property is ignored for both Motif 1.x style and non-editable + comboboxes in Mac style. The default limit is ten + lines. If the number of items in the combobox is or grows larger + than lines, a scroll bar is added. +*/ + +class Q3ComboBoxPopup : public Q3PopupMenu +{ +public: + Q3ComboBoxPopup( QWidget *parent=0, const char *name=0 ) + : Q3PopupMenu( parent, name ) + { + } + + int itemHeight( int index ) + { + return Q3PopupMenu::itemHeight( index ); + } +}; + +static inline QString escapedComboString(const QString &str) +{ + QString stringToReturn = str; + return stringToReturn.replace(QLatin1Char('&'), QLatin1String("&&")); +} + +class Q3ComboBoxPopupItem : public QMenuItem +{ + Q3ListBoxItem *li; + QSize sc; // Size cache optimization +public: + Q3ComboBoxPopupItem(Q3ListBoxItem *i) : QMenuItem(), li(i), sc(0, 0) { } + virtual bool fullSpan() const { return true; } + virtual void paint( QPainter*, const QColorGroup&, bool, bool, int, int, int, int); + virtual QSize sizeHint() { if (sc.isNull()) sc = QSize(li->width(li->listBox()), QMAX(25, li->height(li->listBox()))); return sc; } +}; +void Q3ComboBoxPopupItem::paint( QPainter* p, const QColorGroup&, bool, + bool, int x, int y, int, int) +{ + p->save(); + p->translate(x, y + ((sizeHint().height() / 2) - (li->height(li->listBox()) / 2))); + li->paint(p); + p->restore(); +} + + +class Q3ComboBoxData +{ +public: + Q3ComboBoxData( Q3ComboBox *cb ): current( 0 ), arrowDown(false), ed( 0 ), usingLBox( false ), pop( 0 ), lBox( 0 ), combo( cb ) + { + duplicatesEnabled = true; + cb->setSizePolicy( QSizePolicy( QSizePolicy::Minimum, QSizePolicy::Fixed ) ); + } + + inline bool usingListBox() { return usingLBox; } + inline Q3ListBox * listBox() { return lBox; } + inline Q3ComboBoxPopup * popup() { return pop; } + void updateLinedGeometry(); + + void setListBox( Q3ListBox *l ) { lBox = l ; usingLBox = true; + l->setMouseTracking( true );} + + void setPopupMenu( Q3ComboBoxPopup * pm, bool isPopup=true ) + { pop = pm; if(isPopup) usingLBox = false; } + + QStyleOptionComboBox getStyleOption() const + { + QStyleOptionComboBox opt; + opt.init(combo); + if (!combo->editable() && combo->hasFocus()) + opt.state |= QStyle::State_Selected; + opt.subControls = QStyle::SC_All; + if (arrowDown) { + opt.activeSubControls = QStyle::SC_ComboBoxArrow; + opt.state |= QStyle::State_Sunken; + } + opt.editable = combo->editable(); + opt.frame = 1; // ### get from style? + if (current > -1 && current < combo->count()) { + opt.currentText = combo->text(current); + if (combo->pixmap(current)) + opt.currentIcon = QIcon(*combo->pixmap(current)); + } + opt.iconSize = QSize(22, 22); // ### need a sane value here +// if (container && container->isVisible()) +// opt.state |= QStyle::State_On; + return opt; + } + + int current; + int maxCount; + int sizeLimit; + Q3ComboBox::Policy p; + bool autoresize; + bool poppedUp; + bool mouseWasInsidePopup; + bool arrowPressed; + bool arrowDown; + bool discardNextMousePress; + bool shortClick; + bool useCompletion; + bool completeNow; + int completeAt; + bool duplicatesEnabled; + int fullHeight, currHeight; + + QLineEdit * ed; // /bin/ed rules! + QTimer *completionTimer; + + QSize sizeHint; + QHash<int, QPixmap> popupPixmaps; + +private: + bool usingLBox; + Q3ComboBoxPopup *pop; + Q3ListBox *lBox; + Q3ComboBox *combo; +}; + +void Q3ComboBoxData::updateLinedGeometry() +{ + if ( !ed || !combo ) + return; + + QStyleOptionComboBox opt = getStyleOption(); + QRect r = combo->style()->subControlRect( + QStyle::CC_ComboBox, &opt, QStyle::SC_ComboBoxEditField, combo); + + const QPixmap *pix = current < combo->count() ? combo->pixmap( current ) : 0; + if ( pix && pix->width() < r.width() ) + r.setLeft( r.left() + pix->width() + 4 ); + if ( r != ed->geometry() ) + ed->setGeometry( r ); +} + +static inline bool checkInsertIndex( const char *method, const char * name, + int count, int *index) +{ + bool range_err = (*index > count); +#if defined(QT_CHECK_RANGE) + if ( range_err ) + qWarning( "Q3ComboBox::%s: (%s) Index %d out of range", + method, name ? name : "<no name>", *index ); +#else + Q_UNUSED( method ) + Q_UNUSED( name ) +#endif + if ( *index < 0 ) // append + *index = count; + return !range_err; +} + + +static inline bool checkIndex( const char *method, const char * name, + int count, int index ) +{ + bool range_err = (index >= count); +#if defined(QT_CHECK_RANGE) + if ( range_err ) + qWarning( "Q3ComboBox::%s: (%s) Index %i out of range", + method, name ? name : "<no name>", index ); +#else + Q_UNUSED( method ) + Q_UNUSED( name ) +#endif + return !range_err; +} + + + +/*! + Constructs a combobox widget with parent \a parent called \a name. + + This constructor creates a popup list if the program uses Motif + (or Aqua) look and feel; this is compatible with Motif 1.x and + Aqua. + + Note: If you use this constructor to create your Q3ComboBox, then + the pixmap() function will always return 0. To workaround this, + use the other constructor. + +*/ + + + +Q3ComboBox::Q3ComboBox( QWidget *parent, const char *name ) + : QWidget( parent, name, Qt::WNoAutoErase ) +{ + d = new Q3ComboBoxData( this ); + QStyleOptionComboBox opt; + opt.init(this); + if ( style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, this) || + style()->styleHint(QStyle::SH_GUIStyle, &opt, this) == Qt::MotifStyle ) { + d->setPopupMenu( new Q3ComboBoxPopup( this, "in-combo" ) ); + d->popup()->setFont( font() ); + connect( d->popup(), SIGNAL(activated(int)), + SLOT(internalActivate(int)) ); + connect( d->popup(), SIGNAL(highlighted(int)), + SLOT(internalHighlight(int)) ); + } else { + setUpListBox(); + } + d->ed = 0; + d->current = 0; + d->maxCount = INT_MAX; + d->sizeLimit = 10; + d->p = AtBottom; + d->autoresize = false; + d->poppedUp = false; + d->arrowDown = false; + d->arrowPressed = false; + d->discardNextMousePress = false; + d->shortClick = false; + d->useCompletion = false; + d->completeAt = 0; + d->completeNow = false; + d->completionTimer = new QTimer( this ); + + setFocusPolicy( Qt::TabFocus ); + setBackgroundMode( Qt::PaletteButton ); +} + + +/*! + Constructs a combobox with a maximum size and either Motif 2.0 or + Windows look and feel. + + The input field can be edited if \a rw is true, otherwise the user + may only choose one of the items in the combobox. + + The \a parent and \a name arguments are passed on to the QWidget + constructor. +*/ + + +Q3ComboBox::Q3ComboBox( bool rw, QWidget *parent, const char *name ) + : QWidget( parent, name, Qt::WNoAutoErase ) +{ + d = new Q3ComboBoxData( this ); + setUpListBox(); + + QStyleOptionComboBox opt = d->getStyleOption(); + if(d->popup() && style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, this)) + d->popup()->setItemChecked(d->current, false); + d->maxCount = INT_MAX; + setSizeLimit(10); + d->p = AtBottom; + d->autoresize = false; + d->poppedUp = false; + d->arrowDown = false; + d->discardNextMousePress = false; + d->shortClick = false; + d->useCompletion = false; + d->completeAt = 0; + d->completeNow = false; + d->completionTimer = new QTimer( this ); + + setFocusPolicy( Qt::StrongFocus ); + + d->ed = 0; + if ( rw ) + setUpLineEdit(); + setBackgroundMode( Qt::PaletteButton, Qt::PaletteBase ); +} + + + +/*! + Destroys the combobox. +*/ + +Q3ComboBox::~Q3ComboBox() +{ + delete d; +} + +void Q3ComboBox::setDuplicatesEnabled( bool enable ) +{ + d->duplicatesEnabled = enable; +} + +bool Q3ComboBox::duplicatesEnabled() const +{ + return d->duplicatesEnabled; +} + +int Q3ComboBox::count() const +{ + if ( d->usingListBox() ) + return d->listBox()->count(); + else if (d->popup()) + return d->popup()->count(); + else + return 0; +} + + +/*! + \overload + + Inserts the \a list of strings at position \a index in the + combobox. + + This is only for compatibility since it does not support Unicode + strings. See insertStringList(). +*/ + +void Q3ComboBox::insertStrList( const Q3StrList &list, int index ) +{ + insertStrList( &list, index ); +} + +/*! + \overload + + Inserts the \a list of strings at position \a index in the + combobox. + + This is only for compatibility since it does not support Unicode + strings. See insertStringList(). +*/ + +void Q3ComboBox::insertStrList( const Q3StrList *list, int index ) +{ + if ( !list ) { +#if defined(QT_CHECK_NULL) + Q_ASSERT( list != 0 ); +#endif + return; + } + Q3StrListIterator it( *list ); + const char* tmp; + if ( index < 0 ) + index = count(); + while ( (tmp=it.current()) ) { + ++it; + if ( d->usingListBox() ) + d->listBox()->insertItem( QString::fromLatin1(tmp), index ); + else + d->popup()->insertItem( escapedComboString(QString::fromLatin1(tmp)), index, index ); + if ( index++ == d->current && d->current < count() ) { + if ( d->ed ) { + d->ed->setText( text( d->current ) ); + d->updateLinedGeometry(); + } else + update(); + currentChanged(); + } + } + if ( index != count() ) + reIndex(); +} + +/*! + Inserts the \a list of strings at position \a index in the + combobox. +*/ + +void Q3ComboBox::insertStringList( const QStringList &list, int index ) +{ + QStringList::ConstIterator it = list.begin(); + if ( index < 0 ) + index = count(); + while ( it != list.end() ) { + if ( d->usingListBox() ) + d->listBox()->insertItem( *it, index ); + else + d->popup()->insertItem( escapedComboString(*it), index, index ); + if ( index++ == d->current && d->current < count() ) { + if ( d->ed ) { + d->ed->setText( text( d->current ) ); + d->updateLinedGeometry(); + } else + update(); + currentChanged(); + } + ++it; + } + if ( index != count() ) + reIndex(); +} + +/*! + Inserts the array of char * \a strings at position \a index in the + combobox. + + The \a numStrings argument is the number of strings. If \a + numStrings is -1 (default), the \a strings array must be + terminated with 0. + + Example: + \snippet doc/src/snippets/code/src_qt3support_widgets_q3combobox.cpp 2 + + \sa insertStringList() +*/ + +void Q3ComboBox::insertStrList( const char **strings, int numStrings, int index) +{ + if ( !strings ) { +#if defined(QT_CHECK_NULL) + Q_ASSERT( strings != 0 ); +#endif + return; + } + if ( index < 0 ) + index = count(); + int i = 0; + while ( (numStrings<0 && strings[i]!=0) || i<numStrings ) { + if ( d->usingListBox() ) + d->listBox()->insertItem( QString::fromLatin1(strings[i]), index ); + else + d->popup()->insertItem( escapedComboString(QString::fromLatin1(strings[i])), index, index ); + i++; + if ( index++ == d->current && d->current < count() ) { + if ( d->ed ) { + d->ed->setText( text( d->current ) ); + d->updateLinedGeometry(); + } else + update(); + currentChanged(); + } + } + if ( index != count() ) + reIndex(); +} + + +/*! + Inserts a text item with text \a t, at position \a index. The item + will be appended if \a index is negative. +*/ + +void Q3ComboBox::insertItem( const QString &t, int index ) +{ + int cnt = count(); + if ( !checkInsertIndex( "insertItem", name(), cnt, &index ) ) + return; + if ( d->usingListBox() ) + d->listBox()->insertItem( t, index ); + else + d->popup()->insertItem( escapedComboString(t), index, index ); + if ( index != cnt ) + reIndex(); + if ( index == d->current && d->current < count() ) { + if ( d->ed ) { + d->ed->setText( text( d->current ) ); + d->updateLinedGeometry(); + } else + update(); + } + if ( index == d->current ) + currentChanged(); +} + +/*! + \overload + + Inserts a \a pixmap item at position \a index. The item will be + appended if \a index is negative. +*/ + +void Q3ComboBox::insertItem( const QPixmap &pixmap, int index ) +{ + int cnt = count(); + if ( !checkInsertIndex( "insertItem", name(), cnt, &index ) ) + return; + if ( d->usingListBox() ) + d->listBox()->insertItem( pixmap, index ); + else + d->popup()->insertItem( pixmap, index, index ); + if ( index != cnt ) + reIndex(); + if ( index == d->current && d->current < count() ) { + if ( d->ed ) { + d->ed->setText( text( d->current ) ); + d->updateLinedGeometry(); + } else + update(); + } + if ( index == d->current ) + currentChanged(); +} + +/*! + \overload + + Inserts a \a pixmap item with additional text \a text at position + \a index. The item will be appended if \a index is negative. +*/ + +void Q3ComboBox::insertItem( const QPixmap &pixmap, const QString& text, int index ) +{ + int cnt = count(); + if ( !checkInsertIndex( "insertItem", name(), cnt, &index ) ) + return; + if ( d->usingListBox() ) + d->listBox()->insertItem( pixmap, text, index ); + else + d->popup()->insertItem( pixmap, escapedComboString(text), index, index ); + if ( index != cnt ) + reIndex(); + if ( index == d->current && d->current < count() ) { + if ( d->ed ) { + d->ed->setText( this->text( d->current ) ); + d->updateLinedGeometry(); + } else + update(); + } + if ( index == d->current ) + currentChanged(); +} + + +/*! + Removes the item at position \a index. +*/ + +void Q3ComboBox::removeItem( int index ) +{ + int cnt = count(); + if ( !checkIndex( "removeItem", name(), cnt, index ) ) + return; + if ( d->usingListBox() ) { + QStyleOptionComboBox opt = d->getStyleOption(); + if ( style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, this) && d->popup() ) + d->popup()->removeItemAt( index ); + d->listBox()->removeItem( index ); + } else { + d->popup()->removeItemAt( index ); + } + if ( index != cnt-1 ) + reIndex(); + if ( index == d->current ) { + if ( d->ed ) { + QString s = QString::fromLatin1(""); + if (d->current < cnt - 1) + s = text( d->current ); + d->ed->setText( s ); + d->updateLinedGeometry(); + } + else { + if ( d->usingListBox() ) { + d->current = d->listBox()->currentItem(); + } else { + if (d->current > count()-1 && d->current > 0) + d->current--; + } + update(); + } + currentChanged(); + } + else { + if ( !d->ed ) { + if (d->current < cnt - 1) + setCurrentItem( d->current ); + else + setCurrentItem( d->current - 1 ); + } + } + +} + + +/*! + Removes all combobox items. +*/ + +void Q3ComboBox::clear() +{ + QStyleOptionComboBox opt = d->getStyleOption(); + if ( d->usingListBox() ) { + if ( style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, this) && d->popup() ) + d->popup()->clear(); + d->listBox()->resize( 0, 0 ); + d->listBox()->clear(); + } else { + d->popup()->clear(); + } + + if(d->popup() && style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, this)) + d->popup()->setItemChecked(d->current, false); + d->current = 0; + if ( d->ed ) { + d->ed->setText( QString::fromLatin1("") ); + d->updateLinedGeometry(); + } + currentChanged(); +} + + +QString Q3ComboBox::currentText() const +{ + if ( d->ed ) + return d->ed->text(); + else if ( d->current < count() ) + return text( currentItem() ); + else + return QString::null; +} + +void Q3ComboBox::setCurrentText( const QString& txt ) +{ + int i; + for ( i = 0; i < count(); i++) + if ( text( i ) == txt ) + break; + if ( i < count() ) + setCurrentItem( i ); + else if ( d->ed ) + d->ed->setText( txt ); + else + changeItem( txt, currentItem() ); +} + + +/*! + Returns the text item at position \a index, or QString::null if + the item is not a string. + + \sa currentText() +*/ + +QString Q3ComboBox::text( int index ) const +{ + if ( !checkIndex( "text", name(), count(), index ) ) + return QString::null; + if ( d->usingListBox() ) { + return d->listBox()->text( index ); + } else { + QString retText = d->popup()->text(index); + retText.replace(QLatin1String("&&"), QString(QLatin1Char('&'))); + return retText; + } +} + +/*! + Returns the pixmap item at position \a index, or 0 if the item is + not a pixmap. +*/ + +const QPixmap *Q3ComboBox::pixmap( int index ) const +{ + if ( !checkIndex( "pixmap", name(), count(), index ) ) + return 0; + + if (d->usingListBox()) { + return d->listBox()->pixmap( index ); + } else { + d->popupPixmaps[index] = d->popup()->pixmap(index); + return d->popupPixmaps[index].isNull() ? 0 : &d->popupPixmaps[index]; + } +} + +/*! + Replaces the item at position \a index with the text \a t. +*/ + +void Q3ComboBox::changeItem( const QString &t, int index ) +{ + if ( !checkIndex( "changeItem", name(), count(), index ) ) + return; + if ( d->usingListBox() ) + d->listBox()->changeItem( t, index ); + else + d->popup()->changeItem(index, t); + if ( index == d->current ) { + if ( d->ed ) { + d->ed->setText( text( d->current ) ); + d->updateLinedGeometry(); + } else + update(); + } +} + +/*! + \overload + + Replaces the item at position \a index with the pixmap \a im, + unless the combobox is editable. + + \sa insertItem() +*/ + +void Q3ComboBox::changeItem( const QPixmap &im, int index ) +{ + if ( !checkIndex( "changeItem", name(), count(), index ) ) + return; + if ( d->usingListBox() ) + d->listBox()->changeItem( im, index ); + else + d->popup()->changeItem(index, im); + if ( index == d->current ) + update(); +} + +/*! + \overload + + Replaces the item at position \a index with the pixmap \a im and + the text \a t. + + \sa insertItem() +*/ + +void Q3ComboBox::changeItem( const QPixmap &im, const QString &t, int index ) +{ + if ( !checkIndex( "changeItem", name(), count(), index ) ) + return; + if ( d->usingListBox() ) + d->listBox()->changeItem( im, t, index ); + else + d->popup()->changeItem(index, im, t); + if ( index == d->current ) + update(); +} + + +int Q3ComboBox::currentItem() const +{ + return d->current; +} + +void Q3ComboBox::setCurrentItem( int index ) +{ + if ( index == d->current && !d->ed ) { + return; + } + if ( !checkIndex( "setCurrentItem", name(), count(), index ) ) { + return; + } + + if ( d->usingListBox() && !( listBox()->item(index) && listBox()->item(index)->isSelectable() ) ) + return; + + QStyleOptionComboBox opt = d->getStyleOption(); + if(d->popup() && style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, this)) + d->popup()->setItemChecked(d->current, false); + d->current = index; + d->completeAt = 0; + if ( d->ed ) { + d->ed->setText( text( index ) ); + d->updateLinedGeometry(); + } + // ### We want to keep ListBox's currentItem in sync, even if NOT popuped... + if ( d->usingListBox() && d->listBox() ) { + d->listBox()->setCurrentItem( index ); + } else { + internalHighlight( index ); + // internalActivate( index ); ### this leads to weird behavior, as in 3.0.1 + } + + currentChanged(); +} + +/*! + Returns true if auto-resize is enabled; otherwise returns false. + + \sa autoResize +*/ + +bool Q3ComboBox::autoResize() const +{ + return d->autoresize; +} + +/*! + If \a enable is true, enable auto-resize; disable it otherwise. + + \sa autoResize +*/ + +void Q3ComboBox::setAutoResize( bool enable ) +{ + if ( (bool)d->autoresize != enable ) { + d->autoresize = enable; + if ( enable ) + adjustSize(); + } +} + + +/*! + \reimp + + This implementation caches the size hint to avoid resizing when + the contents change dynamically. To invalidate the cached value + call setFont(). +*/ +QSize Q3ComboBox::sizeHint() const +{ + if ( isVisible() && d->sizeHint.isValid() ) + return d->sizeHint; + + constPolish(); + int i, w; + QFontMetrics fm = fontMetrics(); + + int maxW = count() ? 18 : 7 * fm.width(QLatin1Char('x')) + 18; + int maxH = QMAX( fm.lineSpacing(), 14 ) + 2; + + if ( !d->usingListBox() ) { + w = d->popup()->sizeHint().width() - 2* d->popup()->frameWidth(); + if ( w > maxW ) + maxW = w; + } else { + for( i = 0; i < count(); i++ ) { + w = d->listBox()->item( i )->width( d->listBox() ); + if ( w > maxW ) + maxW = w; + } + } + + QStyleOptionComboBox opt = d->getStyleOption(); + d->sizeHint = (style()->sizeFromContents(QStyle::CT_ComboBox, &opt, QSize(maxW, maxH), this). + expandedTo(QApplication::globalStrut())); + + return d->sizeHint; +} + + +/*! + \internal + Receives activated signals from an internal popup list and emits + the activated() signal. +*/ + +void Q3ComboBox::internalActivate( int index ) +{ + QStyleOptionComboBox opt = d->getStyleOption(); + if ( d->current != index ) { + if ( !d->usingListBox() || listBox()->item( index )->isSelectable() ) { + if (d->popup() && style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, this)) + d->popup()->setItemChecked(d->current, false); + d->current = index; + currentChanged(); + } + } + if ( d->usingListBox() ) + popDownListBox(); + else + d->popup()->removeEventFilter( this ); + d->poppedUp = false; + + QString t( text( index ) ); + if ( d->ed ) { + d->ed->setText( t ); + d->updateLinedGeometry(); + } + emit activated( index ); + emit activated( t ); +} + +/*! + \internal + Receives highlighted signals from an internal popup list and emits + the highlighted() signal. +*/ + +void Q3ComboBox::internalHighlight( int index ) +{ + emit highlighted( index ); + QString t = text( index ); + if ( !t.isNull() ) + emit highlighted( t ); +} + +/*! + \internal + Receives timeouts after a click. Used to decide if a Motif style + popup should stay up or not after a click. +*/ +void Q3ComboBox::internalClickTimeout() +{ + d->shortClick = false; +} + +/*! + Sets the palette for both the combobox button and the combobox + popup list to \a palette. +*/ + +void Q3ComboBox::setPalette( const QPalette &palette ) +{ + QWidget::setPalette( palette ); + if ( d->listBox() ) + d->listBox()->setPalette( palette ); + if ( d->popup() ) + d->popup()->setPalette( palette ); +} + +/*! + Sets the font for both the combobox button and the combobox popup + list to \a font. +*/ + +void Q3ComboBox::setFont( const QFont &font ) +{ + d->sizeHint = QSize(); // invalidate size hint + QWidget::setFont( font ); + if ( d->usingListBox() ) + d->listBox()->setFont( font ); + else + d->popup()->setFont( font ); + if (d->ed) + d->ed->setFont( font ); + if ( d->autoresize ) + adjustSize(); +} + + +/*!\reimp +*/ + +void Q3ComboBox::resizeEvent( QResizeEvent * e ) +{ + if ( d->ed ) + d->updateLinedGeometry(); + if ( d->listBox() ) + d->listBox()->resize( width(), d->listBox()->height() ); + QWidget::resizeEvent( e ); +} + +/*!\reimp +*/ + +void Q3ComboBox::paintEvent( QPaintEvent * ) +{ + QPainter p( this ); + const QColorGroup & g = colorGroup(); + p.setPen(g.text()); + + if ( width() < 5 || height() < 5 ) { + qDrawShadePanel( &p, rect(), g, false, 2, + &g.brush( QColorGroup::Button ) ); + return; + } + + QStyleOptionComboBox opt = d->getStyleOption(); + bool reverse = QApplication::reverseLayout(); + if ( !d->usingListBox() && + style()->styleHint(QStyle::SH_GUIStyle) == Qt::MotifStyle) { // motif 1.x style + int dist, buttonH, buttonW; + dist = 8; + buttonH = 7; + buttonW = 11; + int xPos; + int x0; + int w = width() - dist - buttonW - 1; + if ( reverse ) { + xPos = dist + 1; + x0 = xPos + 4; + } else { + xPos = w; + x0 = 4; + } + qDrawShadePanel( &p, rect(), g, false, + style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &opt, this), + &g.brush( QColorGroup::Button ) ); + qDrawShadePanel( &p, xPos, (height() - buttonH)/2, + buttonW, buttonH, g, false, + style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &opt, this) ); + QRect clip( x0, 2, w - 2 - 4 - 5, height() - 4 ); + QString str = d->popup()->text( this->d->current ); + if ( !str.isNull() ) { + p.drawText( clip, Qt::AlignCenter | Qt::TextSingleLine, str ); + } + + QPixmap pix = d->popup()->pixmap( this->d->current ); + QIcon iconSet = d->popup()->iconSet( this->d->current ); + if (!pix.isNull() || !iconSet.isNull()) { + QPixmap pm = ( !pix.isNull() ? pix : iconSet.pixmap() ); + p.setClipRect( clip ); + p.drawPixmap( 4, (height()-pm.height())/2, pm ); + p.setClipping( false ); + } + + if ( hasFocus() ) + p.drawRect( xPos - 5, 4, width() - xPos + 1 , height() - 8 ); + } else if(!d->usingListBox()) { + style()->drawComplexControl(QStyle::CC_ComboBox, &opt, &p, this); + QRect re = style()->subControlRect(QStyle::CC_ComboBox, &opt, + QStyle::SC_ComboBoxEditField, this); + p.setClipRect( re ); + + QString str = d->popup()->text( this->d->current ); + QPixmap pix = d->popup()->pixmap( this->d->current ); + if ( !str.isNull() ) { + p.save(); + p.setFont(font()); + QFontMetrics fm(font()); + int x = re.x(), y = re.y() + fm.ascent(); + x += pix.width() + 5; + p.drawText( x, y, str ); + p.restore(); + } + if (!pix.isNull()) { + p.fillRect(re.x(), re.y(), pix.width() + 4, re.height(), + colorGroup().brush(QColorGroup::Base)); + p.drawPixmap(re.x() + 2, re.y() + (re.height() - pix.height()) / 2, pix); + } + } else { + style()->drawComplexControl(QStyle::CC_ComboBox, &opt, &p, this); + QRect re = style()->subControlRect(QStyle::CC_ComboBox, &opt, + QStyle::SC_ComboBoxEditField, this); + p.setClipRect(re); + + if ( !d->ed ) { + Q3ListBoxItem * item = d->listBox()->item( d->current ); + if ( item ) { + int itemh = item->height( d->listBox() ); + p.translate( re.x(), re.y() + (re.height() - itemh)/2 ); + item->paint( &p ); + } + } else if ( d->listBox() && d->listBox()->item( d->current ) ) { + Q3ListBoxItem * item = d->listBox()->item( d->current ); + const QPixmap *pix = item->pixmap(); + if ( pix ) { + p.fillRect( re.x(), re.y(), pix->width() + 4, re.height(), + colorGroup().brush( QColorGroup::Base ) ); + p.drawPixmap( re.x() + 2, re.y() + + ( re.height() - pix->height() ) / 2, *pix ); + } + } + p.setClipping( false ); + } +} + + +/*!\reimp +*/ + +void Q3ComboBox::mousePressEvent( QMouseEvent *e ) +{ + if ( e->button() != Qt::LeftButton ) + return; + if ( d->discardNextMousePress ) { + d->discardNextMousePress = false; + return; + } + + QStyleOptionComboBox opt = d->getStyleOption(); + QRect arrowRect = style()->subControlRect(QStyle::CC_ComboBox, &opt, QStyle::SC_ComboBoxArrow + , this); + + // Correction for motif style, where arrow is smaller + // and thus has a rect that doesn't fit the button. + arrowRect.setHeight( QMAX( height() - (2 * arrowRect.y()), arrowRect.height() ) ); + + if ( count() && ( !editable() || arrowRect.contains( e->pos() ) ) ) { + d->arrowPressed = false; + + if ( d->usingListBox() ) { + listBox()->blockSignals( true ); + qApp->sendEvent( listBox(), e ); // trigger the listbox's autoscroll + listBox()->setCurrentItem(d->current); + listBox()->blockSignals( false ); + popup(); + if ( arrowRect.contains( e->pos() ) ) { + d->arrowPressed = true; + d->arrowDown = true; + repaint( false ); + } + } else { + popup(); + } + QTimer::singleShot( 200, this, SLOT(internalClickTimeout())); + d->shortClick = true; + } +} + +/*!\reimp +*/ + +void Q3ComboBox::mouseMoveEvent( QMouseEvent * ) +{ +} + +/*!\reimp +*/ + +void Q3ComboBox::mouseReleaseEvent( QMouseEvent * ) +{ +} + +/*!\reimp +*/ + +void Q3ComboBox::mouseDoubleClickEvent( QMouseEvent *e ) +{ + mousePressEvent( e ); +} + + +/*!\reimp +*/ + +void Q3ComboBox::keyPressEvent( QKeyEvent *e ) +{ + bool handleEventHere = d->usingListBox() || !d->poppedUp; + + int c = currentItem(); + if ( ( e->key() == Qt::Key_F4 && e->state() == 0 ) || + ( e->key() == Qt::Key_Down && (e->state() & Qt::AltModifier) ) || + ( !d->ed && e->key() == Qt::Key_Space ) ) { + if ( count() ) { + if ( !d->usingListBox() ) + d->popup()->setActiveItem( this->d->current ); + popup(); + } + return; + } else if ( handleEventHere && e->key() == Qt::Key_Up ) { + if ( c > 0 ) + setCurrentItem( c-1 ); + } else if ( handleEventHere && e->key() == Qt::Key_Down ) { + if ( ++c < count() ) + setCurrentItem( c ); + } else if ( handleEventHere && e->key() == Qt::Key_Home && ( !d->ed || !d->ed->hasFocus() ) ) { + setCurrentItem( 0 ); + } else if ( handleEventHere && e->key() == Qt::Key_End && ( !d->ed || !d->ed->hasFocus() ) ) { + setCurrentItem( count()-1 ); + } else if ( !d->ed && e->ascii() >= 32 && !e->text().isEmpty() ) { + if ( !d->completionTimer->isActive() ) { + d->completeAt = 0; + c = completionIndex( e->text(), ++c ); + if ( c >= 0 ) { + setCurrentItem( c ); + d->completeAt = e->text().length(); + } + } else { + d->completionTimer->stop(); + QString ct = currentText().left( d->completeAt ) + e->text(); + c = completionIndex( ct, c ); + if ( c < 0 && d->completeAt > 0 ) { + c = completionIndex( e->text(), 0 ); + ct = e->text(); + } + d->completeAt = 0; + if ( c >= 0 ) { + setCurrentItem( c ); + d->completeAt = ct.length(); + } + } + d->completionTimer->start( 400, true ); + } else { + e->ignore(); + return; + } + + c = currentItem(); + if ( count() && !text( c ).isNull() ) + emit activated( text( c ) ); + emit activated( c ); +} + + +/*!\reimp +*/ + +void Q3ComboBox::focusInEvent( QFocusEvent * e ) +{ + QWidget::focusInEvent( e ); + d->completeNow = false; + d->completeAt = 0; +} + +/*!\reimp +*/ + +void Q3ComboBox::focusOutEvent( QFocusEvent * e ) +{ + QWidget::focusOutEvent( e ); + d->completeNow = false; + d->completeAt = 0; +} + +/*!\reimp +*/ +#ifndef QT_NO_WHEELEVENT +void Q3ComboBox::wheelEvent( QWheelEvent *e ) +{ + if ( d->poppedUp ) { + if ( d->usingListBox() ) { + QApplication::sendEvent( d->listBox(), e ); + } + } else { + if ( e->delta() > 0 ) { + int c = currentItem(); + if ( c > 0 ) { + setCurrentItem( c-1 ); + emit activated( currentItem() ); + emit activated( currentText() ); + } + } else { + int c = currentItem(); + if ( ++c < count() ) { + setCurrentItem( c ); + emit activated( currentItem() ); + emit activated( currentText() ); + } + } + e->accept(); + } +} +#endif + +/*! + \internal + Calculates the listbox height needed to contain all items, or as + many as the list box is supposed to contain. +*/ +static int listHeight( Q3ListBox *l, int sl ) +{ + if ( l->count() > 0 ) + return QMIN( l->count(), (uint)sl) * l->item( 0 )->height(l); + else + return l->sizeHint().height(); +} + + +/*! + Pops up the combobox popup list. + + If the list is empty, no items appear. +*/ + +void Q3ComboBox::popup() +{ + if ( !count() || d->poppedUp ) + return; + + QStyleOptionComboBox opt = d->getStyleOption(); + if( !d->usingListBox() || style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, this) ) { + if(d->usingListBox()) { + if(!d->popup()) { + Q3ComboBoxPopup *p = new Q3ComboBoxPopup( this, "in-combo" ); + d->setPopupMenu( p, false ); + p->setFont( font() ); + connect( p, SIGNAL(activated(int)), SLOT(internalActivate(int)) ); + connect( p, SIGNAL(highlighted(int)), SLOT(internalHighlight(int)) ); + } + d->popup()->clear(); + for(unsigned int i = 0; i < d->listBox()->count(); i++) { + Q3ListBoxItem *item = d->listBox()->item(i); + if(item->rtti() == Q3ListBoxText::RTTI) { + d->popup()->insertItem(escapedComboString(item->text()), i, i); + } else if(item->rtti() == Q3ListBoxPixmap::RTTI) { + if(item->pixmap()) + d->popup()->insertItem(QIcon(*item->pixmap()), escapedComboString(item->text()), i, i); + else + d->popup()->insertItem(escapedComboString(item->text()), i, i); + } else { + d->popup()->insertItem(new Q3ComboBoxPopupItem(item), i, i); + } + } + } + d->popup()->installEventFilter( this ); + if(d->popup() && style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, this)) + d->popup()->setItemChecked(this->d->current, true); + d->popup()->popup( mapToGlobal( QPoint(0,0) ), this->d->current ); + update(); + } else { + // Send all listbox events to eventFilter(): + Q3ListBox* lb = d->listBox(); + lb->triggerUpdate( true ); + lb->installEventFilter( this ); + d->mouseWasInsidePopup = false; + int w = lb->variableWidth() ? lb->sizeHint().width() : width(); + int h = listHeight( lb, d->sizeLimit ) + 2; + QRect screen = QApplication::desktop()->availableGeometry( this ); + + int sx = screen.x(); // screen pos + int sy = screen.y(); + int sw = screen.width(); // screen width + int sh = screen.height(); // screen height + QPoint pos = mapToGlobal( QPoint(0,height()) ); + // ## Similar code is in QPopupMenu + int x = pos.x(); + int y = pos.y(); + + // the complete widget must be visible + if ( x + w > sx + sw ) + x = sx+sw - w; + if ( x < sx ) + x = sx; + if (y + h > sy+sh && y - h - height() >= 0 ) + y = y - h - height(); + + opt.rect = QRect(x, y, w, h); + QRect rect = style()->subControlRect(QStyle::CC_ComboBox, &opt, + QStyle::SC_ComboBoxListBoxPopup, this); + + // work around older styles that don't implement the combobox + // listbox popup subcontrol + if ( rect.isNull() ) + rect.setRect( x, y, w, h ); + lb->setGeometry( rect ); + + lb->raise(); + bool block = lb->signalsBlocked(); + lb->blockSignals( true ); + Q3ListBoxItem* currentLBItem = 0; + if ( editable() && currentText() != text( currentItem() ) ) + currentLBItem = lb->findItem( currentText() ); + + currentLBItem = currentLBItem ? currentLBItem : lb->item( d->current ); + + lb->setCurrentItem( currentLBItem ); + lb->setContentsPos( lb->contentsX(), + lb->viewportToContents( lb->itemRect( currentLBItem ).topLeft() ).y() ); + + // set the current item to also be the selected item if it isn't already + if ( currentLBItem && currentLBItem->isSelectable() && !currentLBItem->isSelected() ) + lb->setSelected( currentLBItem, true ); + lb->blockSignals( block ); + lb->setVScrollBarMode(Q3ScrollView::Auto); + +#ifndef QT_NO_EFFECTS + if ( QApplication::isEffectEnabled( Qt::UI_AnimateCombo ) ) { + if ( lb->y() < mapToGlobal(QPoint(0,0)).y() ) + qScrollEffect( lb, QEffects::UpScroll ); + else + qScrollEffect( lb ); + } else +#endif + lb->show(); + } + d->poppedUp = true; +} + + +/*! + Updates the widget mask. + + \sa QWidget::setMask() +*/ +void Q3ComboBox::updateMask() +{ + QBitmap bm( size() ); + bm.fill( Qt::color0 ); + + QStyleOptionComboBox opt = d->getStyleOption(); + { + QPainter p(&bm); + p.initFrom(this); + p.fillRect(opt.rect, Qt::color1); // qcommonstyle old drawComplexControl implementation + } + + setMask( bm ); +} + +/*! + \internal + Pops down (removes) the combobox popup list box. +*/ +void Q3ComboBox::popDownListBox() +{ + Q_ASSERT( d->usingListBox() ); + d->listBox()->removeEventFilter( this ); + d->listBox()->viewport()->removeEventFilter( this ); + d->listBox()->hide(); + d->listBox()->setCurrentItem( d->current ); + if ( d->arrowDown ) { + d->arrowDown = false; + repaint( false ); + } + d->poppedUp = false; +} + + +/*! + \internal + Re-indexes the identifiers in the popup list. +*/ + +void Q3ComboBox::reIndex() +{ + if ( !d->usingListBox() ) { + int cnt = count(); + while ( cnt-- ) + d->popup()->setId( cnt, cnt ); + } +} + +/*! + \internal + Repaints the combobox. +*/ + +void Q3ComboBox::currentChanged() +{ + if ( d->autoresize ) + adjustSize(); + update(); + +#if defined(QT_ACCESSIBILITY_SUPPORT) + QAccessible::updateAccessibility( this, 0, QAccessible::ValueChanged ); +#endif +} + +/*! \reimp + + \internal + + The event filter steals events from the popup or listbox when they + are popped up. It makes the popup stay up after a short click in + motif style. In windows style it toggles the arrow button of the + combobox field, and activates an item and takes down the listbox + when the mouse button is released. +*/ + +bool Q3ComboBox::eventFilter( QObject *object, QEvent *event ) +{ + QStyleOptionComboBox opt = d->getStyleOption(); + if ( !event ) + return true; + else if ( object == d->ed ) { + if ( event->type() == QEvent::KeyPress ) { + bool isAccepted = ( (QKeyEvent*)event )->isAccepted(); + keyPressEvent( (QKeyEvent *)event ); + if ( ((QKeyEvent *)event)->isAccepted() ) { + d->completeNow = false; + return true; + } else if ( ((QKeyEvent *)event)->key() != Qt::Key_End ) { + d->completeNow = true; + d->completeAt = d->ed->cursorPosition(); + } + if ( isAccepted ) + ( (QKeyEvent*)event )->accept(); + else + ( (QKeyEvent*)event )->ignore(); + } else if ( event->type() == QEvent::KeyRelease ) { + keyReleaseEvent( (QKeyEvent *)event ); + return ((QKeyEvent *)event)->isAccepted(); + } else if ( event->type() == QEvent::FocusIn ) { + focusInEvent( (QFocusEvent *)event ); + } else if ( event->type() == QEvent::FocusOut ) { + focusOutEvent( (QFocusEvent *)event ); + } else if ( d->useCompletion && d->completeNow ) { + d->completeNow = false; + if ( !d->ed->text().isNull() && + d->ed->cursorPosition() > d->completeAt && + d->ed->cursorPosition() == (int)d->ed->text().length() ) { + QString ct( d->ed->text() ); + int i = completionIndex( ct, currentItem() ); + if ( i > -1 ) { + QString it = text( i ); + d->ed->validateAndSet( it, ct.length(), + ct.length(), it.length() ); + d->current = i; + // ### sets current item without emitting signals. This is to + // make sure the right item is current if you change current with + // wheel/up/down. While typing current is not valid anyway. Fix properly + // in 4.0. + } + } + } + } else if ( d->usingListBox() && ( object == d->listBox() || + object == d->listBox()->viewport() )) { + QMouseEvent *e = (QMouseEvent*)event; + switch( event->type() ) { + case QEvent::MouseMove: + if ( !d->mouseWasInsidePopup ) { + QPoint pos = e->pos(); + if ( d->listBox()->rect().contains( pos ) ) + d->mouseWasInsidePopup = true; + // Check if arrow button should toggle + if ( d->arrowPressed ) { + QPoint comboPos; + comboPos = mapFromGlobal( d->listBox()->mapToGlobal(pos) ); + QRect arrowRect = style()->subControlRect(QStyle::CC_ComboBox, &opt, + QStyle::SC_ComboBoxArrow, this); + if ( arrowRect.contains( comboPos ) ) { + if ( !d->arrowDown ) { + d->arrowDown = true; + repaint( false ); + } + } else { + if ( d->arrowDown ) { + d->arrowDown = false; + repaint( false ); + } + } + } + } else if ((e->state() & ( Qt::RightButton | Qt::LeftButton | Qt::MidButton ) ) == 0 && + style()->styleHint(QStyle::SH_ComboBox_ListMouseTracking, &opt, this)) { + QWidget *mouseW = QApplication::widgetAt( e->globalPos(), true ); + if ( mouseW == d->listBox()->viewport() ) { //### + QMouseEvent m( QEvent::MouseMove, e->pos(), e->globalPos(), + Qt::NoButton, Qt::LeftButton ); + QApplication::sendEvent( object, &m ); //### Evil + return true; + } + } + + break; + case QEvent::MouseButtonRelease: + if ( d->listBox()->rect().contains( e->pos() ) ) { + QMouseEvent tmp( QEvent::MouseButtonDblClick, + e->pos(), e->button(), e->state() ) ; + // will hide popup + QApplication::sendEvent( object, &tmp ); + return true; + } else { + if ( d->mouseWasInsidePopup ) { + popDownListBox(); + } else { + d->arrowPressed = false; + if ( d->arrowDown ) { + d->arrowDown = false; + repaint( false ); + } + } + } + break; + case QEvent::MouseButtonDblClick: + case QEvent::MouseButtonPress: + if ( !d->listBox()->rect().contains( e->pos() ) ) { + QPoint globalPos = d->listBox()->mapToGlobal(e->pos()); + if ( QApplication::widgetAt( globalPos, true ) == this ) { + d->discardNextMousePress = true; + // avoid popping up again + } + popDownListBox(); + return true; + } + break; + case QEvent::KeyPress: + switch( ((QKeyEvent *)event)->key() ) { + case Qt::Key_Up: + case Qt::Key_Down: + if ( !(((QKeyEvent *)event)->state() & Qt::AltModifier) ) + break; + case Qt::Key_F4: + case Qt::Key_Escape: + if ( d->poppedUp ) { + popDownListBox(); + return true; + } + break; + case Qt::Key_Enter: + case Qt::Key_Return: + // work around QDialog's enter handling + return false; + default: + break; + } + break; + case QEvent::Hide: + popDownListBox(); + break; + default: + break; + } + } else if ( (!d->usingListBox() || style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, this)) && + object == d->popup() ) { + QMouseEvent *e = (QMouseEvent*)event; + switch ( event->type() ) { + case QEvent::MouseButtonRelease: + if ( d->shortClick ) { + QMouseEvent tmp( QEvent::MouseMove, + e->pos(), e->button(), e->state() ) ; + // highlight item, but don't pop down: + QApplication::sendEvent( object, &tmp ); + return true; + } + break; + case QEvent::MouseButtonDblClick: + case QEvent::MouseButtonPress: + if ( !d->popup()->rect().contains( e->pos() ) ) { + d->poppedUp = false; + d->arrowDown = false; + // remove filter, event will take down popup: + d->popup()->removeEventFilter( this ); + // ### uglehack! + // call internalHighlight so the highlighed signal + // will be emitted at least as often as necessary. + // it may be called more often than necessary + internalHighlight( d->current ); + } + break; + case QEvent::Hide: + d->poppedUp = false; + break; + default: + break; + } + } + return QWidget::eventFilter( object, event ); +} + + +/*! + Returns the index of the first item \e after \a startingAt of + which \a prefix is a case-insensitive prefix. Returns -1 if no + items start with \a prefix. +*/ + +int Q3ComboBox::completionIndex( const QString & prefix, + int startingAt = 0 ) const +{ + int start = startingAt; + if ( start < 0 || start >= count() ) + start = 0; + if ( start >= count() ) + return -1; + QString match = prefix.lower(); + if ( match.length() < 1 ) + return start; + + QString current; + int i = start; + do { + current = text( i ).lower(); + if ( current.startsWith( match ) ) + return i; + i++; + if ( i == count() ) + i = 0; + } while ( i != start ); + return -1; +} + +int Q3ComboBox::sizeLimit() const +{ + return d ? d->sizeLimit : INT_MAX; +} + +void Q3ComboBox::setSizeLimit( int lines ) +{ + d->sizeLimit = lines; +} + + +int Q3ComboBox::maxCount() const +{ + return d ? d->maxCount : INT_MAX; +} + +void Q3ComboBox::setMaxCount( int count ) +{ + int l = this->count(); + while( --l > count ) + removeItem( l ); + d->maxCount = count; +} + +Q3ComboBox::Policy Q3ComboBox::insertionPolicy() const +{ + return d->p; +} + +void Q3ComboBox::setInsertionPolicy( Policy policy ) +{ + d->p = policy; +} + + + +/*! + Internal slot to keep the line editor up to date. +*/ + +void Q3ComboBox::returnPressed() +{ + QString s( d->ed->text() ); + + if ( s.isEmpty() ) + return; + + int c = 0; + bool doInsert = true; + if ( !d->duplicatesEnabled ) { + for ( int i = 0; i < count(); ++i ) { + if ( s == text( i ) ) { + doInsert = false; + c = i; + break; + } + } + } + + if ( doInsert ) { + if ( insertionPolicy() != NoInsert ) { + int cnt = count(); + while ( cnt >= d->maxCount ) { + removeItem( --cnt ); + } + } + + switch ( insertionPolicy() ) { + case InsertAtCurrent: + if (count() == 0) + insertItem(s); + else if ( s != text( currentItem() ) ) + changeItem( s, currentItem() ); + emit activated( currentItem() ); + emit activated( s ); + return; + case NoInsert: + emit activated( s ); + return; + case InsertAtTop: + c = 0; + break; + case InsertAtBottom: + c = count(); + break; + case InsertBeforeCurrent: + c = currentItem(); + break; + case InsertAfterCurrent: + c = count() == 0 ? 0 : currentItem() + 1; + break; + } + insertItem( s, c ); + } + + setCurrentItem( c ); + emit activated( c ); + emit activated( s ); +} + + +/*! + Enables the combobox if \a enable is true; otherwise disables it. + + \sa QWidget::enabled +*/ + +void Q3ComboBox::setEnabled( bool enable ) +{ + if ( !enable ) { + if ( d->usingListBox() ) { + popDownListBox(); + } else { + d->popup()->removeEventFilter( this ); + d->popup()->close(); + d->poppedUp = false; + } + } + QWidget::setEnabled( enable ); +} + + + +/*! + Applies the validator \a v to the combobox so that only text which + is valid according to \a v is accepted. + + This function does nothing if the combobox is not editable. + + \sa validator() clearValidator() QValidator +*/ + +void Q3ComboBox::setValidator( const QValidator * v ) +{ + if ( d && d->ed ) + d->ed->setValidator( v ); +} + + +/*! + Returns the validator which constrains editing for this combobox + if there is one; otherwise returns 0. + + \sa setValidator() clearValidator() QValidator +*/ + +const QValidator * Q3ComboBox::validator() const +{ + return d && d->ed ? d->ed->validator() : 0; +} + + +/*! + This slot is equivalent to setValidator( 0 ). +*/ + +void Q3ComboBox::clearValidator() +{ + if ( d && d->ed ) + d->ed->setValidator( 0 ); +} + + +/*! + Sets the combobox to use \a newListBox instead of the current list + box or popup. As a side effect, it clears the combobox of its + current contents. + + \warning Q3ComboBox assumes that newListBox->text(n) returns + non-null for 0 \<= n \< newListbox->count(). This assumption is + necessary because of the line edit in Q3ComboBox. +*/ + +void Q3ComboBox::setListBox( Q3ListBox * newListBox ) +{ + clear(); + + if ( d->usingListBox() ) { + delete d->listBox(); + } else { + delete d->popup(); + d->setPopupMenu(0, false); + } + + newListBox->reparent( this, Qt::WType_Popup, QPoint(0,0), false ); + d->setListBox( newListBox ); + d->listBox()->setFont( font() ); + d->listBox()->setPalette( palette() ); + d->listBox()->setVScrollBarMode(Q3ScrollView::AlwaysOff); + d->listBox()->setHScrollBarMode(Q3ScrollView::AlwaysOff); + d->listBox()->setFrameStyle( Q3Frame::Box | Q3Frame::Plain ); + d->listBox()->setLineWidth( 1 ); + d->listBox()->resize( 100, 10 ); + + connect( d->listBox(), SIGNAL(selected(int)), + SLOT(internalActivate(int)) ); + connect( d->listBox(), SIGNAL(highlighted(int)), + SLOT(internalHighlight(int))); +} + + +/*! + Returns the current list box, or 0 if there is no list box. + (Q3ComboBox can use QPopupMenu instead of QListBox.) Provided to + match setListBox(). + + \sa setListBox() +*/ + +Q3ListBox * Q3ComboBox::listBox() const +{ + return d && d->usingListBox() ? d->listBox() : 0; +} + +/*! + Returns the line edit, or 0 if there is no line edit. + + Only editable listboxes have a line editor. +*/ +QLineEdit* Q3ComboBox::lineEdit() const +{ + return d->ed; +} + + + +/*! + Clears the line edit without changing the combobox's contents. + Does nothing if the combobox isn't editable. + + This is particularly useful when using a combobox as a line edit + with history. For example you can connect the combobox's + activated() signal to clearEdit() in order to present the user + with a new, empty line as soon as Enter is pressed. + + \sa setEditText() +*/ + +void Q3ComboBox::clearEdit() +{ + if ( d && d->ed ) + d->ed->clear(); +} + + +/*! + Sets the text in the line edit to \a newText without changing the + combobox's contents. Does nothing if the combobox isn't editable. + + This is useful e.g. for providing a good starting point for the + user's editing and entering the change in the combobox only when + the user presses Enter. + + \sa clearEdit() insertItem() +*/ + +void Q3ComboBox::setEditText( const QString &newText ) +{ + if ( d && d->ed ) { + d->updateLinedGeometry(); + d->ed->setText( newText ); + } +} + +void Q3ComboBox::setAutoCompletion( bool enable ) +{ + d->useCompletion = enable; + d->completeNow = false; +} + + +bool Q3ComboBox::autoCompletion() const +{ + return d->useCompletion; +} + +/*!\reimp + */ +void Q3ComboBox::styleChange( QStyle& s ) +{ + d->sizeHint = QSize(); // invalidate size hint... + if ( d->ed ) + d->updateLinedGeometry(); + QWidget::styleChange( s ); +} + +bool Q3ComboBox::editable() const +{ + return d->ed != 0; +} + +void Q3ComboBox::setEditable( bool y ) +{ + if ( y == editable() ) + return; + if ( y ) { + if ( !d->usingListBox() ) + setUpListBox(); + setUpLineEdit(); + d->ed->show(); + if ( currentItem() ) + setEditText( currentText() ); + } else { + delete d->ed; + d->ed = 0; + } + + setFocusPolicy(Qt::StrongFocus); + updateGeometry(); + update(); +} + + +void Q3ComboBox::setUpListBox() +{ + d->setListBox( new Q3ListBox( this, "in-combo", Qt::WType_Popup ) ); + d->listBox()->setFont( font() ); + d->listBox()->setPalette( palette() ); + d->listBox()->setVScrollBarMode( Q3ListBox::AlwaysOff ); + d->listBox()->setHScrollBarMode( Q3ListBox::AlwaysOff ); + d->listBox()->setFrameStyle( Q3Frame::Box | Q3Frame::Plain ); + d->listBox()->setLineWidth( 1 ); + d->listBox()->resize( 100, 10 ); + + connect( d->listBox(), SIGNAL(selected(int)), + SLOT(internalActivate(int)) ); + connect( d->listBox(), SIGNAL(highlighted(int)), + SLOT(internalHighlight(int))); +} + + +void Q3ComboBox::setUpLineEdit() +{ + if ( !d->ed ) + setLineEdit( new QLineEdit( this, "combo edit" ) ); +} + +/*! + Sets the line edit to use \a edit instead of the current line edit. +*/ + +void Q3ComboBox::setLineEdit( QLineEdit *edit ) +{ + if ( !edit ) { +#if defined(QT_CHECK_NULL) + Q_ASSERT( edit != 0 ); +#endif + return; + } + + edit->setText( currentText() ); + delete d->ed; + d->ed = edit; + + if ( edit->parent() != this ) + edit->reparent( this, QPoint(0,0), false ); + + connect (edit, SIGNAL(textChanged(QString)), + this, SIGNAL(textChanged(QString)) ); + connect( edit, SIGNAL(returnPressed()), SLOT(returnPressed()) ); + + edit->setFrame( false ); + d->updateLinedGeometry(); + edit->installEventFilter( this ); + setFocusProxy( edit ); + setFocusPolicy(Qt::StrongFocus); + setInputMethodEnabled( true ); + + if ( !d->usingListBox() ) + setUpListBox(); + + if ( isVisible() ) + edit->show(); + + updateGeometry(); + update(); +} + +/*! + Hides the combobox. + + \sa QWidget::hide() +*/ +void Q3ComboBox::hide() +{ + QWidget::hide(); + + if (listBox()) + listBox()->hide(); + else if (d->popup()) + d->popup()->hide(); +} + +QT_END_NAMESPACE + +#endif // QT_NO_COMBOBOX |