QComboBox: Use native popups on Mac

This remains an opt-in solution bound to the usage
of SH_ComboBox_UseNativePopup in a proxy style. The
midterm goal is to make this option on by default,
possibly in 5.4. This solution is and will remain a hint
in the sense that some exotic use cases of QComboBox
(e.g., when setting its view) are inherently incompatible
with the native popup idea.

Task-number: QTBUG-32731
Change-Id: I2a3d780795c22f9989e44325fcaf314538b1de49
Reviewed-by: Jens Bache-Wiig <jens.bache-wiig@digia.com>
This commit is contained in:
Gabriel de Dietrich 2014-04-03 17:52:10 +02:00 committed by The Qt Project
parent b8d0fac5a9
commit abbdb4d98d
2 changed files with 94 additions and 6 deletions

View File

@ -44,6 +44,7 @@
#ifndef QT_NO_COMBOBOX #ifndef QT_NO_COMBOBOX
#include <qstylepainter.h> #include <qstylepainter.h>
#include <qpa/qplatformtheme.h> #include <qpa/qplatformtheme.h>
#include <qpa/qplatformmenu.h>
#include <qlineedit.h> #include <qlineedit.h>
#include <qapplication.h> #include <qapplication.h>
#include <qdesktopwidget.h> #include <qdesktopwidget.h>
@ -205,7 +206,7 @@ void QComboBoxPrivate::updateArrow(QStyle::StateFlag state)
arrowState = state; arrowState = state;
QStyleOptionComboBox opt; QStyleOptionComboBox opt;
q->initStyleOption(&opt); q->initStyleOption(&opt);
q->update(q->style()->subControlRect(QStyle::CC_ComboBox, &opt, QStyle::SC_ComboBoxArrow, q)); q->update(q->rect());
} }
void QComboBoxPrivate::_q_modelReset() void QComboBoxPrivate::_q_modelReset()
@ -2375,6 +2376,79 @@ QSize QComboBox::sizeHint() const
return d->recomputeSizeHint(d->sizeHint); return d->recomputeSizeHint(d->sizeHint);
} }
#ifdef Q_OS_OSX
/*!
* \internal
*
* Tries to show a native popup. Returns true if it could, false otherwise.
*
*/
bool QComboBoxPrivate::showNativePopup()
{
Q_Q(QComboBox);
QPlatformTheme *theme = QGuiApplicationPrivate::instance()->platformTheme();
if (QPlatformMenu *menu = theme->createPlatformMenu()) {
int itemsCount = q->count();
struct IndexSetter {
int index;
QComboBox *cb;
void operator()(void) { cb->setCurrentIndex(index); }
};
QList<QPlatformMenuItem *> items;
items.reserve(itemsCount);
QPlatformMenuItem *currentItem = 0;
int currentIndex = q->currentIndex();
for (int i = 0; i < itemsCount; ++i) {
QPlatformMenuItem *item = theme->createPlatformMenuItem();
QModelIndex rowIndex = model->index(i, modelColumn, root);
QVariant textVariant = model->data(rowIndex, Qt::EditRole);
item->setText(textVariant.toString());
QVariant iconVariant = model->data(rowIndex, Qt::DecorationRole);
if (iconVariant.canConvert<QIcon>())
item->setIcon(iconVariant.value<QIcon>());
item->setCheckable(true);
item->setChecked(i == currentIndex);
if (!currentItem || i == currentIndex)
currentItem = item;
IndexSetter setter = { i, q };
QObject::connect(item, &QPlatformMenuItem::activated, setter);
menu->insertMenuItem(item, 0);
menu->syncMenuItem(item);
}
QWindow *tlw = q->window()->windowHandle();
menu->setFont(q->font());
menu->setMinimumWidth(q->rect().width());
QPoint offset = QPoint(0, 7);
if (q->testAttribute(Qt::WA_MacSmallSize))
offset = QPoint(-1, 7);
else if (q->testAttribute(Qt::WA_MacMiniSize))
offset = QPoint(-2, 6);
menu->showPopup(tlw, tlw->mapFromGlobal(q->mapToGlobal(offset)), currentItem);
menu->deleteLater();
Q_FOREACH (QPlatformMenuItem *item, items)
item->deleteLater();
// The Cocoa popup will swallow any mouse release event.
// We need to fake one here to un-press the button.
QMouseEvent mouseReleased(QEvent::MouseButtonRelease, q->pos(), Qt::LeftButton,
Qt::MouseButtons(Qt::LeftButton), Qt::KeyboardModifiers());
qApp->sendEvent(q, &mouseReleased);
return true;
}
return false;
}
#endif // Q_OS_OSX
/*! /*!
Displays the list of items in the combobox. If the list is empty Displays the list of items in the combobox. If the list is empty
then the no items will be shown. then the no items will be shown.
@ -2390,6 +2464,21 @@ void QComboBox::showPopup()
if (count() <= 0) if (count() <= 0)
return; return;
QStyle * const style = this->style();
QStyleOptionComboBox opt;
initStyleOption(&opt);
const bool usePopup = style->styleHint(QStyle::SH_ComboBox_Popup, &opt, this);
#ifdef Q_OS_OSX
if (usePopup
&& (!d->container
|| (view()->metaObject()->className() == QByteArray("QComboBoxListView")
&& view()->itemDelegate()->metaObject()->className() == QByteArray("QComboMenuDelegate")))
&& style->styleHint(QStyle::SH_ComboBox_UseNativePopup, &opt, this)
&& d->showNativePopup())
return;
#endif // Q_OS_OSX
#ifdef QT_KEYPAD_NAVIGATION #ifdef QT_KEYPAD_NAVIGATION
#ifndef QT_NO_COMPLETER #ifndef QT_NO_COMPLETER
if (QApplication::keypadNavigationEnabled() && d->completer) { if (QApplication::keypadNavigationEnabled() && d->completer) {
@ -2401,14 +2490,10 @@ void QComboBox::showPopup()
#endif #endif
#endif #endif
QStyle * const style = this->style();
// set current item and select it // set current item and select it
view()->selectionModel()->setCurrentIndex(d->currentIndex, view()->selectionModel()->setCurrentIndex(d->currentIndex,
QItemSelectionModel::ClearAndSelect); QItemSelectionModel::ClearAndSelect);
QComboBoxPrivateContainer* container = d->viewContainer(); QComboBoxPrivateContainer* container = d->viewContainer();
QStyleOptionComboBox opt;
initStyleOption(&opt);
QRect listRect(style->subControlRect(QStyle::CC_ComboBox, &opt, QRect listRect(style->subControlRect(QStyle::CC_ComboBox, &opt,
QStyle::SC_ComboBoxListBoxPopup, this)); QStyle::SC_ComboBoxListBoxPopup, this));
QRect screen = d->popupGeometry(QApplication::desktop()->screenNumber(this)); QRect screen = d->popupGeometry(QApplication::desktop()->screenNumber(this));
@ -2419,7 +2504,6 @@ void QComboBox::showPopup()
int aboveHeight = above.y() - screen.y(); int aboveHeight = above.y() - screen.y();
bool boundToScreen = !window()->testAttribute(Qt::WA_DontShowOnScreen); bool boundToScreen = !window()->testAttribute(Qt::WA_DontShowOnScreen);
const bool usePopup = style->styleHint(QStyle::SH_ComboBox_Popup, &opt, this);
{ {
int listHeight = 0; int listHeight = 0;
int count = 0; int count = 0;

View File

@ -377,6 +377,10 @@ public:
void modelChanged(); void modelChanged();
void updateViewContainerPaletteAndOpacity(); void updateViewContainerPaletteAndOpacity();
#ifdef Q_OS_OSX
bool showNativePopup();
#endif
QAbstractItemModel *model; QAbstractItemModel *model;
QLineEdit *lineEdit; QLineEdit *lineEdit;
QComboBoxPrivateContainer *container; QComboBoxPrivateContainer *container;