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
#include <qstylepainter.h>
#include <qpa/qplatformtheme.h>
#include <qpa/qplatformmenu.h>
#include <qlineedit.h>
#include <qapplication.h>
#include <qdesktopwidget.h>
@ -205,7 +206,7 @@ void QComboBoxPrivate::updateArrow(QStyle::StateFlag state)
arrowState = state;
QStyleOptionComboBox 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()
@ -2375,6 +2376,79 @@ QSize QComboBox::sizeHint() const
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
then the no items will be shown.
@ -2390,6 +2464,21 @@ void QComboBox::showPopup()
if (count() <= 0)
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
#ifndef QT_NO_COMPLETER
if (QApplication::keypadNavigationEnabled() && d->completer) {
@ -2401,14 +2490,10 @@ void QComboBox::showPopup()
#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));
@ -2419,7 +2504,6 @@ void QComboBox::showPopup()
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;

View File

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