Core: Allow numeric options in OptionsPopup

For numeric options a spinbox is added to the popup. The text shown
before and behind the spinbox is controlled via the action's label:

    "Show {} preceding lines"

A label with the text "Show " is created before the spinbox, and another
label with the text " preceding lines" is created behind.

Task-number: QTCREATORBUG-30167
Change-Id: Id0fba97c3a674ae41d4ab9d767694f5f5674ef18
Reviewed-by: hjk <hjk@qt.io>
Reviewed-by: Eike Ziller <eike.ziller@qt.io>
This commit is contained in:
Mathias Hasselmann
2024-01-07 10:32:06 +01:00
committed by hjk
parent 5374705531
commit 54532795ab
2 changed files with 120 additions and 8 deletions

View File

@@ -5,34 +5,111 @@
#include "../actionmanager/actionmanager.h"
#include <utils/proxyaction.h>
#include <utils/qtcassert.h>
#include <utils/stylehelper.h>
#include <QAction>
#include <QCheckBox>
#include <QEvent>
#include <QKeyEvent>
#include <QLabel>
#include <QScreen>
#include <QSpinBox>
#include <QStyleOption>
#include <QVBoxLayout>
using namespace Utils;
const char kNumericOptionProperty[] = "qtc_numericOption";
namespace Core {
static QCheckBox *createCheckboxForCommand(QObject *owner, Id id)
static QCheckBox *createCheckboxForAction(QObject *owner, QAction *action)
{
QAction *action = ActionManager::command(id)->action();
QCheckBox *checkbox = new QCheckBox(action->text());
checkbox->setToolTip(action->toolTip());
checkbox->setChecked(action->isChecked());
checkbox->setEnabled(action->isEnabled());
checkbox->installEventFilter(owner); // enter key handling
QObject::connect(checkbox, &QCheckBox::clicked, action, &QAction::setChecked);
QObject::connect(action, &QAction::changed, checkbox, [action, checkbox] {
checkbox->setEnabled(action->isEnabled());
});
QObject::connect(action, &QAction::enabledChanged, checkbox, &QCheckBox::setEnabled);
return checkbox;
}
static QWidget *createSpinboxForAction(QObject *owner, QAction *action)
{
const std::optional<NumericOption> option = NumericOption::get(action);
QTC_ASSERT(option, return nullptr);
const auto proxyAction = qobject_cast<ProxyAction *>(action);
QTC_ASSERT(proxyAction, return nullptr);
const auto prefix = action->text().section("{}", 0, 0);
const auto suffix = action->text().section("{}", 1);
const auto widget = new QWidget{};
widget->setEnabled(action->isEnabled());
auto styleOption = QStyleOptionButton{};
const QRect box = widget->style()->subElementRect(QStyle::SE_CheckBoxContents, &styleOption);
const auto spinbox = new QSpinBox{widget};
spinbox->installEventFilter(owner); // enter key handling
spinbox->setMinimum(option->minimumValue);
spinbox->setMaximum(option->maximumValue);
spinbox->setValue(option->currentValue);
QObject::connect(spinbox, &QSpinBox::valueChanged, action, [action](int value) {
std::optional<NumericOption> option = NumericOption::get(action);
QTC_ASSERT(option, return);
option->currentValue = value;
NumericOption::set(action, *option);
emit action->changed();
});
const auto updateCurrentAction = [proxyAction] {
if (!proxyAction->action())
return;
const std::optional<NumericOption> option = NumericOption::get(proxyAction);
QTC_ASSERT(option, return);
NumericOption::set(proxyAction->action(), *option);
emit proxyAction->action()->changed();
};
QObject::connect(proxyAction, &ProxyAction::currentActionChanged, updateCurrentAction);
QObject::connect(proxyAction, &QAction::changed, updateCurrentAction);
QObject::connect(action, &QAction::enabledChanged, widget, &QWidget::setEnabled);
const auto layout = new QHBoxLayout{widget};
layout->setContentsMargins(box.left(), 0, 0, 0);
layout->setSpacing(0); // the label will have spaces before and after the placeholder
if (!prefix.isEmpty()) {
const auto prefixLabel = new QLabel{prefix, widget};
const auto prefixSpan = suffix.isEmpty() ? 1 : 0;
layout->addWidget(prefixLabel, prefixSpan);
prefixLabel->setBuddy(spinbox);
}
layout->addWidget(spinbox);
if (!suffix.isEmpty()) {
const auto suffixLabel = new QLabel{suffix, widget};
layout->addWidget(suffixLabel, 1);
suffixLabel->setBuddy(spinbox);
}
return widget;
}
static QWidget *createWidgetForCommand(QObject *owner, Id id)
{
const auto action = ActionManager::command(id)->action();
if (NumericOption::get(action))
return createSpinboxForAction(owner, action);
else
return createCheckboxForAction(owner, action);
}
/*!
\class Core::OptionsPopup
\inmodule QtCreator
@@ -50,12 +127,12 @@ OptionsPopup::OptionsPopup(QWidget *parent, const QVector<Id> &commands)
bool first = true;
for (const Id &command : commands) {
QCheckBox *checkBox = createCheckboxForCommand(this, command);
const auto widget = createWidgetForCommand(this, command);
if (first) {
checkBox->setFocus();
widget->setFocus();
first = false;
}
layout->addWidget(checkBox);
layout->addWidget(widget);
}
const QPoint globalPos = parent->mapToGlobal(QPoint(0, -sizeHint().height()));
const QRect screenGeometry = parent->screen()->availableGeometry();
@@ -88,4 +165,19 @@ bool OptionsPopup::eventFilter(QObject *obj, QEvent *ev)
return QWidget::eventFilter(obj, ev);
}
std::optional<NumericOption> NumericOption::get(QObject *o)
{
const QVariant opt = o->property(kNumericOptionProperty);
if (opt.isValid()) {
QTC_ASSERT(opt.canConvert<NumericOption>(), return {});
return opt.value<NumericOption>();
}
return {};
}
void NumericOption::set(QObject *o, const NumericOption &opt)
{
o->setProperty(kNumericOptionProperty, QVariant::fromValue(opt));
}
} // namespace Core

View File

@@ -9,8 +9,28 @@
#include <QWidget>
#include <optional>
namespace Core {
class CORE_EXPORT NumericOption
{
public:
NumericOption() noexcept = default;
NumericOption(int current, int minimum, int maximum) noexcept
: currentValue{current}
, minimumValue{minimum}
, maximumValue{maximum}
{}
static std::optional<NumericOption> get(QObject *o);
static void set(QObject *o, const NumericOption &opt);
int currentValue;
int minimumValue;
int maximumValue;
};
class CORE_EXPORT OptionsPopup : public QWidget
{
public: