diff --git a/app/viewmainpage.cpp b/app/viewmainpage.cpp index 4614a69329ccd777e8265bc6704877d3725fceb1..1dd7e11cc20a2213febf1fca96ce93a24a375585 100644 --- a/app/viewmainpage.cpp +++ b/app/viewmainpage.cpp @@ -781,7 +781,6 @@ void ViewMainPage::slotEnterPressed() } } } - emit goToBrowseModeRequested(); } bool ViewMainPage::eventFilter(QObject* watched, QEvent* event) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index e1443f94c3b4fa5d9e98325ae9aaaf6934653c42..bdf5fe399151f7a5eaf8872906eb8abf949493a3 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -140,6 +140,7 @@ set(gwenviewlib_SRCS transformimageoperation.cpp urlutils.cpp widgetfloater.cpp + zoomcombobox/zoomcombobox.cpp zoomslider.cpp zoomwidget.cpp scrollerutils.cpp diff --git a/lib/documentview/abstractimageview.cpp b/lib/documentview/abstractimageview.cpp index 1a7eef0777d820187e276dde5873d3ede59240a5..1660f6a79ebb0ddd07757dad32584e234edda2a0 100644 --- a/lib/documentview/abstractimageview.cpp +++ b/lib/documentview/abstractimageview.cpp @@ -34,7 +34,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA #include #include #include - namespace Gwenview { @@ -277,6 +276,9 @@ bool AbstractImageView::zoomToFill() const void AbstractImageView::setZoomToFit(bool on) { + if (d->mZoomToFit == on) { + return; + } d->mZoomToFit = on; if (on) { d->mZoomToFill = false; @@ -290,6 +292,9 @@ void AbstractImageView::setZoomToFit(bool on) void AbstractImageView::setZoomToFill(bool on, const QPointF& center) { + if (d->mZoomToFill == on) { + return; + } d->mZoomToFill = on; if (on) { d->mZoomToFit = false; diff --git a/lib/documentview/abstractimageview.h b/lib/documentview/abstractimageview.h index eca327c4a0fe1ecdd9306a04205ffd7cc5d0a3bd..f0a4d031bd14565a12d4894acc3422b0a8036687 100644 --- a/lib/documentview/abstractimageview.h +++ b/lib/documentview/abstractimageview.h @@ -36,7 +36,9 @@ class AlphaBackgroundItem; struct AbstractImageViewPrivate; /** - * + * The abstract base class used to implement common functionality of raster and vector image views + * like for example zooming, computing the area where the image will be displayed in the view and + * dealing with a background for transparent areas. */ class AbstractImageView : public QGraphicsWidget { @@ -127,8 +129,11 @@ public Q_SLOTS: void updateCursor(); Q_SIGNALS: + /** Emitted when the zoom mode changes to or from "Fit". */ void zoomToFitChanged(bool); + /** Emitted when the zoom mode changes to or from "Fill". */ void zoomToFillChanged(bool); + /** Emitted when the zoom value changes in any way. */ void zoomChanged(qreal); void zoomInRequested(const QPointF&); void zoomOutRequested(const QPointF&); diff --git a/lib/documentview/documentviewcontroller.cpp b/lib/documentview/documentviewcontroller.cpp index e50ac69ef49d3f0da1aa6ccd542b1a317b9457a5..ab00aec5b98726d0c73c750800ef91eb43440d9c 100644 --- a/lib/documentview/documentviewcontroller.cpp +++ b/lib/documentview/documentviewcontroller.cpp @@ -79,7 +79,7 @@ struct DocumentViewControllerPrivate mActualSizeAction = view->addAction(KStandardAction::ActualSize); mActualSizeAction->setCheckable(true); mActualSizeAction->setIcon(QIcon::fromTheme(QStringLiteral("zoom-original"))); - mActualSizeAction->setIconText(i18nc("@action:button Zoom to original size, shown in status bar, keep it short please", "100%")); + mActualSizeAction->setIconText(QLocale().toString(100).append(QLocale().percent())); mZoomInAction = view->addAction(KStandardAction::ZoomIn); mZoomOutAction = view->addAction(KStandardAction::ZoomOut); @@ -100,8 +100,10 @@ struct DocumentViewControllerPrivate return; } + // from mZoomWidget to mView QObject::connect(mZoomWidget, &ZoomWidget::zoomChanged, mView, &DocumentView::setZoom); + // from mView to mZoomWidget QObject::connect(mView, &DocumentView::minimumZoomChanged, mZoomWidget, &ZoomWidget::setMinimumZoom); QObject::connect(mView, &DocumentView::zoomChanged, mZoomWidget, &ZoomWidget::setZoom); @@ -165,10 +167,13 @@ void DocumentViewController::setView(DocumentView* view) connect(d->mView, &DocumentView::zoomToFillChanged, this, &DocumentViewController::updateZoomToFillActionFromView); connect(d->mView, &DocumentView::currentToolChanged, this, &DocumentViewController::updateTool); - connect(d->mZoomToFitAction, &QAction::triggered, d->mView, &DocumentView::toggleZoomToFit); - connect(d->mZoomToFillAction, &QAction::triggered, d->mView, &DocumentView::toggleZoomToFill); - connect(d->mActualSizeAction, SIGNAL(triggered()), - d->mView, SLOT(zoomActualSize())); + connect(d->mZoomToFitAction, &QAction::toggled, d->mView, &DocumentView::setZoomToFit); + connect(d->mZoomToFillAction, &QAction::toggled, d->mView, &DocumentView::setZoomToFill); + connect(d->mActualSizeAction, &QAction::toggled, d->mView, [this](bool checked){ + if (checked) { + d->mView->setZoom(1.0); + } + }); connect(d->mZoomInAction, SIGNAL(triggered()), d->mView, SLOT(zoomIn())); connect(d->mZoomOutAction, SIGNAL(triggered()), diff --git a/lib/zoomcombobox/zoomcombobox.cpp b/lib/zoomcombobox/zoomcombobox.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6c1bd98ad865cfe18910eb5bb1ec2ca56838e37a --- /dev/null +++ b/lib/zoomcombobox/zoomcombobox.cpp @@ -0,0 +1,321 @@ +// SPDX-FileCopyrightText: 2021 Noah Davis +// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL + +#include "zoomcombobox.h" +#include "zoomcombobox_p.h" + +#include +#include +#include +#include +#include +#include + +using namespace Gwenview; + +ZoomValidator::ZoomValidator(qreal minimum, qreal maximum, ZoomComboBox *q, ZoomComboBoxPrivate *d, QWidget* parent) + : QValidator(parent) + , m_minimum(minimum) + , m_maximum(maximum) + , m_zoomComboBox(q) + , m_zoomComboBoxPrivate(d) +{ +} + +ZoomValidator::~ZoomValidator() noexcept +{ +} + +qreal ZoomValidator::minimum() const +{ + return m_minimum; +} + +void ZoomValidator::setMinimum(const qreal minimum) +{ + if (m_minimum == minimum) { + return; + } + m_minimum = minimum; + Q_EMIT changed(); +} + +qreal ZoomValidator::maximum() const +{ + return m_maximum; +} + +void ZoomValidator::setMaximum(const qreal maximum) +{ + if (m_maximum == maximum) { + return; + } + m_maximum = maximum; + Q_EMIT changed(); +} + +QValidator::State ZoomValidator::validate(QString& input, int& pos) const +{ + Q_UNUSED(pos) + if (m_zoomComboBox->findText(input, Qt::MatchFixedString) > -1) { + return QValidator::Acceptable; + } + + QString copy = input.trimmed(); + copy.remove(locale().groupSeparator()); + copy.remove(locale().percent()); + const bool startsWithNumber = copy.constBegin()->isNumber(); + + if (startsWithNumber || copy.isEmpty()) { + return QValidator::Intermediate; + } + + QValidator::State state; + bool ok = false; + int value = locale().toInt(copy, &ok); + if (!ok || value < std::ceil(m_minimum * 100) || value > std::floor(m_maximum * 100)) { + state = QValidator::Intermediate; + } else { + state = QValidator::Acceptable; + } + return state; +} + +ZoomComboBoxPrivate::ZoomComboBoxPrivate(ZoomComboBox *q) + : q_ptr(q) + , validator(new ZoomValidator(0, 0, q, this)) +{ +} + +ZoomComboBox::ZoomComboBox(QWidget* parent) + : QComboBox(parent) + , d_ptr(new ZoomComboBoxPrivate(this)) +{ + Q_D(ZoomComboBox); + + d->validator->setObjectName(QLatin1String("zoomValidator")); + setValidator(d->validator); + + setEditable(true); + setInsertPolicy(QComboBox::NoInsert); + // QLocale::percent() will return a QString in Qt 6. + // Qt encourages using QString(locale().percent()) in QLocale documentation. + int percentLength = QString(locale().percent()).length(); + setMinimumContentsLength(locale().toString(9999).length() + percentLength); + + connect(lineEdit(), &QLineEdit::textEdited, this, [this, d](const QString &text){ + const bool startsWithNumber = text.constBegin()->isNumber(); + int matches = 0; + for (int i = 0; i < count(); ++i) { + if (itemText(i).startsWith(text, Qt::CaseInsensitive)) { + matches += 1; + } + } + Qt::MatchFlags matchFlags = startsWithNumber || matches > 1 ? Qt::MatchFixedString : Qt::MatchStartsWith; + const int textIndex = findText(text, matchFlags); + if (textIndex < 0) { + setValue(valueFromText(text)); + } + activateAndChangeZoomTo(textIndex); + lineEdit()->setCursorPosition(lineEdit()->cursorPosition() - 1); + }); + connect(this, qOverload(&ZoomComboBox::highlighted), + this, &ZoomComboBox::changeZoomTo); + view()->installEventFilter(this); + connect(this, qOverload(&ZoomComboBox::activated), + this, &ZoomComboBox::activateAndChangeZoomTo); +} + +ZoomComboBox::~ZoomComboBox() noexcept +{ +} + +void ZoomComboBox::setActions(QAction* zoomToFitAction, QAction* zoomToFillAction, QAction* actualSizeAction) +{ + Q_D(ZoomComboBox); + d->setActions(zoomToFitAction, zoomToFillAction, actualSizeAction); + + connect(zoomToFitAction, &QAction::toggled, + this, &ZoomComboBox::updateDisplayedText); + connect(zoomToFillAction, &QAction::toggled, + this, &ZoomComboBox::updateDisplayedText); +} + +void ZoomComboBoxPrivate::setActions(QAction* zoomToFitAction, QAction* zoomToFillAction, QAction* actualSizeAction) +{ + Q_Q(ZoomComboBox); + q->clear(); + q->addItem(zoomToFitAction->iconText(), QVariant::fromValue(zoomToFitAction)); // index = 0 + q->addItem(zoomToFillAction->iconText(), QVariant::fromValue(zoomToFillAction)); // index = 1 + q->addItem(actualSizeAction->iconText(), QVariant::fromValue(actualSizeAction)); // index = 2 + + mZoomToFitAction = zoomToFitAction; + mZoomToFillAction = zoomToFillAction; + mActualSizeAction = actualSizeAction; +} + +qreal ZoomComboBox::value() const +{ + Q_D(const ZoomComboBox); + return d->value; +} + +void ZoomComboBox::setValue(qreal value) +{ + Q_D(ZoomComboBox); + d->value = value; + + updateDisplayedText(); +} + +qreal ZoomComboBox::minimum() const +{ + Q_D(const ZoomComboBox); + return d->validator->minimum(); +} + +void ZoomComboBox::setMinimum(qreal minimum) +{ + Q_D(ZoomComboBox); + if (this->minimum() == minimum) { + return; + } + d->validator->setMinimum(minimum); + setValue(qMax(minimum, d->value)); + // Generate zoom presets below 100% + // FIXME: combobox value gets reset to last index value when this code runs + const int zoomToFillActionIndex = findData(QVariant::fromValue(d->mZoomToFillAction)); + const int actualSizeActionIndex = findData(QVariant::fromValue(d->mActualSizeAction)); + for (int i = actualSizeActionIndex - 1; i > zoomToFillActionIndex; --i) { + removeItem(i); + } + qreal value = minimum; + for (int i = zoomToFillActionIndex + 1; value < 1.0; ++i) { + insertItem(i, textFromValue(value), QVariant::fromValue(value)); + value *= 2; + } +} + +qreal ZoomComboBox::maximum() const +{ + Q_D(const ZoomComboBox); + return d->validator->maximum(); +} + +void ZoomComboBox::setMaximum(qreal maximum) +{ + Q_D(ZoomComboBox); + if (this->maximum() == maximum) { + return; + } + d->validator->setMaximum(maximum); + setValue(qMin(d->value, maximum)); + // Generate zoom presets above 100% + // NOTE: This probably has the same problem as setMinimum(), + // but the problem is never enountered since max zoom doesn't actually change + const int actualSizeActionIndex = findData(QVariant::fromValue(d->mActualSizeAction)); + const int count = this->count(); + for(int i = actualSizeActionIndex + 1; i < count; ++i) { + removeItem(i); + } + qreal value = 2.0; + while (value < maximum) { + addItem(textFromValue(value), QVariant::fromValue(value)); + value *= 2; + } + if (value >= maximum) { + addItem(textFromValue(maximum), QVariant::fromValue(maximum)); + } +} + +qreal ZoomComboBox::valueFromText(const QString& text, bool *ok) const +{ + Q_D(const ZoomComboBox); + QString copy = text; + copy.remove(locale().groupSeparator()); + copy.remove(locale().percent()); + return qreal(locale().toInt(copy, ok)) / 100.0; +} + +QString ZoomComboBox::textFromValue(const qreal value) const +{ + Q_D(const ZoomComboBox); + QString text = locale().toString(qRound(value * 100)); + d->validator->fixup(text); + text.remove(locale().groupSeparator()); + return text.append(locale().percent()); +} + +void ZoomComboBox::updateDisplayedText() +{ + Q_D(ZoomComboBox); + if (d->mZoomToFitAction->isChecked()) { + lineEdit()->setText(d->mZoomToFitAction->iconText()); + } else if (d->mZoomToFillAction->isChecked()) { + lineEdit()->setText(d->mZoomToFillAction->iconText()); + } else if (d->mActualSizeAction->isChecked()) { + lineEdit()->setText(d->mActualSizeAction->iconText()); + } else { + const QString currentZoomValueText(textFromValue(d->value)); + lineEdit()->setText(currentZoomValueText); + d->lastSelectedIndex = -1; + d->lastCustomZoomValue = d->value; + } +} + +bool ZoomComboBox::eventFilter(QObject *watched, QEvent *event) +{ + if (event->type() == QEvent::Hide) { + Q_D(ZoomComboBox); + changeZoomTo(d->lastSelectedIndex); + } + return false; +} + +void ZoomComboBox::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton){ + Q_D(ZoomComboBox); + if (d->mZoomToFitAction->isChecked()) { + setCurrentIndex(0); + d->lastSelectedIndex = 0; + } else if (d->mZoomToFillAction->isChecked()) { + setCurrentIndex(1); + d->lastSelectedIndex = 1; + } else if (d->mActualSizeAction->isChecked()) { + setCurrentIndex(2); + d->lastSelectedIndex = 2; + } else { + d->lastSelectedIndex = -1; + } + } + QComboBox::mousePressEvent(event); +} + + +void ZoomComboBox::changeZoomTo(int index) { + if (index < 0) { + Q_D(ZoomComboBox); + Q_EMIT zoomChanged(d->lastCustomZoomValue); + return; + } + + QVariant itemData = this->itemData(index); + QAction *action = itemData.value(); + if (action) { + if (action->isCheckable()) { + action->setChecked(true); + } else { + action->trigger(); + } + } else if (itemData.canConvert(QMetaType::QReal)) { + Q_EMIT zoomChanged(itemData.toReal()); + } +} + +void ZoomComboBox::activateAndChangeZoomTo(int index) +{ + Q_D(ZoomComboBox); + d->lastSelectedIndex = index; + changeZoomTo(index); +} diff --git a/lib/zoomcombobox/zoomcombobox.h b/lib/zoomcombobox/zoomcombobox.h new file mode 100644 index 0000000000000000000000000000000000000000..b8703dfa5d800311f6a133d006edc66e4a7797fc --- /dev/null +++ b/lib/zoomcombobox/zoomcombobox.h @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: 2021 Noah Davis +// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL + +#ifndef GWENVIEW_ZOOMCOMBOBOX_H +#define GWENVIEW_ZOOMCOMBOBOX_H + +#include +#include + +namespace Gwenview { + +class ZoomComboBoxPrivate; + +/** + * QComboBox subclass designed to be somewhat similar to QSpinBox. + * Allows the user to use non-integer combobox list items, + * but only accepts integers as custom input. + * + * This class is structured in a way so that changes to the zoom done through + * user interaction are signalled to the outside. On the other hand changes + * done to the visual state of this class without user interaction will not + * lead to emitted zoom changes/signals from this class. + */ +class GWENVIEWLIB_EXPORT ZoomComboBox : public QComboBox +{ + Q_OBJECT + Q_PROPERTY(qreal value READ value WRITE setValue) + Q_PROPERTY(qreal minimum READ minimum WRITE setMinimum) + Q_PROPERTY(qreal maximum READ maximum WRITE setMaximum) + +public: + explicit ZoomComboBox(QWidget *parent = nullptr); + ~ZoomComboBox() override; + + void setActions(QAction *zoomToFitAction, QAction *zoomToFillAction, QAction *actualSizeAction); + + qreal value() const; + /** + * Called when the value that is being displayed should change. + * Calling this method doesn't affect the zoom of the currently viewed image. + */ + void setValue(const qreal value); + + qreal minimum() const; + void setMinimum(const qreal minimum); + + qreal maximum() const; + void setMaximum(const qreal maximum); + + /// Gets an integer value from text. + qreal valueFromText(const QString &text, bool *ok = nullptr) const; + + /// Gets appropriately decorated text from an integer value. + QString textFromValue(const qreal value) const; + + void updateDisplayedText(); + +Q_SIGNALS: + void zoomChanged(qreal zoom); + +protected: + bool eventFilter(QObject *watched, QEvent *event) override; + + void mousePressEvent(QMouseEvent *event) override; + +private Q_SLOTS: + /** + * This method changes the zoom mode or value of the currently displayed image in response to + * user interaction with the Combobox' dropdown menu. + * @param index of the zoom mode or value that the user interacted with. + */ + void changeZoomTo(int index); + + /** + * Sets the index as the fallback when the popup menu is closed without selection in the + * future and then calls changeZoomTo(). + * @see changeZoomTo() + */ + void activateAndChangeZoomTo(int index); + +private: + const std::unique_ptr d_ptr; + Q_DECLARE_PRIVATE(ZoomComboBox) + Q_DISABLE_COPY(ZoomComboBox) +}; + +} + +#endif // GWENVIEW_ZOOMCOMBOBOX_H diff --git a/lib/zoomcombobox/zoomcombobox_p.h b/lib/zoomcombobox/zoomcombobox_p.h new file mode 100644 index 0000000000000000000000000000000000000000..170878f9c7de79c6730f45eeb5e8550a752b76a9 --- /dev/null +++ b/lib/zoomcombobox/zoomcombobox_p.h @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: 2021 Noah Davis +// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL + +#ifndef GWENVIEW_ZOOMCOMBOBOX_P_H +#define GWENVIEW_ZOOMCOMBOBOX_P_H + +#include "zoomcombobox.h" + +namespace Gwenview { + +class ZoomValidator : public QValidator +{ + Q_OBJECT + Q_PROPERTY(qreal minimum READ minimum WRITE setMinimum NOTIFY changed) + Q_PROPERTY(qreal maximum READ maximum WRITE setMaximum NOTIFY changed) +public: + explicit ZoomValidator(qreal minimum, qreal maximum, ZoomComboBox *q, ZoomComboBoxPrivate *d, QWidget* parent = nullptr); + ~ZoomValidator() override; + + qreal minimum() const; + void setMinimum(const qreal minimum); + + qreal maximum() const; + void setMaximum(const qreal maximum); + + QValidator::State validate(QString &input, int &pos) const override; + +private: + qreal m_minimum; + qreal m_maximum; + ZoomComboBox *m_zoomComboBox; + ZoomComboBoxPrivate *m_zoomComboBoxPrivate; + Q_DISABLE_COPY(ZoomValidator) +}; + +class ZoomComboBoxPrivate +{ + Q_DECLARE_PUBLIC(ZoomComboBox) + +public: + ZoomComboBoxPrivate(ZoomComboBox *q); + + void setActions(QAction *zoomToFitAction, QAction *zoomToFillAction, QAction *actualSizeAction); + +public: + ZoomComboBox *const q_ptr; + + QAction *mZoomToFitAction = nullptr; + QAction *mZoomToFillAction = nullptr; + QAction *mActualSizeAction = nullptr; + + qreal value = 1.0; + ZoomValidator *validator = nullptr; + int lastSelectedIndex = 0; + qreal lastCustomZoomValue = 1.0; +}; + +} + +#endif // GWENVIEW_ZOOMCOMBOBOX_P_H diff --git a/lib/zoomwidget.cpp b/lib/zoomwidget.cpp index 9fc2d5343661f665788f885316182130fe325eb4..e9740e809b27403ed3dda8d0905c3b4ce471ee7d 100644 --- a/lib/zoomwidget.cpp +++ b/lib/zoomwidget.cpp @@ -26,16 +26,14 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA // Qt #include -#include -#include #include #include // KF // Local -#include "zoomslider.h" #include "signalblocker.h" -#include "statusbartoolbutton.h" +#include "zoomcombobox/zoomcombobox.h" +#include "zoomslider.h" namespace Gwenview { @@ -57,14 +55,9 @@ struct ZoomWidgetPrivate { ZoomWidget* q; - StatusBarToolButton* mZoomToFitButton; - StatusBarToolButton* mActualSizeButton; - StatusBarToolButton* mZoomToFillButton; ZoomSlider* mZoomSlider; - QSpinBox* mZoomSpinBox; - QAction* mZoomToFitAction; - QAction* mActualSizeAction; - QAction* mZoomToFillAction; + ZoomComboBox* mZoomComboBox; + QAction* mActualSizeAction = nullptr; bool mZoomUpdatedBySlider; @@ -89,45 +82,22 @@ ZoomWidget::ZoomWidget(QWidget* parent) setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); - d->mZoomToFitButton = new StatusBarToolButton; - d->mActualSizeButton = new StatusBarToolButton; - d->mZoomToFillButton = new StatusBarToolButton; - d->mZoomToFitButton->setCheckable(true); - d->mActualSizeButton->setCheckable(true); - d->mZoomToFillButton->setCheckable(true); - d->mZoomToFitButton->setChecked(true); - - if (QApplication::isLeftToRight()) { - d->mZoomToFitButton->setGroupPosition(StatusBarToolButton::GroupLeft); - d->mZoomToFillButton->setGroupPosition(StatusBarToolButton::GroupCenter); - d->mActualSizeButton->setGroupPosition(StatusBarToolButton::GroupRight); - } else { - d->mActualSizeButton->setGroupPosition(StatusBarToolButton::GroupLeft); - d->mZoomToFillButton->setGroupPosition(StatusBarToolButton::GroupCenter); - d->mZoomToFitButton->setGroupPosition(StatusBarToolButton::GroupRight); - } - d->mZoomSlider = new ZoomSlider; d->mZoomSlider->setMinimumWidth(150); d->mZoomSlider->slider()->setSingleStep(int(PRECISION)); d->mZoomSlider->slider()->setPageStep(3 * int(PRECISION)); connect(d->mZoomSlider->slider(), &QAbstractSlider::actionTriggered, this, &ZoomWidget::slotZoomSliderActionTriggered); - d->mZoomSpinBox = new QSpinBox; - d->mZoomSpinBox->setFixedWidth(d->mZoomSpinBox->fontMetrics().boundingRect(QStringLiteral(" 1000% ")).width()); - d->mZoomSpinBox->setAlignment(Qt::AlignCenter); - d->mZoomSpinBox->setSuffix(QLatin1String("%")); - connect(d->mZoomSpinBox, QOverload::of(&QSpinBox::valueChanged), [=](int i){ setCustomZoomFromSpinBox(static_cast(i)); }); + d->mZoomComboBox = new ZoomComboBox; + connect(d->mZoomComboBox, &ZoomComboBox::zoomChanged, + this, &ZoomWidget::zoomChanged); // Layout auto* layout = new QHBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); - layout->addWidget(d->mZoomToFitButton); - layout->addWidget(d->mZoomToFillButton); - layout->addWidget(d->mActualSizeButton); layout->addWidget(d->mZoomSlider); - layout->addWidget(d->mZoomSpinBox); + layout->addWidget(d->mZoomComboBox); } ZoomWidget::~ZoomWidget() @@ -137,28 +107,18 @@ ZoomWidget::~ZoomWidget() void ZoomWidget::setActions(QAction* zoomToFitAction, QAction* actualSizeAction, QAction* zoomInAction, QAction* zoomOutAction, QAction* zoomToFillAction) { - d->mZoomToFitAction = zoomToFitAction; - d->mActualSizeAction = actualSizeAction; - d->mZoomToFillAction = zoomToFillAction; - - d->mZoomToFitButton->setDefaultAction(zoomToFitAction); - d->mActualSizeButton->setDefaultAction(actualSizeAction); - d->mZoomToFillButton->setDefaultAction(zoomToFillAction); - d->mZoomSlider->setZoomInAction(zoomInAction); d->mZoomSlider->setZoomOutAction(zoomOutAction); + d->mZoomComboBox->setActions(zoomToFitAction, zoomToFillAction, actualSizeAction); + auto *actionGroup = new QActionGroup(d->q); - actionGroup->addAction(d->mZoomToFitAction); - actionGroup->addAction(d->mZoomToFillAction); - actionGroup->addAction(d->mActualSizeAction); - actionGroup->setExclusive(true); - - // Adjust sizes - int width = std::max({d->mZoomToFitButton->sizeHint().width(), d->mActualSizeButton->sizeHint().width(), d->mZoomToFillButton->sizeHint().width()}); - d->mZoomToFitButton->setFixedWidth(width); - d->mActualSizeButton->setFixedWidth(width); - d->mZoomToFillButton->setFixedWidth(width); + actionGroup->addAction(zoomToFitAction); + actionGroup->addAction(zoomToFillAction); + actionGroup->addAction(actualSizeAction); + actionGroup->setExclusionPolicy(QActionGroup::ExclusionPolicy::ExclusiveOptional); + + d->mActualSizeAction = actualSizeAction; } void ZoomWidget::slotZoomSliderActionTriggered() @@ -170,7 +130,10 @@ void ZoomWidget::slotZoomSliderActionTriggered() void ZoomWidget::setZoom(qreal zoom) { - d->mZoomSpinBox->setValue(qRound(zoom * PRECISION)); + if (zoom != 1.0) { + d->mActualSizeAction->setChecked(false); + } + d->mZoomComboBox->setValue(zoom); // Don't change slider value if we come here because the slider change, // avoids choppy sliding scroll. @@ -188,25 +151,16 @@ void ZoomWidget::setZoom(qreal zoom) } } -void ZoomWidget::setCustomZoomFromSpinBox(qreal zoom) -{ - setZoom(zoom / PRECISION); - if (d->mZoomSpinBox->hasFocus()) { - d->emitZoomChanged(); - } -} - void ZoomWidget::setMinimumZoom(qreal minimumZoom) { d->mZoomSlider->setMinimum(sliderValueForZoom(minimumZoom)); - d->mZoomSpinBox->setMinimum(qRound(minimumZoom * PRECISION)); - d->mZoomSpinBox->setValue(minimumZoom * PRECISION); + d->mZoomComboBox->setMinimum(minimumZoom); } void ZoomWidget::setMaximumZoom(qreal zoom) { d->mZoomSlider->setMaximum(sliderValueForZoom(zoom)); - d->mZoomSpinBox->setMaximum(qRound(zoom * PRECISION)); + d->mZoomComboBox->setMaximum(zoom); } } // namespace diff --git a/lib/zoomwidget.h b/lib/zoomwidget.h index 40e9efe75d0d87481e06eeb040d71dcf95d1a551..d911f4157a4529077251ba0a7dfa044cd0d61a83 100644 --- a/lib/zoomwidget.h +++ b/lib/zoomwidget.h @@ -46,6 +46,9 @@ public: void setActions(QAction* zoomToFitAction, QAction* actualSizeAction, QAction* zoomInAction, QAction* zoomOutAction, QAction* zoomToFillAction); public Q_SLOTS: + /** + * Called when the zoom value has been changed from outside the ZoomWidget. + */ void setZoom(qreal zoom); void setMinimumZoom(qreal zoom); @@ -56,7 +59,6 @@ Q_SIGNALS: private Q_SLOTS: void slotZoomSliderActionTriggered(); - void setCustomZoomFromSpinBox(qreal zoom); private: friend struct ZoomWidgetPrivate;