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/gui/widgets/qcombobox.cpp | |
download | Qt-e5fcad302d86d316390c6b0f62759a067313e8a9.zip Qt-e5fcad302d86d316390c6b0f62759a067313e8a9.tar.gz Qt-e5fcad302d86d316390c6b0f62759a067313e8a9.tar.bz2 |
Long live Qt 4.5!
Diffstat (limited to 'src/gui/widgets/qcombobox.cpp')
-rw-r--r-- | src/gui/widgets/qcombobox.cpp | 3186 |
1 files changed, 3186 insertions, 0 deletions
diff --git a/src/gui/widgets/qcombobox.cpp b/src/gui/widgets/qcombobox.cpp new file mode 100644 index 0000000..9a0c404 --- /dev/null +++ b/src/gui/widgets/qcombobox.cpp @@ -0,0 +1,3186 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui 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 "qcombobox.h" + +#ifndef QT_NO_COMBOBOX +#include <qstylepainter.h> +#include <qlineedit.h> +#include <qapplication.h> +#include <qdesktopwidget.h> +#include <qlistview.h> +#include <qtableview.h> +#include <qitemdelegate.h> +#include <qmap.h> +#include <qmenu.h> +#include <qevent.h> +#include <qlayout.h> +#include <qscrollbar.h> +#include <qtreeview.h> +#include <qheaderview.h> +#ifndef QT_NO_IM +#include "qinputcontext.h" +#endif +#include <private/qcombobox_p.h> +#include <private/qabstractitemmodel_p.h> +#include <qdebug.h> + +#ifdef Q_WS_X11 +#include <private/qt_x11_p.h> +#endif +#if defined(Q_WS_MAC) && !defined(QT_NO_EFFECTS) && !defined(QT_NO_STYLE_MAC) +#include <private/qcore_mac_p.h> +#include <QMacStyle> +#include <private/qt_cocoa_helpers_mac_p.h> +#endif +#ifndef QT_NO_EFFECTS +# include <private/qeffects_p.h> +#endif +QT_BEGIN_NAMESPACE + +extern QHash<QByteArray, QFont> *qt_app_fonts_hash(); + +QComboBoxPrivate::QComboBoxPrivate() + : QWidgetPrivate(), + model(0), + lineEdit(0), + container(0), + insertPolicy(QComboBox::InsertAtBottom), + sizeAdjustPolicy(QComboBox::AdjustToContentsOnFirstShow), + minimumContentsLength(0), + shownOnce(false), + autoCompletion(true), + duplicatesEnabled(false), + frame(true), + maxVisibleItems(10), + maxCount(INT_MAX), + modelColumn(0), + inserting(false), + arrowState(QStyle::State_None), + hoverControl(QStyle::SC_None), + autoCompletionCaseSensitivity(Qt::CaseInsensitive), + indexBeforeChange(-1) +#ifndef QT_NO_COMPLETER + , completer(0) +#endif +{ +} + +QStyleOptionMenuItem QComboMenuDelegate::getStyleOption(const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + QStyleOptionMenuItem menuOption; + menuOption.palette = QComboBoxPrivate::viewContainerPalette(mCombo).resolve(QApplication::palette("QMenu")); + menuOption.state = QStyle::State_None; + if (mCombo->window()->isActiveWindow()) + menuOption.state = QStyle::State_Active; + if ((option.state & QStyle::State_Enabled) && (index.model()->flags(index) & Qt::ItemIsEnabled)) + menuOption.state |= QStyle::State_Enabled; + else + menuOption.palette.setCurrentColorGroup(QPalette::Disabled); + if (option.state & QStyle::State_Selected) + menuOption.state |= QStyle::State_Selected; + menuOption.checkType = QStyleOptionMenuItem::NonExclusive; + menuOption.checked = mCombo->currentIndex() == index.row(); + if (QComboBoxDelegate::isSeparator(index)) + menuOption.menuItemType = QStyleOptionMenuItem::Separator; + else + menuOption.menuItemType = QStyleOptionMenuItem::Normal; + + QVariant variant = index.model()->data(index, Qt::DecorationRole); + switch (variant.type()) { + case QVariant::Icon: + menuOption.icon = qvariant_cast<QIcon>(variant); + break; + case QVariant::Color: { + static QPixmap pixmap(option.decorationSize); + pixmap.fill(qvariant_cast<QColor>(variant)); + menuOption.icon = pixmap; + break; } + default: + menuOption.icon = qvariant_cast<QPixmap>(variant); + break; + } + + menuOption.text = index.model()->data(index, Qt::DisplayRole).toString() + .replace(QLatin1Char('&'), QLatin1String("&&")); + menuOption.tabWidth = 0; + menuOption.maxIconWidth = option.decorationSize.width() + 4; + menuOption.menuRect = option.rect; + menuOption.rect = option.rect; + + // Make sure fonts set on the combo box also overrides the font for the popup menu. + if (mCombo->testAttribute(Qt::WA_SetFont) || mCombo->testAttribute(Qt::WA_MacSmallSize) + || mCombo->testAttribute(Qt::WA_MacMiniSize)) + menuOption.font = mCombo->font(); + else + menuOption.font = qt_app_fonts_hash()->value("QComboMenuItem", mCombo->font()); + + menuOption.fontMetrics = QFontMetrics(menuOption.font); + + return menuOption; +} + +#ifdef QT_KEYPAD_NAVIGATION +void QComboBoxPrivate::_q_completerActivated() +{ + Q_Q(QComboBox); + if ( QApplication::keypadNavigationEnabled() + && q->isEditable() + && q->completer() + && q->completer()->completionMode() == QCompleter::UnfilteredPopupCompletion ) { + q->setEditFocus(false); + } +} +#endif + +void QComboBoxPrivate::updateArrow(QStyle::StateFlag state) +{ + Q_Q(QComboBox); + if (arrowState == state) + return; + arrowState = state; + QStyleOptionComboBox opt; + q->initStyleOption(&opt); + q->update(q->style()->subControlRect(QStyle::CC_ComboBox, &opt, QStyle::SC_ComboBoxArrow, q)); +} + +void QComboBoxPrivate::_q_modelReset() +{ + Q_Q(QComboBox); + if (lineEdit) { + lineEdit->setText(QString()); + updateLineEditGeometry(); + } + q->update(); +} + +void QComboBoxPrivate::_q_modelDestroyed() +{ + model = QAbstractItemModelPrivate::staticEmptyModel(); +} + + +//Windows and KDE allows menus to cover the taskbar, while GNOME and Mac don't +QRect QComboBoxPrivate::popupGeometry(int screen) const +{ +#ifdef Q_WS_WIN + return QApplication::desktop()->screenGeometry(screen); +#elif defined Q_WS_X11 + if (X11->desktopEnvironment == DE_KDE) + return QApplication::desktop()->screenGeometry(screen); + else + return QApplication::desktop()->availableGeometry(screen); +#else + return QApplication::desktop()->availableGeometry(screen); +#endif +} + +bool QComboBoxPrivate::updateHoverControl(const QPoint &pos) +{ + + Q_Q(QComboBox); + QRect lastHoverRect = hoverRect; + QStyle::SubControl lastHoverControl = hoverControl; + bool doesHover = q->testAttribute(Qt::WA_Hover); + if (lastHoverControl != newHoverControl(pos) && doesHover) { + q->update(lastHoverRect); + q->update(hoverRect); + return true; + } + return !doesHover; +} + +QStyle::SubControl QComboBoxPrivate::newHoverControl(const QPoint &pos) +{ + Q_Q(QComboBox); + QStyleOptionComboBox opt; + q->initStyleOption(&opt); + opt.subControls = QStyle::SC_All; + hoverControl = q->style()->hitTestComplexControl(QStyle::CC_ComboBox, &opt, pos, q); + hoverRect = (hoverControl != QStyle::SC_None) + ? q->style()->subControlRect(QStyle::CC_ComboBox, &opt, hoverControl, q) + : QRect(); + return hoverControl; +} + +/* + Computes a size hint based on the maximum width + for the items in the combobox. +*/ +int QComboBoxPrivate::computeWidthHint() const +{ + Q_Q(const QComboBox); + + int width = 0; + const int count = q->count(); + const int iconWidth = q->iconSize().width() + 4; + const QFontMetrics &fontMetrics = q->fontMetrics(); + + for (int i = 0; i < count; ++i) { + const int textWidth = fontMetrics.width(q->itemText(i)); + if (q->itemIcon(i).isNull()) + width = (qMax(width, textWidth)); + else + width = (qMax(width, textWidth + iconWidth)); + } + + QStyleOptionComboBox opt; + q->initStyleOption(&opt); + QSize tmp(width, 0); + tmp = q->style()->sizeFromContents(QStyle::CT_ComboBox, &opt, tmp, q); + return tmp.width(); +} + +QSize QComboBoxPrivate::recomputeSizeHint(QSize &sh) const +{ + Q_Q(const QComboBox); + if (!sh.isValid()) { + bool hasIcon = sizeAdjustPolicy == QComboBox::AdjustToMinimumContentsLengthWithIcon ? true : false; + int count = q->count(); + QSize iconSize = q->iconSize(); + const QFontMetrics &fm = q->fontMetrics(); + + // text width + if (&sh == &sizeHint || minimumContentsLength == 0) { + switch (sizeAdjustPolicy) { + case QComboBox::AdjustToContents: + case QComboBox::AdjustToContentsOnFirstShow: + if (count == 0) { + sh.rwidth() = 7 * fm.width(QLatin1Char('x')); + } else { + for (int i = 0; i < count; ++i) { + if (!q->itemIcon(i).isNull()) { + hasIcon = true; + sh.setWidth(qMax(sh.width(), fm.boundingRect(q->itemText(i)).width() + iconSize.width() + 4)); + } else { + sh.setWidth(qMax(sh.width(), fm.boundingRect(q->itemText(i)).width())); + } + } + } + break; + case QComboBox::AdjustToMinimumContentsLength: + for (int i = 0; i < count && !hasIcon; ++i) + hasIcon = !q->itemIcon(i).isNull(); + default: + ; + } + } else { + for (int i = 0; i < count && !hasIcon; ++i) + hasIcon = !q->itemIcon(i).isNull(); + } + if (minimumContentsLength > 0) + sh.setWidth(qMax(sh.width(), minimumContentsLength * fm.width(QLatin1Char('X')) + (hasIcon ? iconSize.width() + 4 : 0))); + + + // height + sh.setHeight(qMax(fm.lineSpacing(), 14) + 2); + if (hasIcon) { + sh.setHeight(qMax(sh.height(), iconSize.height() + 2)); + } + + // add style and strut values + QStyleOptionComboBox opt; + q->initStyleOption(&opt); + sh = q->style()->sizeFromContents(QStyle::CT_ComboBox, &opt, sh, q); + } + return sh.expandedTo(QApplication::globalStrut()); +} + +void QComboBoxPrivate::adjustComboBoxSize() +{ + viewContainer()->adjustSizeTimer.start(20, container); +} + +void QComboBoxPrivate::updateLayoutDirection() +{ + Q_Q(const QComboBox); + QStyleOptionComboBox opt; + q->initStyleOption(&opt); + Qt::LayoutDirection dir = Qt::LayoutDirection( + q->style()->styleHint(QStyle::SH_ComboBox_LayoutDirection, &opt, q)); + if (lineEdit) + lineEdit->setLayoutDirection(dir); + if (container) + container->setLayoutDirection(dir); +} + + +void QComboBoxPrivateContainer::timerEvent(QTimerEvent *timerEvent) +{ + if (timerEvent->timerId() == adjustSizeTimer.timerId()) { + adjustSizeTimer.stop(); + if (combo->sizeAdjustPolicy() == QComboBox::AdjustToContents) { + combo->adjustSize(); + combo->update(); + } + } +} + +void QComboBoxPrivateContainer::resizeEvent(QResizeEvent *e) +{ + QStyleOptionComboBox opt = comboStyleOption(); + if (combo->style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, combo)) { + QStyleOption myOpt; + myOpt.initFrom(this); + QStyleHintReturnMask mask; + if (combo->style()->styleHint(QStyle::SH_Menu_Mask, &myOpt, this, &mask)) { + setMask(mask.region); + } + } else { + clearMask(); + } + QFrame::resizeEvent(e); +} + +void QComboBoxPrivateContainer::leaveEvent(QEvent *) +{ +// On Mac using the Mac style we want to clear the selection +// when the mouse moves outside the popup. +#ifdef Q_WS_MAC + QStyleOptionComboBox opt = comboStyleOption(); + if (combo->style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, combo)) + view->clearSelection(); +#endif +} + +QComboBoxPrivateContainer::QComboBoxPrivateContainer(QAbstractItemView *itemView, QComboBox *parent) + : QFrame(parent, Qt::Popup), combo(parent), view(0), top(0), bottom(0) +{ + // we need the combobox and itemview + Q_ASSERT(parent); + Q_ASSERT(itemView); + + setAttribute(Qt::WA_WindowPropagation); + setAttribute(Qt::WA_X11NetWmWindowTypeCombo); + + // setup container + blockMouseReleaseTimer.setSingleShot(true); + + // we need a vertical layout + QBoxLayout *layout = new QBoxLayout(QBoxLayout::TopToBottom, this); + layout->setSpacing(0); + layout->setMargin(0); + + // set item view + setItemView(itemView); + + // add scroller arrows if style needs them + QStyleOptionComboBox opt = comboStyleOption(); + const bool usePopup = combo->style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, combo); + if (usePopup) { + top = new QComboBoxPrivateScroller(QAbstractSlider::SliderSingleStepSub, this); + bottom = new QComboBoxPrivateScroller(QAbstractSlider::SliderSingleStepAdd, this); + top->hide(); + bottom->hide(); + } else { + setLineWidth(1); + } + + setFrameStyle(combo->style()->styleHint(QStyle::SH_ComboBox_PopupFrameStyle, &opt, combo)); + + if (top) { + layout->insertWidget(0, top); + connect(top, SIGNAL(doScroll(int)), this, SLOT(scrollItemView(int))); + } + if (bottom) { + layout->addWidget(bottom); + connect(bottom, SIGNAL(doScroll(int)), this, SLOT(scrollItemView(int))); + } + + // Some styles (Mac) have a margin at the top and bottom of the popup. + layout->insertSpacing(0, 0); + layout->addSpacing(0); + updateTopBottomMargin(); +} + +void QComboBoxPrivateContainer::scrollItemView(int action) +{ +#ifndef QT_NO_SCROLLBAR + if (view->verticalScrollBar()) + view->verticalScrollBar()->triggerAction(static_cast<QAbstractSlider::SliderAction>(action)); +#endif +} + +/* + Hides or shows the scrollers when we emulate a popupmenu +*/ +void QComboBoxPrivateContainer::updateScrollers() +{ +#ifndef QT_NO_SCROLLBAR + if (!top || !bottom) + return; + + if (isVisible() == false) + return; + + QStyleOptionComboBox opt = comboStyleOption(); + if (combo->style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, combo) && + view->verticalScrollBar()->minimum() < view->verticalScrollBar()->maximum()) { + + bool needTop = view->verticalScrollBar()->value() + > (view->verticalScrollBar()->minimum() + spacing()); + bool needBottom = view->verticalScrollBar()->value() + < (view->verticalScrollBar()->maximum() - spacing()*2); + if (needTop) + top->show(); + else + top->hide(); + if (needBottom) + bottom->show(); + else + bottom->hide(); + } else { + top->hide(); + bottom->hide(); + } +#endif // QT_NO_SCROLLBAR +} + +/* + Cleans up when the view is destroyed. +*/ +void QComboBoxPrivateContainer::viewDestroyed() +{ + view = 0; + setItemView(new QComboBoxListView()); +} + +/* + Sets currentIndex on entered if the LeftButton is not pressed. This + means that if mouseTracking(...) is on, we setCurrentIndex and select + even when LeftButton is not pressed. +*/ +void QComboBoxPrivateContainer::setCurrentIndex(const QModelIndex &index) +{ + if (QComboBoxDelegate::isSeparator(index)) + return; + view->setCurrentIndex(index); +} + +/* + Returns the item view used for the combobox popup. +*/ +QAbstractItemView *QComboBoxPrivateContainer::itemView() const +{ + return view; +} + +/*! + Sets the item view to be used for the combobox popup. +*/ +void QComboBoxPrivateContainer::setItemView(QAbstractItemView *itemView) +{ + Q_ASSERT(itemView); + + // clean up old one + if (view) { + view->removeEventFilter(this); + view->viewport()->removeEventFilter(this); +#ifndef QT_NO_SCROLLBAR + disconnect(view->verticalScrollBar(), SIGNAL(valueChanged(int)), + this, SLOT(updateScrollers())); + disconnect(view->verticalScrollBar(), SIGNAL(rangeChanged(int,int)), + this, SLOT(updateScrollers())); +#endif + disconnect(view, SIGNAL(entered(QModelIndex)), + this, SLOT(setCurrentIndex(QModelIndex))); + disconnect(view, SIGNAL(destroyed()), + this, SLOT(viewDestroyed())); + delete view; + view = 0; + } + + // setup the item view + view = itemView; + view->setParent(this); + view->setAttribute(Qt::WA_MacShowFocusRect, false); + qobject_cast<QBoxLayout*>(layout())->insertWidget(top ? 2 : 0, view); + view->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); + view->installEventFilter(this); + view->viewport()->installEventFilter(this); + view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + QStyleOptionComboBox opt = comboStyleOption(); + const bool usePopup = combo->style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, combo); +#ifndef QT_NO_SCROLLBAR + if (usePopup) + view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); +#endif + if (combo->style()->styleHint(QStyle::SH_ComboBox_ListMouseTracking, &opt, combo) || + usePopup) { + view->setMouseTracking(true); + } + view->setSelectionMode(QAbstractItemView::SingleSelection); + view->setFrameStyle(QFrame::NoFrame); + view->setLineWidth(0); + view->setEditTriggers(QAbstractItemView::NoEditTriggers); +#ifndef QT_NO_SCROLLBAR + connect(view->verticalScrollBar(), SIGNAL(valueChanged(int)), + this, SLOT(updateScrollers())); + connect(view->verticalScrollBar(), SIGNAL(rangeChanged(int,int)), + this, SLOT(updateScrollers())); +#endif + connect(view, SIGNAL(entered(QModelIndex)), + this, SLOT(setCurrentIndex(QModelIndex))); + connect(view, SIGNAL(destroyed()), + this, SLOT(viewDestroyed())); +} + +/*! + Returns the spacing between the items in the view. +*/ +int QComboBoxPrivateContainer::spacing() const +{ + QListView *lview = qobject_cast<QListView*>(view); + if (lview) + return lview->spacing(); +#ifndef QT_NO_TABLEVIEW + QTableView *tview = qobject_cast<QTableView*>(view); + if (tview) + return tview->showGrid() ? 1 : 0; +#endif + return 0; +} + +void QComboBoxPrivateContainer::updateTopBottomMargin() +{ + if (!layout() || layout()->count() < 1) + return; + + QBoxLayout *boxLayout = qobject_cast<QBoxLayout *>(layout()); + if (!boxLayout) + return; + + const QStyleOptionComboBox opt = comboStyleOption(); + const bool usePopup = combo->style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, combo); + const int margin = usePopup ? combo->style()->pixelMetric(QStyle::PM_MenuVMargin, &opt, combo) : 0; + + QSpacerItem *topSpacer = boxLayout->itemAt(0)->spacerItem(); + if (topSpacer) + topSpacer->changeSize(0, margin, QSizePolicy::Minimum, QSizePolicy::Fixed); + + QSpacerItem *bottomSpacer = boxLayout->itemAt(boxLayout->count() - 1)->spacerItem(); + if (bottomSpacer && bottomSpacer != topSpacer) + bottomSpacer->changeSize(0, margin, QSizePolicy::Minimum, QSizePolicy::Fixed); + + boxLayout->invalidate(); +} + +void QComboBoxPrivateContainer::changeEvent(QEvent *e) +{ + if (e->type() == QEvent::StyleChange) { + QStyleOptionComboBox opt = comboStyleOption(); + view->setMouseTracking(combo->style()->styleHint(QStyle::SH_ComboBox_ListMouseTracking, &opt, combo) || + combo->style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, combo)); + setFrameStyle(combo->style()->styleHint(QStyle::SH_ComboBox_PopupFrameStyle, &opt, combo)); + } + QWidget::changeEvent(e); +} + + +bool QComboBoxPrivateContainer::eventFilter(QObject *o, QEvent *e) +{ + switch (e->type()) { + case QEvent::ShortcutOverride: + switch (static_cast<QKeyEvent*>(e)->key()) { + case Qt::Key_Enter: + case Qt::Key_Return: +#ifdef QT_KEYPAD_NAVIGATION + case Qt::Key_Select: +#endif + if (view->currentIndex().isValid() && (view->currentIndex().flags() & Qt::ItemIsEnabled) ) { + combo->hidePopup(); + emit itemSelected(view->currentIndex()); + } + return true; + case Qt::Key_Down: + if (!(static_cast<QKeyEvent*>(e)->modifiers() & Qt::AltModifier)) + break; + // fall through + case Qt::Key_F4: + case Qt::Key_Escape: +#ifdef QT_KEYPAD_NAVIGATION + case Qt::Key_Back: +#endif + combo->hidePopup(); + return true; + default: + break; + } + break; + case QEvent::MouseMove: { + if (isVisible()) { + QMouseEvent *m = static_cast<QMouseEvent *>(e); + QWidget *widget = static_cast<QWidget *>(o); + QPoint vector = widget->mapToGlobal(m->pos()) - initialClickPosition; + if (vector.manhattanLength() > 9 && blockMouseReleaseTimer.isActive()) + blockMouseReleaseTimer.stop(); + } + break; + } + case QEvent::MouseButtonRelease: { + QMouseEvent *m = static_cast<QMouseEvent *>(e); + if (isVisible() && view->rect().contains(m->pos()) && view->currentIndex().isValid() + && !blockMouseReleaseTimer.isActive() + && (view->currentIndex().flags() & Qt::ItemIsEnabled) + && (view->currentIndex().flags() & Qt::ItemIsSelectable)) { + combo->hidePopup(); + emit itemSelected(view->currentIndex()); + return true; + } + break; + } + default: + break; + } + return QFrame::eventFilter(o, e); +} + +void QComboBoxPrivateContainer::showEvent(QShowEvent *) +{ + combo->update(); +} + +void QComboBoxPrivateContainer::hideEvent(QHideEvent *) +{ + emit resetButton(); + combo->update(); +} + +void QComboBoxPrivateContainer::mousePressEvent(QMouseEvent *e) +{ + + QStyleOptionComboBox opt = comboStyleOption(); + opt.subControls = QStyle::SC_All; + opt.activeSubControls = QStyle::SC_ComboBoxArrow; + QStyle::SubControl sc = combo->style()->hitTestComplexControl(QStyle::CC_ComboBox, &opt, + combo->mapFromGlobal(e->globalPos()), + combo); + if ((combo->isEditable() && sc == QStyle::SC_ComboBoxArrow) + || (!combo->isEditable() && sc != QStyle::SC_None)) + setAttribute(Qt::WA_NoMouseReplay); + combo->hidePopup(); +} + +void QComboBoxPrivateContainer::mouseReleaseEvent(QMouseEvent *e) +{ + Q_UNUSED(e); + if (!blockMouseReleaseTimer.isActive()){ + combo->hidePopup(); + emit resetButton(); + } +} + +QStyleOptionComboBox QComboBoxPrivateContainer::comboStyleOption() const +{ + // ### This should use QComboBox's initStyleOption(), but it's protected + // perhaps, we could cheat by having the QCombo private instead? + QStyleOptionComboBox opt; + opt.initFrom(combo); + opt.subControls = QStyle::SC_All; + opt.activeSubControls = QStyle::SC_None; + opt.editable = combo->isEditable(); + return opt; +} + +/*! + \enum QComboBox::InsertPolicy + + This enum specifies what the QComboBox should do when a new string is + entered by the user. + + \value NoInsert The string will not be inserted into the combobox. + \value InsertAtTop The string will be inserted as the first item in the combobox. + \value InsertAtCurrent The current item will be \e replaced by the string. + \value InsertAtBottom The string will be inserted after the last item in the combobox. + \value InsertAfterCurrent The string is inserted after the current item in the combobox. + \value InsertBeforeCurrent The string is inserted before the current item in the combobox. + \value InsertAlphabetically The string is inserted in the alphabetic order in the combobox. + \omitvalue NoInsertion + \omitvalue AtTop + \omitvalue AtCurrent + \omitvalue AtBottom + \omitvalue AfterCurrent + \omitvalue BeforeCurrent +*/ + +/*! + \enum QComboBox::SizeAdjustPolicy + + This enum specifies how the size hint of the QComboBox should + adjust when new content is added or content changes. + + \value AdjustToContents The combobox will always adjust to the contents + \value AdjustToContentsOnFirstShow The combobox will adjust to its contents the first time it is shown. + \value AdjustToMinimumContentsLength Use AdjustToContents or AdjustToContentsOnFirstShow instead. + \value AdjustToMinimumContentsLengthWithIcon The combobox will adjust to \l minimumContentsLength plus space for an icon. For performance reasons use this policy on large models. +*/ + +/*! + \fn void QComboBox::activated(int index) + + This signal is sent when the user chooses an item in the combobox. + The item's \a index is passed. Note that this signal is sent even + when the choice is not changed. If you need to know when the + choice actually changes, use signal currentIndexChanged(). + +*/ + +/*! + \fn void QComboBox::activated(const QString &text) + + This signal is sent when the user chooses an item in the combobox. + The item's \a text is passed. Note that this signal is sent even + when the choice is not changed. If you need to know when the + choice actually changes, use signal currentIndexChanged(). + +*/ + +/*! + \fn void QComboBox::highlighted(int index) + + This signal is sent when an item in the combobox popup list is + highlighted by the user. The item's \a index is passed. +*/ + +/*! + \fn void QComboBox::highlighted(const QString &text) + + This signal is sent when an item in the combobox popup list is + highlighted by the user. The item's \a text is passed. +*/ + +/*! + \fn void QComboBox::currentIndexChanged(int index) + \since 4.1 + + This signal is sent whenever the currentIndex in the combobox + changes either through user interaction or programmatically. The + item's \a index is passed or -1 if the combobox becomes empty or the + currentIndex was reset. +*/ + +/*! + \fn void QComboBox::currentIndexChanged(const QString &text) + \since 4.1 + + This signal is sent whenever the currentIndex in the combobox + changes either through user interaction or programmatically. The + item's \a text is passed. +*/ + +/*! + Constructs a combobox with the given \a parent, using the default + model QStandardItemModel. +*/ +QComboBox::QComboBox(QWidget *parent) + : QWidget(*new QComboBoxPrivate(), parent, 0) +{ + Q_D(QComboBox); + d->init(); +} + +/*! + \internal +*/ +QComboBox::QComboBox(QComboBoxPrivate &dd, QWidget *parent) + : QWidget(dd, parent, 0) +{ + Q_D(QComboBox); + d->init(); +} + +#ifdef QT3_SUPPORT +/*! + Use one of the constructors that doesn't take the \a name + argument and then use setObjectName() instead. +*/ +QComboBox::QComboBox(QWidget *parent, const char *name) + : QWidget(*new QComboBoxPrivate(), parent, 0) +{ + Q_D(QComboBox); + d->init(); + setObjectName(QString::fromAscii(name)); +} + +/*! + Use one of the constructors that doesn't take the \a name + argument and then use setObjectName() instead. +*/ +QComboBox::QComboBox(bool rw, QWidget *parent, const char *name) + : QWidget(*new QComboBoxPrivate(), parent, 0) +{ + Q_D(QComboBox); + d->init(); + setEditable(rw); + setObjectName(QString::fromAscii(name)); +} + +#endif //QT3_SUPPORT + +/*! + \class QComboBox + \brief The QComboBox widget is a combined button and popup list. + + \ingroup basicwidgets + \mainclass + + A QComboBox provides a means of presenting a list of options to the user + in a way that takes up the minimum amount of screen space. + + A combobox is a selection widget that displays the current item, + and can pop up a list of selectable items. A combobox may be editable, + allowing the user to modify each item in the list. + + Comboboxes can contain pixmaps as well as strings; the + insertItem() and setItemText() functions are suitably overloaded. + For editable comboboxes, the function clearEditText() is provided, + to clear the displayed string without changing the combobox's + contents. + + There are two signals emitted if the current item of a combobox + changes, currentIndexChanged() and activated(). + currentIndexChanged() is always emitted regardless if the change + was done programmatically or by user interaction, while + activated() is only emitted when the change is caused by user + interaction. The highlighted() signal is emitted when the user + highlights an item in the combobox popup list. All three signals + exist in two versions, one with a QString argument and one with an + \c int argument. If the user selectes or highlights a pixmap, only + the \c int signals are emitted. Whenever the text of an editable + combobox is changed the editTextChanged() 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 \l AtBottom but you can change + this using setInsertPolicy(). + + It is possible to constrain the input to an editable combobox + using QValidator; see setValidator(). By default, any input is + accepted. + + A combobox can be populated using the insert functions, + insertItem() and insertItems() for example. Items can be + changed with setItemText(). 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 setCurrentIndex(). 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 + setCompleter() and whether or not the user can add duplicates + is set with setDuplicatesEnabled(). + + QComboBox uses the \l{Model/View Programming}{model/view + framework} for its popup list and to store its items. By default + a QStandardItemModel stores the items and a QListView subclass + displays the popuplist. You can access the model and view directly + (with model() and view()), but QComboBox also provides functions + to set and get item data (e.g., setItemData() and itemText()). You + can also set a new model and view (with setModel() and setView()). + For the text and icon in the combobox label, the data in the model + that has the Qt::DisplayRole and Qt::DecorationRole is used. + + \image qstyle-comboboxes.png Comboboxes in the different built-in styles. + + \sa QLineEdit, QSpinBox, QRadioButton, QButtonGroup, + {fowler}{GUI Design Handbook: Combo Box, Drop-Down List Box} +*/ + +void QComboBoxPrivate::init() +{ + Q_Q(QComboBox); + q->setFocusPolicy(Qt::WheelFocus); + q->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed, + QSizePolicy::ComboBox)); + setLayoutItemMargins(QStyle::SE_ComboBoxLayoutItem); + q->setModel(new QStandardItemModel(0, 1, q)); + q->setAttribute(Qt::WA_InputMethodEnabled); +} + +QComboBoxPrivateContainer* QComboBoxPrivate::viewContainer() +{ + if (container) + return container; + + Q_Q(QComboBox); + container = new QComboBoxPrivateContainer(new QComboBoxListView(q), q); + container->itemView()->setModel(model); + container->itemView()->setTextElideMode(Qt::ElideMiddle); + updateDelegate(); + updateLayoutDirection(); + QObject::connect(container, SIGNAL(itemSelected(QModelIndex)), + q, SLOT(_q_itemSelected(QModelIndex))); + QObject::connect(container->itemView()->selectionModel(), + SIGNAL(currentChanged(QModelIndex,QModelIndex)), + q, SLOT(_q_emitHighlighted(QModelIndex))); + QObject::connect(container, SIGNAL(resetButton()), q, SLOT(_q_resetButton())); + return container; +} + + +void QComboBoxPrivate::_q_resetButton() +{ + updateArrow(QStyle::State_None); +} + +void QComboBoxPrivate::_q_dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + Q_Q(QComboBox); + if (inserting || topLeft.parent() != root) + return; + + if (sizeAdjustPolicy == QComboBox::AdjustToContents) { + sizeHint = QSize(); + adjustComboBoxSize(); + q->updateGeometry(); + } + + if (currentIndex.row() >= topLeft.row() && currentIndex.row() <= bottomRight.row()) { + if (lineEdit) { + lineEdit->setText(q->itemText(currentIndex.row())); + updateLineEditGeometry(); + } + q->update(); + } +} + +void QComboBoxPrivate::_q_rowsAboutToBeInserted(const QModelIndex & parent, + int /*start*/, int /*end*/) +{ + if (parent != root) + return; + indexBeforeChange = currentIndex.row(); +} + +void QComboBoxPrivate::_q_rowsInserted(const QModelIndex &parent, int start, int end) +{ + Q_Q(QComboBox); + if (inserting || parent != root) + return; + + if (sizeAdjustPolicy == QComboBox::AdjustToContents) { + sizeHint = QSize(); + adjustComboBoxSize(); + q->updateGeometry(); + } + + // set current index if combo was previously empty + if (start == 0 && (end - start + 1) == q->count() && !currentIndex.isValid()) { + q->setCurrentIndex(0); + // need to emit changed if model updated index "silently" + } else if (currentIndex.row() != indexBeforeChange) { + q->update(); + _q_emitCurrentIndexChanged(currentIndex); + } +} + +void QComboBoxPrivate::_q_rowsAboutToBeRemoved(const QModelIndex &parent, int /*start*/, int /*end*/) +{ + if (parent != root) + return; + + indexBeforeChange = currentIndex.row(); +} + +void QComboBoxPrivate::_q_rowsRemoved(const QModelIndex &parent, int /*start*/, int /*end*/) +{ + Q_Q(QComboBox); + if (parent != root) + return; + + if (sizeAdjustPolicy == QComboBox::AdjustToContents) { + sizeHint = QSize(); + adjustComboBoxSize(); + q->updateGeometry(); + } + + // model has changed the currentIndex + if (currentIndex.row() != indexBeforeChange) { + if (!currentIndex.isValid() && q->count()) { + q->setCurrentIndex(qMin(q->count() - 1, qMax(indexBeforeChange, 0))); + return; + } + if (lineEdit) { + lineEdit->setText(q->itemText(currentIndex.row())); + updateLineEditGeometry(); + } + q->update(); + _q_emitCurrentIndexChanged(currentIndex); + } +} + + +/*! + Initialize \a option with the values from this QComboBox. This method + is useful for subclasses when they need a QStyleOptionComboBox, but don't want + to fill in all the information themselves. + + \sa QStyleOption::initFrom() +*/ +void QComboBox::initStyleOption(QStyleOptionComboBox *option) const +{ + if (!option) + return; + + Q_D(const QComboBox); + option->initFrom(this); + option->editable = isEditable(); + option->frame = d->frame; + if (hasFocus() && !option->editable) + option->state |= QStyle::State_Selected; + option->subControls = QStyle::SC_All; + if (d->arrowState == QStyle::State_Sunken) { + option->activeSubControls = QStyle::SC_ComboBoxArrow; + option->state |= d->arrowState; + } else { + option->activeSubControls = d->hoverControl; + } + if (d->currentIndex.isValid()) { + option->currentText = currentText(); + option->currentIcon = d->itemIcon(d->currentIndex); + } + option->iconSize = iconSize(); + if (d->container && d->container->isVisible()) + option->state |= QStyle::State_On; +} + +void QComboBoxPrivate::updateLineEditGeometry() +{ + if (!lineEdit) + return; + + Q_Q(QComboBox); + QStyleOptionComboBox opt; + q->initStyleOption(&opt); + QRect editRect = q->style()->subControlRect(QStyle::CC_ComboBox, &opt, + QStyle::SC_ComboBoxEditField, q); + if (!q->itemIcon(q->currentIndex()).isNull()) { + QRect comboRect(editRect); + editRect.setWidth(editRect.width() - q->iconSize().width() - 4); + editRect = QStyle::alignedRect(q->layoutDirection(), Qt::AlignRight, + editRect.size(), comboRect); + } + lineEdit->setGeometry(editRect); +} + +void QComboBoxPrivate::_q_returnPressed() +{ + Q_Q(QComboBox); + if (lineEdit && !lineEdit->text().isEmpty()) { + if (q->count() >= maxCount && !(this->insertPolicy == QComboBox::InsertAtCurrent)) + return; + lineEdit->deselect(); + lineEdit->end(false); + QString text = lineEdit->text(); + // check for duplicates (if not enabled) and quit + int index = -1; + if (!duplicatesEnabled) { + // Base how duplicates are determined on the autocompletion case sensitivity + Qt::MatchFlags flags = Qt::MatchFixedString; +#ifndef QT_NO_COMPLETER + if (!lineEdit->completer() || lineEdit->completer()->caseSensitivity() == Qt::CaseSensitive) +#endif + flags |= Qt::MatchCaseSensitive; + index = q->findText(text, flags); + if (index != -1) { + q->setCurrentIndex(index); + emitActivated(currentIndex); + return; + } + } + switch (insertPolicy) { + case QComboBox::InsertAtTop: + index = 0; + break; + case QComboBox::InsertAtBottom: + index = q->count(); + break; + case QComboBox::InsertAtCurrent: + case QComboBox::InsertAfterCurrent: + case QComboBox::InsertBeforeCurrent: + if (!q->count() || !currentIndex.isValid()) + index = 0; + else if (insertPolicy == QComboBox::InsertAtCurrent) + q->setItemText(q->currentIndex(), text); + else if (insertPolicy == QComboBox::InsertAfterCurrent) + index = q->currentIndex() + 1; + else if (insertPolicy == QComboBox::InsertBeforeCurrent) + index = q->currentIndex(); + break; + case QComboBox::InsertAlphabetically: + index = 0; + for (int i=0; i< q->count(); i++, index++ ) { + if (text.toLower() < q->itemText(i).toLower()) + break; + } + break; + case QComboBox::NoInsert: + default: + break; + } + if (index >= 0) { + q->insertItem(index, text); + q->setCurrentIndex(index); + emitActivated(currentIndex); + } + } +} + +void QComboBoxPrivate::_q_itemSelected(const QModelIndex &item) +{ + Q_Q(QComboBox); + if (item != currentIndex) { + setCurrentIndex(item); + } else if (lineEdit) { + lineEdit->selectAll(); + lineEdit->setText(q->itemText(currentIndex.row())); + } + emitActivated(currentIndex); +} + +void QComboBoxPrivate::emitActivated(const QModelIndex &index) +{ + Q_Q(QComboBox); + if (!index.isValid()) + return; + QString text(itemText(index)); + emit q->activated(index.row()); + emit q->activated(text); +} + +void QComboBoxPrivate::_q_emitHighlighted(const QModelIndex &index) +{ + Q_Q(QComboBox); + if (!index.isValid()) + return; + QString text(itemText(index)); + emit q->highlighted(index.row()); + emit q->highlighted(text); +} + +void QComboBoxPrivate::_q_emitCurrentIndexChanged(const QModelIndex &index) +{ + Q_Q(QComboBox); + emit q->currentIndexChanged(index.row()); + emit q->currentIndexChanged(itemText(index)); +} + +QString QComboBoxPrivate::itemText(const QModelIndex &index) const +{ + return index.isValid() ? model->data(index, itemRole()).toString() : QString(); +} + +int QComboBoxPrivate::itemRole() const +{ + return q_func()->isEditable() ? Qt::EditRole : Qt::DisplayRole; +} + +/*! + Destroys the combobox. +*/ +QComboBox::~QComboBox() +{ + // ### check delegateparent and delete delegate if us? + Q_D(QComboBox); + + disconnect(d->model, SIGNAL(destroyed()), + this, SLOT(_q_modelDestroyed())); +} + +/*! + \property QComboBox::maxVisibleItems + \brief the maximum allowed size on screen of the combo box, measured in items + + By default, this property has a value of 10. + + \note This property is ignored for non-editable comboboxes in Mac style. +*/ +int QComboBox::maxVisibleItems() const +{ + Q_D(const QComboBox); + return d->maxVisibleItems; +} + +void QComboBox::setMaxVisibleItems(int maxItems) +{ + Q_D(QComboBox); + if (maxItems < 0) { + qWarning("QComboBox::setMaxVisibleItems: " + "Invalid max visible items (%d) must be >= 0", maxItems); + return; + } + d->maxVisibleItems = maxItems; +} + +/*! + \property QComboBox::count + \brief the number of items in the combobox + + By default, for an empty combo box, this property has a value of 0. +*/ +int QComboBox::count() const +{ + Q_D(const QComboBox); + return d->model->rowCount(d->root); +} + +/*! + \property QComboBox::maxCount + \brief the maximum number of items allowed in the combobox + + \note If you set the maximum number to be less then the current + amount of items in the combobox, the extra items will be + truncated. This also applies if you have set an external model on + the combobox. + + By default, this property's value is derived from the highest + signed integer available (typically 2147483647). +*/ +void QComboBox::setMaxCount(int max) +{ + Q_D(QComboBox); + if (max < 0) { + qWarning("QComboBox::setMaxCount: Invalid count (%d) must be >= 0", max); + return; + } + + if (max < count()) + d->model->removeRows(max, count() - max, d->root); + + d->maxCount = max; +} + +int QComboBox::maxCount() const +{ + Q_D(const QComboBox); + return d->maxCount; +} + +#ifndef QT_NO_COMPLETER + +/*! + \property QComboBox::autoCompletion + \brief whether the combobox provides auto-completion for editable items + \since 4.1 + \obsolete + + Use setCompleter() instead. + + By default, this property is true. + + \sa editable +*/ + +/*! + \obsolete + + Use setCompleter() instead. +*/ +bool QComboBox::autoCompletion() const +{ + Q_D(const QComboBox); + return d->autoCompletion; +} + +/*! + \obsolete + + Use setCompleter() instead. +*/ +void QComboBox::setAutoCompletion(bool enable) +{ + Q_D(QComboBox); + +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled() && !enable && isEditable()) + qWarning("QComboBox::setAutoCompletion: auto completion is mandatory when combo box editable"); +#endif + + d->autoCompletion = enable; + if (!d->lineEdit) + return; + if (enable) { + if (d->lineEdit->completer()) + return; + d->completer = new QCompleter(d->model, d->lineEdit); + d->completer->setCaseSensitivity(d->autoCompletionCaseSensitivity); + d->completer->setCompletionMode(QCompleter::InlineCompletion); + d->completer->setCompletionColumn(d->modelColumn); + d->lineEdit->setCompleter(d->completer); + d->completer->setWidget(this); + } else { + d->lineEdit->setCompleter(0); + } +} + +/*! + \property QComboBox::autoCompletionCaseSensitivity + \brief whether string comparisons are case-sensitive or case-insensitive for auto-completion + \obsolete + + By default, this property is Qt::CaseInsensitive. + + Use setCompleter() instead. Case sensitivity of the auto completion can be + changed using QCompleter::setCaseSensitivity(). + + \sa autoCompletion +*/ + +/*! + \obsolete + + Use setCompleter() and QCompleter::setCaseSensitivity() instead. +*/ +Qt::CaseSensitivity QComboBox::autoCompletionCaseSensitivity() const +{ + Q_D(const QComboBox); + return d->autoCompletionCaseSensitivity; +} + +/*! + \obsolete + + Use setCompleter() and QCompleter::setCaseSensitivity() instead. +*/ +void QComboBox::setAutoCompletionCaseSensitivity(Qt::CaseSensitivity sensitivity) +{ + Q_D(QComboBox); + d->autoCompletionCaseSensitivity = sensitivity; + if (d->lineEdit && d->lineEdit->completer()) + d->lineEdit->completer()->setCaseSensitivity(sensitivity); +} + +#endif // QT_NO_COMPLETER + +/*! + \property QComboBox::duplicatesEnabled + \brief whether the user can enter duplicate items into the combobox + + Note that it is always possible to programmatically insert duplicate items into the + combobox. + + By default, this property is false (duplicates are not allowed). +*/ +bool QComboBox::duplicatesEnabled() const +{ + Q_D(const QComboBox); + return d->duplicatesEnabled; +} + +void QComboBox::setDuplicatesEnabled(bool enable) +{ + Q_D(QComboBox); + d->duplicatesEnabled = enable; +} + +/*! \fn int QComboBox::findText(const QString &text, Qt::MatchFlags flags = Qt::MatchExactly|Qt::MatchCaseSensitive) const + + Returns the index of the item containing the given \a text; otherwise + returns -1. + + The \a flags specify how the items in the combobox are searched. +*/ + +/*! + Returns the index of the item containing the given \a data for the + given \a role; otherwise returns -1. + + The \a flags specify how the items in the combobox are searched. +*/ +int QComboBox::findData(const QVariant &data, int role, Qt::MatchFlags flags) const +{ + Q_D(const QComboBox); + QModelIndexList result; + QModelIndex start = d->model->index(0, d->modelColumn, d->root); + result = d->model->match(start, role, data, 1, flags); + if (result.isEmpty()) + return -1; + return result.first().row(); +} + +/*! + \property QComboBox::insertPolicy + \brief the policy used to determine where user-inserted items should + appear in the combobox + + The default value is \l AtBottom, indicating that new items will appear + at the bottom of the list of items. + + \sa InsertPolicy +*/ + +QComboBox::InsertPolicy QComboBox::insertPolicy() const +{ + Q_D(const QComboBox); + return d->insertPolicy; +} + +void QComboBox::setInsertPolicy(InsertPolicy policy) +{ + Q_D(QComboBox); + d->insertPolicy = policy; +} + +/*! + \property QComboBox::sizeAdjustPolicy + \brief the policy describing how the size of the combobox changes + when the content changes + + The default value is \l AdjustToContentsOnFirstShow. + + \sa SizeAdjustPolicy +*/ + +QComboBox::SizeAdjustPolicy QComboBox::sizeAdjustPolicy() const +{ + Q_D(const QComboBox); + return d->sizeAdjustPolicy; +} + +void QComboBox::setSizeAdjustPolicy(QComboBox::SizeAdjustPolicy policy) +{ + Q_D(QComboBox); + if (policy == d->sizeAdjustPolicy) + return; + + d->sizeAdjustPolicy = policy; + d->sizeHint = QSize(); + d->adjustComboBoxSize(); + updateGeometry(); +} + +/*! + \property QComboBox::minimumContentsLength + \brief the minimum number of characters that should fit into the combobox. + + The default value is 0. + + If this property is set to a positive value, the + minimumSizeHint() and sizeHint() take it into account. + + \sa sizeAdjustPolicy +*/ +int QComboBox::minimumContentsLength() const +{ + Q_D(const QComboBox); + return d->minimumContentsLength; +} + +void QComboBox::setMinimumContentsLength(int characters) +{ + Q_D(QComboBox); + if (characters == d->minimumContentsLength || characters < 0) + return; + + d->minimumContentsLength = characters; + + if (d->sizeAdjustPolicy == AdjustToContents + || d->sizeAdjustPolicy == AdjustToMinimumContentsLength + || d->sizeAdjustPolicy == AdjustToMinimumContentsLengthWithIcon) { + d->sizeHint = QSize(); + d->adjustComboBoxSize(); + updateGeometry(); + } +} + +/*! + \property QComboBox::iconSize + \brief the size of the icons shown in the combobox. + + Unless explicitly set this returns the default value of the + current style. This size is the maximum size that icons can have; + icons of smaller size are not scaled up. +*/ + +QSize QComboBox::iconSize() const +{ + Q_D(const QComboBox); + if (d->iconSize.isValid()) + return d->iconSize; + + int iconWidth = style()->pixelMetric(QStyle::PM_SmallIconSize, 0, this); + return QSize(iconWidth, iconWidth); +} + +void QComboBox::setIconSize(const QSize &size) +{ + Q_D(QComboBox); + if (size == d->iconSize) + return; + + view()->setIconSize(size); + d->iconSize = size; + d->sizeHint = QSize(); + updateGeometry(); +} + +/*! + \property QComboBox::editable + \brief whether the combo box can be edited by the user + + By default, this property is false. +*/ +bool QComboBox::isEditable() const +{ + Q_D(const QComboBox); + return d->lineEdit != 0; +} + +void QComboBoxPrivate::updateDelegate() +{ + Q_Q(QComboBox); + QStyleOptionComboBox opt; + q->initStyleOption(&opt); + if (q->style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, q)) + q->setItemDelegate(new QComboMenuDelegate(q->view(), q)); + else + q->setItemDelegate(new QComboBoxDelegate(q->view(), q)); +} + +QIcon QComboBoxPrivate::itemIcon(const QModelIndex &index) const +{ + QVariant decoration = model->data(index, Qt::DecorationRole); + if (decoration.type() == QVariant::Pixmap) + return QIcon(qvariant_cast<QPixmap>(decoration)); + else + return qvariant_cast<QIcon>(decoration); +} + +void QComboBox::setEditable(bool editable) +{ + Q_D(QComboBox); + if (isEditable() == editable) + return; + + d->updateDelegate(); + + QStyleOptionComboBox opt; + initStyleOption(&opt); + if (editable) { + if (style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, this)) { + d->viewContainer()->updateScrollers(); + view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + } + QLineEdit *le = new QLineEdit(this); + setLineEdit(le); + } else { + if (style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, this)) { + d->viewContainer()->updateScrollers(); + view()->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + } + setAttribute(Qt::WA_InputMethodEnabled, false); + d->lineEdit->hide(); + d->lineEdit->deleteLater(); + d->lineEdit = 0; + } + + d->viewContainer()->updateTopBottomMargin(); + if (!testAttribute(Qt::WA_Resized)) + adjustSize(); +} + +/*! + Sets the line \a edit to use instead of the current line edit widget. + + The combo box takes ownership of the line edit. +*/ +void QComboBox::setLineEdit(QLineEdit *edit) +{ + Q_D(QComboBox); + if (!edit) { + qWarning("QComboBox::setLineEdit: cannot set a 0 line edit"); + return; + } + + if (edit == d->lineEdit) + return; + + edit->setText(currentText()); + delete d->lineEdit; + + d->lineEdit = edit; + if (d->lineEdit->parent() != this) + d->lineEdit->setParent(this); + connect(d->lineEdit, SIGNAL(returnPressed()), this, SLOT(_q_returnPressed())); + connect(d->lineEdit, SIGNAL(textChanged(QString)), this, SIGNAL(editTextChanged(QString))); +#ifdef QT3_SUPPORT + connect(d->lineEdit, SIGNAL(textChanged(QString)), this, SIGNAL(textChanged(QString))); +#endif + d->lineEdit->setFrame(false); + d->lineEdit->setContextMenuPolicy(Qt::NoContextMenu); + d->lineEdit->setFocusProxy(this); + d->lineEdit->setAttribute(Qt::WA_MacShowFocusRect, false); +#ifndef QT_NO_COMPLETER + setAutoCompletion(d->autoCompletion); +#endif + +#ifdef QT_KEYPAD_NAVIGATION +#ifndef QT_NO_COMPLETER + if (QApplication::keypadNavigationEnabled()) { + // Editable combo boxes will have a completer that is set to UnfilteredPopupCompletion. + // This means that when the user enters edit mode they are immediately presented with a + // list of possible completions. + setAutoCompletion(true); + if (d->completer) { + d->completer->setCompletionMode(QCompleter::UnfilteredPopupCompletion); + connect(d->completer, SIGNAL(activated(QModelIndex)), this, SLOT(_q_completerActivated())); + } + } +#endif +#endif + + setAttribute(Qt::WA_InputMethodEnabled); + d->updateLayoutDirection(); + d->updateLineEditGeometry(); + if (isVisible()) + d->lineEdit->show(); + + update(); +} + +/*! + Returns the line edit used to edit items in the combobox, or 0 if there + is no line edit. + + Only editable combo boxes have a line edit. +*/ +QLineEdit *QComboBox::lineEdit() const +{ + Q_D(const QComboBox); + return d->lineEdit; +} + +#ifndef QT_NO_VALIDATOR +/*! + \fn void QComboBox::setValidator(const QValidator *validator) + + Sets the \a validator to use instead of the current validator. +*/ + +void QComboBox::setValidator(const QValidator *v) +{ + Q_D(QComboBox); + if (d->lineEdit) + d->lineEdit->setValidator(v); +} + +/*! + Returns the validator that is used to constrain text input for the + combobox. + + \sa editable +*/ +const QValidator *QComboBox::validator() const +{ + Q_D(const QComboBox); + return d->lineEdit ? d->lineEdit->validator() : 0; +} +#endif // QT_NO_VALIDATOR + +#ifndef QT_NO_COMPLETER + +/*! + \fn void QComboBox::setCompleter(QCompleter *completer) + \since 4.2 + + Sets the \a completer to use instead of the current completer. + If \a completer is 0, auto completion is disabled. + + By default, for an editable combo box, a QCompleter that + performs case insensitive inline completion is automatically created. +*/ +void QComboBox::setCompleter(QCompleter *c) +{ + Q_D(QComboBox); + if (!d->lineEdit) + return; + d->lineEdit->setCompleter(c); + if (c) + c->setWidget(this); +} + +/*! + \since 4.2 + + Returns the completer that is used to auto complete text input for the + combobox. + + \sa editable +*/ +QCompleter *QComboBox::completer() const +{ + Q_D(const QComboBox); + return d->lineEdit ? d->lineEdit->completer() : 0; +} + +#endif // QT_NO_COMPLETER + +/*! + Returns the item delegate used by the popup list view. + + \sa setItemDelegate() +*/ +QAbstractItemDelegate *QComboBox::itemDelegate() const +{ + return view()->itemDelegate(); +} + +/*! + Sets the item \a delegate for the popup list view. + The combobox takes ownership of the delegate. + + \warning You should not share the same instance of a delegate between comboboxes, + widget mappers or views. Doing so can cause incorrect or unintuitive editing behavior + since each view connected to a given delegate may receive the + \l{QAbstractItemDelegate::}{closeEditor()} signal, and attempt to access, modify or + close an editor that has already been closed. + + \sa itemDelegate() +*/ +void QComboBox::setItemDelegate(QAbstractItemDelegate *delegate) +{ + if (!delegate) { + qWarning("QComboBox::setItemDelegate: cannot set a 0 delegate"); + return; + } + delete view()->itemDelegate(); + view()->setItemDelegate(delegate); +} + +/*! + Returns the model used by the combobox. +*/ + +QAbstractItemModel *QComboBox::model() const +{ + Q_D(const QComboBox); + if (d->model == QAbstractItemModelPrivate::staticEmptyModel()) { + QComboBox *that = const_cast<QComboBox*>(this); + that->setModel(new QStandardItemModel(0, 1, that)); + } + return d->model; +} + +/*! + Sets the model to be \a model. \a model must not be 0. + If you want to clear the contents of a model, call clear(). + + \sa clear() +*/ +void QComboBox::setModel(QAbstractItemModel *model) +{ + Q_D(QComboBox); + + if (!model) { + qWarning("QComboBox::setModel: cannot set a 0 model"); + return; + } + +#ifndef QT_NO_COMPLETER + if (d->lineEdit && d->lineEdit->completer() + && d->lineEdit->completer() == d->completer) + d->lineEdit->completer()->setModel(model); +#endif + if (d->model) { + disconnect(d->model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(_q_dataChanged(QModelIndex,QModelIndex))); + disconnect(d->model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(_q_rowsAboutToBeInserted(QModelIndex,int,int))); + disconnect(d->model, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(_q_rowsInserted(QModelIndex,int,int))); + disconnect(d->model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(_q_rowsAboutToBeRemoved(QModelIndex,int,int))); + disconnect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(_q_rowsRemoved(QModelIndex,int,int))); + disconnect(d->model, SIGNAL(destroyed()), + this, SLOT(_q_modelDestroyed())); + disconnect(d->model, SIGNAL(modelReset()), + this, SLOT(_q_modelReset())); + if (d->model->QObject::parent() == this) + delete d->model; + } + + d->model = model; + + connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(_q_dataChanged(QModelIndex,QModelIndex))); + connect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(_q_rowsAboutToBeInserted(QModelIndex,int,int))); + connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(_q_rowsInserted(QModelIndex,int,int))); + connect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(_q_rowsAboutToBeRemoved(QModelIndex,int,int))); + connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(_q_rowsRemoved(QModelIndex,int,int))); + connect(model, SIGNAL(destroyed()), + this, SLOT(_q_modelDestroyed())); + connect(model, SIGNAL(modelReset()), + this, SLOT(_q_modelReset())); + + if (d->container) + d->container->itemView()->setModel(model); + + bool currentReset = false; + + if (count()) { + for (int pos=0; pos < count(); pos++) { + if (d->model->index(pos, d->modelColumn, d->root).flags() & Qt::ItemIsEnabled) { + setCurrentIndex(pos); + currentReset = true; + break; + } + } + } + + if (!currentReset) + setCurrentIndex(-1); + + d->modelChanged(); +} + +/*! + Returns the root model item index for the items in the combobox. + + \sa setRootModelIndex() +*/ + +QModelIndex QComboBox::rootModelIndex() const +{ + Q_D(const QComboBox); + return QModelIndex(d->root); +} + +/*! + Sets the root model item \a index for the items in the combobox. + + \sa rootModelIndex() +*/ +void QComboBox::setRootModelIndex(const QModelIndex &index) +{ + Q_D(QComboBox); + d->root = QPersistentModelIndex(index); + view()->setRootIndex(index); + update(); +} + +/*! + \property QComboBox::currentIndex + \brief the index of the current item in the combobox. + + The current index can change when inserting or removing items. + + By default, for an empty combo box or a combo box in which no current + item is set, this property has a value of -1. +*/ +int QComboBox::currentIndex() const +{ + Q_D(const QComboBox); + return d->currentIndex.row(); +} + +void QComboBox::setCurrentIndex(int index) +{ + Q_D(QComboBox); + QModelIndex mi = d->model->index(index, d->modelColumn, d->root); + d->setCurrentIndex(mi); +} + +void QComboBoxPrivate::setCurrentIndex(const QModelIndex &mi) +{ + Q_Q(QComboBox); + bool indexChanged = (mi != currentIndex); + if (indexChanged) + currentIndex = QPersistentModelIndex(mi); + if (lineEdit) { + QString newText = q->itemText(currentIndex.row()); + if (lineEdit->text() != newText) + lineEdit->setText(q->itemText(currentIndex.row())); + updateLineEditGeometry(); + } + if (indexChanged) { + q->update(); + _q_emitCurrentIndexChanged(currentIndex); + } +} + +/*! + \property QComboBox::currentText + \brief the text of the current item + + By default, for an empty combo box or a combo box in which no current + item is set, this property contains an empty string. +*/ +QString QComboBox::currentText() const +{ + Q_D(const QComboBox); + if (d->lineEdit) + return d->lineEdit->text(); + else if (d->currentIndex.isValid()) + return d->itemText(d->currentIndex); + else + return QString(); +} + +/*! + Returns the text for the given \a index in the combobox. +*/ +QString QComboBox::itemText(int index) const +{ + Q_D(const QComboBox); + QModelIndex mi = d->model->index(index, d->modelColumn, d->root); + return d->itemText(mi); +} + +/*! + Returns the icon for the given \a index in the combobox. +*/ +QIcon QComboBox::itemIcon(int index) const +{ + Q_D(const QComboBox); + QModelIndex mi = d->model->index(index, d->modelColumn, d->root); + return d->itemIcon(mi); +} + +/*! + Returns the data for the given \a role in the given \a index in the + combobox, or QVariant::Invalid if there is no data for this role. +*/ +QVariant QComboBox::itemData(int index, int role) const +{ + Q_D(const QComboBox); + QModelIndex mi = d->model->index(index, d->modelColumn, d->root); + return d->model->data(mi, role); +} + +/*! + \fn void QComboBox::insertItem(int index, const QString &text, const QVariant &userData) + + Inserts the \a text and \a userData (stored in the Qt::UserRole) + into the combobox at the given \a index. + + If the index is equal to or higher than the total number of items, + the new item is appended to the list of existing items. If the + index is zero or negative, the new item is prepended to the list + of existing items. + + \sa insertItems() +*/ + +/*! + + Inserts the \a icon, \a text and \a userData (stored in the + Qt::UserRole) into the combobox at the given \a index. + + If the index is equal to or higher than the total number of items, + the new item is appended to the list of existing items. If the + index is zero or negative, the new item is prepended to the list + of existing items. + + \sa insertItems() +*/ +void QComboBox::insertItem(int index, const QIcon &icon, const QString &text, const QVariant &userData) +{ + Q_D(QComboBox); + int itemCount = count(); + index = qBound(0, index, itemCount); + if (index >= d->maxCount) + return; + + // For the common case where we are using the built in QStandardItemModel + // construct a QStandardItem, reducing the number of expensive signals from the model + if (QStandardItemModel *m = qobject_cast<QStandardItemModel*>(d->model)) { + QStandardItem *item = new QStandardItem(text); + if (!icon.isNull()) item->setData(icon, Qt::DecorationRole); + if (userData.isValid()) item->setData(userData, Qt::UserRole); + m->insertRow(index, item); + ++itemCount; + } else { + d->inserting = true; + if (d->model->insertRows(index, 1, d->root)) { + QModelIndex item = d->model->index(index, d->modelColumn, d->root); + if (icon.isNull() && !userData.isValid()) { + d->model->setData(item, text, Qt::EditRole); + } else { + QMap<int, QVariant> values; + if (!text.isNull()) values.insert(Qt::EditRole, text); + if (!icon.isNull()) values.insert(Qt::DecorationRole, icon); + if (userData.isValid()) values.insert(Qt::UserRole, userData); + if (!values.isEmpty()) d->model->setItemData(item, values); + } + d->inserting = false; + d->_q_rowsInserted(d->root, index, index); + ++itemCount; + } else { + d->inserting = false; + } + } + + if (itemCount > d->maxCount) + d->model->removeRows(itemCount - 1, itemCount - d->maxCount, d->root); +} + +/*! + Inserts the strings from the \a list into the combobox as separate items, + starting at the \a index specified. + + If the index is equal to or higher than the total number of items, the new items + are appended to the list of existing items. If the index is zero or negative, the + new items are prepended to the list of existing items. + + \sa insertItem() + */ +void QComboBox::insertItems(int index, const QStringList &list) +{ + Q_D(QComboBox); + if (list.isEmpty()) + return; + index = qBound(0, index, count()); + int insertCount = qMin(d->maxCount - index, list.count()); + if (insertCount <= 0) + return; + // For the common case where we are using the built in QStandardItemModel + // construct a QStandardItem, reducing the number of expensive signals from the model + if (QStandardItemModel *m = qobject_cast<QStandardItemModel*>(d->model)) { + QList<QStandardItem *> items; + QStandardItem *hiddenRoot = m->invisibleRootItem(); + for (int i = 0; i < insertCount; ++i) + items.append(new QStandardItem(list.at(i))); + hiddenRoot->insertRows(index, items); + } else { + d->inserting = true; + if (d->model->insertRows(index, insertCount, d->root)) { + QModelIndex item; + for (int i = 0; i < insertCount; ++i) { + item = d->model->index(i+index, d->modelColumn, d->root); + d->model->setData(item, list.at(i), Qt::EditRole); + } + d->inserting = false; + d->_q_rowsInserted(d->root, index, index + insertCount - 1); + } else { + d->inserting = false; + } + } + + int mc = count(); + if (mc > d->maxCount) + d->model->removeRows(d->maxCount, mc - d->maxCount, d->root); +} + +/*! + \since 4.4 + + Inserts a separator item into the combobox at the given \a index. + + If the index is equal to or higher than the total number of items, the new item + is appended to the list of existing items. If the index is zero or negative, the + new item is prepended to the list of existing items. + + \sa insertItem() +*/ +void QComboBox::insertSeparator(int index) +{ + Q_D(QComboBox); + int itemCount = count(); + index = qBound(0, index, itemCount); + if (index >= d->maxCount) + return; + insertItem(index, QIcon(), QString()); + QComboBoxDelegate::setSeparator(d->model, d->model->index(index, 0, d->root)); +} + +/*! + Removes the item at the given \a index from the combobox. + This will update the current index if the index is removed. +*/ +void QComboBox::removeItem(int index) +{ + Q_ASSERT(index >= 0 && index < count()); + Q_D(QComboBox); + d->model->removeRows(index, 1, d->root); +} + +/*! + Sets the \a text for the item on the given \a index in the combobox. +*/ +void QComboBox::setItemText(int index, const QString &text) +{ + Q_D(const QComboBox); + QModelIndex item = d->model->index(index, d->modelColumn, d->root); + if (item.isValid()) { + d->model->setData(item, text, Qt::EditRole); + } +} + +/*! + Sets the \a icon for the item on the given \a index in the combobox. +*/ +void QComboBox::setItemIcon(int index, const QIcon &icon) +{ + Q_D(const QComboBox); + QModelIndex item = d->model->index(index, d->modelColumn, d->root); + if (item.isValid()) { + d->model->setData(item, icon, Qt::DecorationRole); + } +} + +/*! + Sets the data \a role for the item on the given \a index in the combobox + to the specified \a value. +*/ +void QComboBox::setItemData(int index, const QVariant &value, int role) +{ + Q_D(const QComboBox); + QModelIndex item = d->model->index(index, d->modelColumn, d->root); + if (item.isValid()) { + d->model->setData(item, value, role); + } +} + +/*! + Returns the list view used for the combobox popup. +*/ +QAbstractItemView *QComboBox::view() const +{ + Q_D(const QComboBox); + return const_cast<QComboBoxPrivate*>(d)->viewContainer()->itemView(); +} + +/*! + Sets the view to be used in the combobox popup to the given \a + itemView. The combobox takes ownership of the view. + + Note: If you want to use the convenience views (like QListWidget, + QTableWidget or QTreeWidget), make sure to call setModel() on the + combobox with the convenience widgets model before calling this + function. +*/ +void QComboBox::setView(QAbstractItemView *itemView) +{ + Q_D(QComboBox); + if (!itemView) { + qWarning("QComboBox::setView: cannot set a 0 view"); + return; + } + + if (itemView->model() != d->model) + itemView->setModel(d->model); + d->viewContainer()->setItemView(itemView); +} + +/*! + \reimp +*/ +QSize QComboBox::minimumSizeHint() const +{ + Q_D(const QComboBox); + return d->recomputeSizeHint(d->minimumSizeHint); +} + +/*! + \reimp + + This implementation caches the size hint to avoid resizing when + the contents change dynamically. To invalidate the cached value + change the \l sizeAdjustPolicy. +*/ +QSize QComboBox::sizeHint() const +{ + Q_D(const QComboBox); + return d->recomputeSizeHint(d->sizeHint); +} + +/*! + Displays the list of items in the combobox. If the list is empty + then the no items will be shown. + + If you reimplement this function to show a custom pop-up, make + sure you call hidePopup() to reset the internal state. + + \sa hidePopup() +*/ +void QComboBox::showPopup() +{ + Q_D(QComboBox); + if (count() <= 0) + return; + +#ifdef QT_KEYPAD_NAVIGATION +#ifndef QT_NO_COMPLETER + if (QApplication::keypadNavigationEnabled() && d->completer) { + // editable combo box is line edit plus completer + setEditFocus(true); + d->completer->complete(); // show popup + return; + } +#endif +#endif + + QStyle * const style = this->style(); + + // set current item and select it + view()->selectionModel()->setCurrentIndex(d->currentIndex, + QItemSelectionModel::ClearAndSelect); + QComboBoxPrivateContainer* container = d->viewContainer(); + QStyleOptionComboBox opt; + initStyleOption(&opt); + QRect listRect(style->subControlRect(QStyle::CC_ComboBox, &opt, + QStyle::SC_ComboBoxListBoxPopup, this)); + QRect screen = d->popupGeometry(QApplication::desktop()->screenNumber(this)); + QPoint below = mapToGlobal(listRect.bottomLeft()); + int belowHeight = screen.bottom() - below.y(); + QPoint above = mapToGlobal(listRect.topLeft()); + int aboveHeight = above.y() - screen.y(); + bool boundToScreen = !window()->testAttribute(Qt::WA_DontShowOnScreen); + + const bool usePopup = style->styleHint(QStyle::SH_ComboBox_Popup, &opt, this); + + { + int listHeight = 0; + int count = 0; + QStack<QModelIndex> toCheck; + toCheck.push(view()->rootIndex()); +#ifndef QT_NO_TREEVIEW + QTreeView *treeView = qobject_cast<QTreeView*>(view()); + if (treeView && treeView->header() && !treeView->header()->isHidden()) + listHeight += treeView->header()->height(); +#endif + while (!toCheck.isEmpty()) { + QModelIndex parent = toCheck.pop(); + for (int i = 0; i < d->model->rowCount(parent); ++i) { + QModelIndex idx = d->model->index(i, d->modelColumn, parent); + if (!idx.isValid()) + continue; + listHeight += view()->visualRect(idx).height() + container->spacing(); +#ifndef QT_NO_TREEVIEW + if (d->model->hasChildren(idx) && treeView && treeView->isExpanded(idx)) + toCheck.push(idx); +#endif + ++count; + if (!usePopup && count > d->maxVisibleItems) { + toCheck.clear(); + break; + } + } + } + listRect.setHeight(listHeight); + } + + // add the frame size to the height. (+the spacing for the top and the bottom item) + int marginTop, marginBottom; + view()->getContentsMargins(0, &marginTop, 0, &marginBottom); + listRect.setHeight(listRect.height() + 2*container->spacing() + + marginTop + marginBottom); + + // Add space for margin at top and bottom if the style wants it. + if (usePopup) + listRect.setHeight(listRect.height() + style->pixelMetric(QStyle::PM_MenuVMargin, &opt, this) * 2); + + // Make sure the popup is wide enough to display its contents. + if (usePopup) { + const int diff = d->computeWidthHint() - width(); + if (diff > 0) + listRect.setWidth(listRect.width() + diff); + } + + //we need to activate the layout to make sure the min/maximum size are set when the widget was not yet show + container->layout()->activate(); + //takes account of the minimum/maximum size of the container + listRect.setSize( listRect.size().expandedTo(container->minimumSize()) + .boundedTo(container->maximumSize())); + + // make sure the widget fits on screen + if (boundToScreen) { + if (listRect.width() > screen.width() ) + listRect.setWidth(screen.width()); + if (mapToGlobal(listRect.bottomRight()).x() > screen.right()) { + below.setX(screen.x() + screen.width() - listRect.width()); + above.setX(screen.x() + screen.width() - listRect.width()); + } + if (mapToGlobal(listRect.topLeft()).x() < screen.x() ) { + below.setX(screen.x()); + above.setX(screen.x()); + } + } + + if (usePopup) { + // Position horizontally. + listRect.moveLeft(above.x()); + + // Position vertically so the curently selected item lines up + // with the combo box. + const QRect currentItemRect = view()->visualRect(view()->currentIndex()); + const int offset = listRect.top() - currentItemRect.top(); + listRect.moveTop(above.y() + offset - listRect.top()); + + + // Clamp the listRect height and vertical position so we don't expand outside the + // available screen geometry.This may override the vertical position, but it is more + // important to show as much as possible of the popup. + const int height = !boundToScreen ? listRect.height() : qMin(listRect.height(), screen.height()); + listRect.setHeight(height); + if (boundToScreen) { + if (listRect.top() < screen.top()) + listRect.moveTop(screen.top()); + if (listRect.bottom() > screen.bottom()) + listRect.moveBottom(screen.bottom()); + } + } else if (!boundToScreen || listRect.height() <= belowHeight) { + listRect.moveTopLeft(below); + } else if (listRect.height() <= aboveHeight) { + listRect.moveBottomLeft(above); + } else if (belowHeight >= aboveHeight) { + listRect.setHeight(belowHeight); + listRect.moveTopLeft(below); + } else { + listRect.setHeight(aboveHeight); + listRect.moveBottomLeft(above); + } + +#ifndef QT_NO_IM + if (QInputContext *qic = inputContext()) + qic->reset(); +#endif + QScrollBar *sb = view()->horizontalScrollBar(); + Qt::ScrollBarPolicy policy = view()->horizontalScrollBarPolicy(); + bool needHorizontalScrollBar = (policy == Qt::ScrollBarAsNeeded || policy == Qt::ScrollBarAlwaysOn) + && sb->minimum() < sb->maximum(); + if (needHorizontalScrollBar) { + listRect.adjust(0, 0, 0, sb->height()); + } + container->setGeometry(listRect); + + bool updatesEnabled = container->updatesEnabled(); +#if defined(Q_WS_WIN) && !defined(QT_NO_EFFECTS) + bool scrollDown = (listRect.topLeft() == below); + if (QApplication::isEffectEnabled(Qt::UI_AnimateCombo) + && !style->styleHint(QStyle::SH_ComboBox_Popup, &opt, this) && !window()->testAttribute(Qt::WA_DontShowOnScreen)) + qScrollEffect(container, scrollDown ? QEffects::DownScroll : QEffects::UpScroll, 150); +#endif + container->setUpdatesEnabled(false); + container->raise(); + container->show(); + container->updateScrollers(); + view()->setFocus(); + + view()->scrollTo(view()->currentIndex(), + style->styleHint(QStyle::SH_ComboBox_Popup, &opt, this) + ? QAbstractItemView::PositionAtCenter + : QAbstractItemView::EnsureVisible); + + container->setUpdatesEnabled(updatesEnabled); + container->update(); +} + +/*! + Hides the list of items in the combobox if it is currently visible + and resets the internal state, so that if the custom pop-up was + shown inside the reimplemented showPopup(), then you also need to + reimplement the hidePopup() function to hide your custom pop-up + and call the base class implementation to reset the internal state + whenever your custom pop-up widget is hidden. + + \sa showPopup() +*/ +void QComboBox::hidePopup() +{ + Q_D(QComboBox); + if (d->container && d->container->isVisible()) { +#if !defined(QT_NO_EFFECTS) + d->model->blockSignals(true); + d->container->itemView()->blockSignals(true); + d->container->blockSignals(true); + // Flash selected/triggered item (if any). + if (style()->styleHint(QStyle::SH_Menu_FlashTriggeredItem)) { + QItemSelectionModel *selectionModel = view() ? view()->selectionModel() : 0; + if (selectionModel && selectionModel->hasSelection()) { + QEventLoop eventLoop; + const QItemSelection selection = selectionModel->selection(); + + // Deselect item and wait 60 ms. + selectionModel->select(selection, QItemSelectionModel::Toggle); + QTimer::singleShot(60, &eventLoop, SLOT(quit())); + eventLoop.exec(); + + // Select item and wait 20 ms. + selectionModel->select(selection, QItemSelectionModel::Toggle); + QTimer::singleShot(20, &eventLoop, SLOT(quit())); + eventLoop.exec(); + } + } + + // Fade out. + bool needFade = style()->styleHint(QStyle::SH_Menu_FadeOutOnHide); + if (needFade) { +#if defined(Q_WS_MAC) + macWindowFade(qt_mac_window_for(d->container)); +#endif // Q_WS_MAC + // Other platform implementations welcome :-) + } + d->model->blockSignals(false); + d->container->itemView()->blockSignals(false); + d->container->blockSignals(false); + + if (!needFade) +#endif // QT_NO_EFFECTS + // Fade should implicitly hide as well ;-) + d->container->hide(); + } +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled() && isEditable() && hasFocus()) + setEditFocus(true); +#endif + d->_q_resetButton(); +} + +/*! + Clears the combobox, removing all items. + + Note: If you have set an external model on the combobox this model + will still be cleared when calling this function. +*/ +void QComboBox::clear() +{ + Q_D(QComboBox); + d->model->removeRows(0, d->model->rowCount(d->root), d->root); +} + +/*! + \fn void QComboBox::clearValidator() + + Use setValidator(0) instead. +*/ + +/*! + Clears the contents of the line edit used for editing in the combobox. +*/ +void QComboBox::clearEditText() +{ + Q_D(QComboBox); + if (d->lineEdit) + d->lineEdit->clear(); +} + +/*! + Sets the \a text in the combobox's text edit. +*/ +void QComboBox::setEditText(const QString &text) +{ + Q_D(QComboBox); + if (d->lineEdit) + d->lineEdit->setText(text); +} + +/*! + \reimp +*/ +void QComboBox::focusInEvent(QFocusEvent *e) +{ + Q_D(QComboBox); + update(); + if (d->lineEdit) { + d->lineEdit->event(e); +#ifndef QT_NO_COMPLETER + if (d->lineEdit->completer()) + d->lineEdit->completer()->setWidget(this); +#endif + } +} + +/*! + \reimp +*/ +void QComboBox::focusOutEvent(QFocusEvent *e) +{ + Q_D(QComboBox); + update(); + if (d->lineEdit) + d->lineEdit->event(e); +} + +/*! \reimp */ +void QComboBox::changeEvent(QEvent *e) +{ + Q_D(QComboBox); + switch (e->type()) { + case QEvent::StyleChange: + d->updateDelegate(); +#ifdef Q_WS_MAC + case QEvent::MacSizeChange: +#endif + d->sizeHint = QSize(); // invalidate size hint + d->minimumSizeHint = QSize(); + d->updateLayoutDirection(); + if (d->lineEdit) + d->updateLineEditGeometry(); + d->setLayoutItemMargins(QStyle::SE_ComboBoxLayoutItem); + // ### need to update scrollers etc. as well here + break; + case QEvent::EnabledChange: + if (!isEnabled()) + hidePopup(); + break; + case QEvent::PaletteChange: { + QStyleOptionComboBox opt; + initStyleOption(&opt); +#ifndef QT_NO_MENU + if (style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, this)) { + QMenu menu; + menu.ensurePolished(); + d->viewContainer()->setPalette(menu.palette()); + d->viewContainer()->setWindowOpacity(menu.windowOpacity()); + } else +#endif + { + d->viewContainer()->setPalette(palette()); + d->viewContainer()->setWindowOpacity(1.0); + } + break; + } + case QEvent::FontChange: + d->sizeHint = QSize(); // invalidate size hint + d->viewContainer()->setFont(font()); + if (d->lineEdit) + d->updateLineEditGeometry(); + break; + default: + break; + } + QWidget::changeEvent(e); +} + +/*! + \reimp +*/ +void QComboBox::resizeEvent(QResizeEvent *) +{ + Q_D(QComboBox); + d->updateLineEditGeometry(); +} + +/*! + \reimp +*/ +void QComboBox::paintEvent(QPaintEvent *) +{ + QStylePainter painter(this); + painter.setPen(palette().color(QPalette::Text)); + + // draw the combobox frame, focusrect and selected etc. + QStyleOptionComboBox opt; + initStyleOption(&opt); + painter.drawComplexControl(QStyle::CC_ComboBox, opt); + + // draw the icon and text + painter.drawControl(QStyle::CE_ComboBoxLabel, opt); +} + +/*! + \reimp +*/ +void QComboBox::showEvent(QShowEvent *e) +{ + Q_D(QComboBox); + if (!d->shownOnce && d->sizeAdjustPolicy == QComboBox::AdjustToContentsOnFirstShow) { + d->sizeHint = QSize(); + updateGeometry(); + } + d->shownOnce = true; + QWidget::showEvent(e); +} + +/*! + \reimp +*/ +void QComboBox::hideEvent(QHideEvent *) +{ + hidePopup(); +} + +/*! + \reimp +*/ +bool QComboBox::event(QEvent *event) +{ + Q_D(QComboBox); + switch(event->type()) { + case QEvent::LayoutDirectionChange: + case QEvent::ApplicationLayoutDirectionChange: + d->updateLayoutDirection(); + d->updateLineEditGeometry(); + break; + case QEvent::HoverEnter: + case QEvent::HoverLeave: + case QEvent::HoverMove: + if (const QHoverEvent *he = static_cast<const QHoverEvent *>(event)) + d->updateHoverControl(he->pos()); + break; + case QEvent::ShortcutOverride: + if (d->lineEdit) + return d->lineEdit->event(event); + break; +#ifdef QT_KEYPAD_NAVIGATION + case QEvent::EnterEditFocus: + if (!d->lineEdit) + setEditFocus(false); // We never want edit focus if we are not editable + else + d->lineEdit->event(event); //so cursor starts + break; + case QEvent::LeaveEditFocus: + if (d->lineEdit) + d->lineEdit->event(event); //so cursor stops + break; +#endif + default: + break; + } + return QWidget::event(event); +} + +/*! + \reimp +*/ +void QComboBox::mousePressEvent(QMouseEvent *e) +{ + Q_D(QComboBox); + QStyleOptionComboBox opt; + initStyleOption(&opt); + QStyle::SubControl sc = style()->hitTestComplexControl(QStyle::CC_ComboBox, &opt, e->pos(), + this); + if (e->button() == Qt::LeftButton && (sc == QStyle::SC_ComboBoxArrow || !isEditable()) + && !d->viewContainer()->isVisible()) { + if (sc == QStyle::SC_ComboBoxArrow) + d->updateArrow(QStyle::State_Sunken); +#ifdef QT_KEYPAD_NAVIGATION + if (!d->lineEdit) { +#endif + // We've restricted the next couple of lines, because by not calling + // viewContainer(), we avoid creating the QComboBoxPrivateContainer. + d->viewContainer()->blockMouseReleaseTimer.start(QApplication::doubleClickInterval()); + d->viewContainer()->initialClickPosition = mapToGlobal(e->pos()); +#ifdef QT_KEYPAD_NAVIGATION + } +#endif + showPopup(); + } else { +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled() && sc == QStyle::SC_ComboBoxEditField && d->lineEdit) { + d->lineEdit->event(e); //so lineedit can move cursor, etc + return; + } +#endif + QWidget::mousePressEvent(e); + } +} + +/*! + \reimp +*/ +void QComboBox::mouseReleaseEvent(QMouseEvent *e) +{ + Q_D(QComboBox); + Q_UNUSED(e); + d->updateArrow(QStyle::State_None); +} + +/*! + \reimp +*/ +void QComboBox::keyPressEvent(QKeyEvent *e) +{ + Q_D(QComboBox); + +#ifndef QT_NO_COMPLETER + if (d->lineEdit + && d->lineEdit->completer() + && d->lineEdit->completer()->popup() + && d->lineEdit->completer()->popup()->isVisible()) { + // provide same autocompletion support as line edit + d->lineEdit->event(e); + return; + } +#endif + + enum Move { NoMove=0 , MoveUp , MoveDown , MoveFirst , MoveLast}; + + Move move = NoMove; + int newIndex = currentIndex(); + switch (e->key()) { + case Qt::Key_Up: + if (e->modifiers() & Qt::ControlModifier) + break; // pass to line edit for auto completion + case Qt::Key_PageUp: +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled()) + e->ignore(); + else +#endif + move = MoveUp; + break; + case Qt::Key_Down: + if (e->modifiers() & Qt::AltModifier) { + showPopup(); + return; + } else if (e->modifiers() & Qt::ControlModifier) + break; // pass to line edit for auto completion + // fall through + case Qt::Key_PageDown: +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled()) + e->ignore(); + else +#endif + move = MoveDown; + break; + case Qt::Key_Home: + if (!d->lineEdit) + move = MoveFirst; + break; + case Qt::Key_End: + if (!d->lineEdit) + move = MoveLast; + break; + case Qt::Key_F4: + if (!e->modifiers()) { + showPopup(); + return; + } + break; + case Qt::Key_Space: + if (!d->lineEdit) { + showPopup(); + return; + } + case Qt::Key_Enter: + case Qt::Key_Return: + case Qt::Key_Escape: + if (!d->lineEdit) + e->ignore(); + break; +#ifdef QT_KEYPAD_NAVIGATION + case Qt::Key_Select: + if (QApplication::keypadNavigationEnabled() + && (!hasEditFocus() || !d->lineEdit)) { + showPopup(); + return; + } + break; + case Qt::Key_Left: + case Qt::Key_Right: + if (QApplication::keypadNavigationEnabled() && !hasEditFocus()) + e->ignore(); + break; + case Qt::Key_Back: + if (QApplication::keypadNavigationEnabled()) { + if (!hasEditFocus() || !d->lineEdit) + e->ignore(); + } else { + e->ignore(); // let the surounding dialog have it + } + break; +#endif + default: + if (!d->lineEdit) { + if (!e->text().isEmpty()) + d->keyboardSearchString(e->text()); + else + e->ignore(); + } + } + + if (move != NoMove) { + e->accept(); + switch (move) { + case MoveFirst: + newIndex = -1; + case MoveDown: + newIndex++; + while ((newIndex < count()) && !(d->model->flags(d->model->index(newIndex,d->modelColumn,d->root)) & Qt::ItemIsEnabled)) + newIndex++; + break; + case MoveLast: + newIndex = count(); + case MoveUp: + newIndex--; + while ((newIndex >= 0) && !(d->model->flags(d->model->index(newIndex,d->modelColumn,d->root)) & Qt::ItemIsEnabled)) + newIndex--; + break; + default: + e->ignore(); + break; + } + + if (newIndex >= 0 && newIndex < count() && newIndex != currentIndex()) { + setCurrentIndex(newIndex); + d->emitActivated(d->currentIndex); + } + } else if (d->lineEdit) { + d->lineEdit->event(e); + } +} + + +/*! + \reimp +*/ +void QComboBox::keyReleaseEvent(QKeyEvent *e) +{ + Q_D(QComboBox); + if (d->lineEdit) + d->lineEdit->event(e); +} + +/*! + \reimp +*/ +#ifndef QT_NO_WHEELEVENT +void QComboBox::wheelEvent(QWheelEvent *e) +{ + Q_D(QComboBox); + if (!d->viewContainer()->isVisible()) { + int newIndex = currentIndex(); + + if (e->delta() > 0) { + newIndex--; + while ((newIndex >= 0) && !(d->model->flags(d->model->index(newIndex,d->modelColumn,d->root)) & Qt::ItemIsEnabled)) + newIndex--; + } else { + newIndex++; + while ((newIndex < count()) && !(d->model->flags(d->model->index(newIndex,d->modelColumn,d->root)) & Qt::ItemIsEnabled)) + newIndex++; + } + + if (newIndex >= 0 && newIndex < count() && newIndex != currentIndex()) { + setCurrentIndex(newIndex); + d->emitActivated(d->currentIndex); + } + e->accept(); + } +} +#endif + +#ifndef QT_NO_CONTEXTMENU +/*! + \reimp +*/ +void QComboBox::contextMenuEvent(QContextMenuEvent *e) +{ + Q_D(QComboBox); + if (d->lineEdit) { + Qt::ContextMenuPolicy p = d->lineEdit->contextMenuPolicy(); + d->lineEdit->setContextMenuPolicy(Qt::DefaultContextMenu); + d->lineEdit->event(e); + d->lineEdit->setContextMenuPolicy(p); + } +} +#endif // QT_NO_CONTEXTMENU + +void QComboBoxPrivate::keyboardSearchString(const QString &text) +{ + // use keyboardSearch from the listView so we do not duplicate code + QAbstractItemView *view = viewContainer()->itemView(); + view->setCurrentIndex(currentIndex); + int currentRow = view->currentIndex().row(); + view->keyboardSearch(text); + if (currentRow != view->currentIndex().row()) { + setCurrentIndex(view->currentIndex()); + emitActivated(currentIndex); + } +} + +void QComboBoxPrivate::modelChanged() +{ + Q_Q(QComboBox); + + if (sizeAdjustPolicy == QComboBox::AdjustToContents) { + sizeHint = QSize(); + adjustComboBoxSize(); + q->updateGeometry(); + } +} + +/*! + \reimp +*/ +void QComboBox::inputMethodEvent(QInputMethodEvent *e) +{ + Q_D(QComboBox); + if (d->lineEdit) { + d->lineEdit->event(e); + } else { + if (!e->commitString().isEmpty()) + d->keyboardSearchString(e->commitString()); + else + e->ignore(); + } +} + +/*! + \reimp +*/ +QVariant QComboBox::inputMethodQuery(Qt::InputMethodQuery query) const +{ + Q_D(const QComboBox); + if (d->lineEdit) + return d->lineEdit->inputMethodQuery(query); + return QWidget::inputMethodQuery(query); +} + +/*! + \fn bool QComboBox::editable() const + + Use isEditable() instead. +*/ + +/*! + \fn void QComboBox::insertItem(const QPixmap &pixmap, int index) + + Use an insertItem() function that takes a QIcon instead, for + example, insertItem(index, QIcon(pixmap)). +*/ + +/*! + \fn void QComboBox::insertItem(const QPixmap &pixmap, const QString &text, int index) + + Use an insertItem() function that takes a QIcon instead, for + example, insertItem(index, QIcon(pixmap), text). + + \sa insertItems() +*/ + +/*! + \fn void QComboBox::changeItem(const QString &text, int index) + + Use setItemText() instead. +*/ + +/*! + \fn void QComboBox::changeItem(const QPixmap &pixmap, int index) + + Use setItemIcon() instead, for example, + setItemIcon(index, QIcon(pixmap)). +*/ + +/*! + \fn void QComboBox::changeItem(const QPixmap &pixmap, const QString &text, int index) + + Use setItem() instead, for example, setItem(index, QIcon(pixmap),text). +*/ + +/*! + \fn void QComboBox::addItem(const QString &text, const QVariant &userData) + + Adds an item to the combobox with the given \a text, and + containing the specified \a userData (stored in the Qt::UserRole). + The item is appended to the list of existing items. +*/ + +/*! + \fn void QComboBox::addItem(const QIcon &icon, const QString &text, + const QVariant &userData) + + Adds an item to the combobox with the given \a icon and \a text, + and containing the specified \a userData (stored in the + Qt::UserRole). The item is appended to the list of existing items. +*/ + +/*! + \fn void QComboBox::addItems(const QStringList &texts) + + Adds each of the strings in the given \a texts to the combobox. Each item + is appended to the list of existing items in turn. +*/ + +/*! + \fn void QComboBox::editTextChanged(const QString &text) + + This signal is emitted when the text in the combobox's line edit + widget is changed. The new text is specified by \a text. +*/ + +/*! + \fn QComboBox::InsertPolicy QComboBox::insertionPolicy() const + \compat + + Use QComboBox::insertPolicy instead. +*/ + +/*! + \fn void QComboBox::setInsertionPolicy(InsertPolicy policy) + \compat + + Use QComboBox::insertPolicy instead. +*/ + +/*! + \fn void QComboBox::setCurrentText(const QString &text) + \compat + + Use setItemText() instead. + + \sa currentIndex() +*/ + +/*! + \fn QString QComboBox::text(int index) const + \compat + + Use itemText() instead. +*/ + +/*! + \fn QPixmap QComboBox::pixmap(int index) const + \compat + + Use itemIcon() instead. +*/ + +/*! + \fn void QComboBox::insertStringList(const QStringList &list, int index) + \compat + + Use insertItems() instead. +*/ + +/*! + \fn void QComboBox::insertItem(const QString &text, int index) + \compat +*/ + +/*! + \fn void QComboBox::clearEdit() + \compat + + Use clearEditText() instead. +*/ + + +/*! + \property QComboBox::frame + \brief whether the combo box draws itself with a frame + + + If enabled (the default) the combo box draws itself inside a + frame, otherwise the combo box draws itself without any frame. +*/ +bool QComboBox::hasFrame() const +{ + Q_D(const QComboBox); + return d->frame; +} + + +void QComboBox::setFrame(bool enable) +{ + Q_D(QComboBox); + d->frame = enable; + update(); + updateGeometry(); +} + +/*! + \property QComboBox::modelColumn + \brief the column in the model that is visible. + + If set prior to populating the combo box, the pop-up view will + not be affected and will show the first column (using this property's + default value). + + By default, this property has a value of 0. +*/ +int QComboBox::modelColumn() const +{ + Q_D(const QComboBox); + return d->modelColumn; +} + +void QComboBox::setModelColumn(int visibleColumn) +{ + Q_D(QComboBox); + d->modelColumn = visibleColumn; + QListView *lv = qobject_cast<QListView *>(d->viewContainer()->itemView()); + if (lv) + lv->setModelColumn(visibleColumn); +#ifndef QT_NO_COMPLETER + if (d->lineEdit && d->lineEdit->completer() + && d->lineEdit->completer() == d->completer) + d->lineEdit->completer()->setCompletionColumn(visibleColumn); +#endif + setCurrentIndex(currentIndex()); //update the text to the text of the new column; +} + +/*! + \fn int QComboBox::currentItem() const + + Use currentIndex() instead. +*/ + +/*! + \fn void QComboBox::setCurrentItem(int) + + Use setCurrentIndex(int) instead. +*/ + +/*! + \fn void QComboBox::popup() + + Use showPopup() instead. +*/ + +/*! + \fn void QComboBox::textChanged(const QString &text) + + Use the editTextChanged(const QString &text) signal instead. +*/ + +/*! + \typedef QComboBox::Policy + \compat + + Use QComboBox::InsertPolicy instead. +*/ + +QT_END_NAMESPACE + +#include "moc_qcombobox.cpp" + +#endif // QT_NO_COMBOBOX |