88 lines
3.5 KiB
C++
88 lines
3.5 KiB
C++
#include "UI/NavigationToolHeader.h"
|
||
#include "UI/NumericScrub.h"
|
||
#include <QKeyEvent>
|
||
#include <QRegularExpression>
|
||
#include <cmath>
|
||
|
||
NavigationToolHeader::NavigationToolHeader(EditorSession &session, QWidget *parent)
|
||
: ToolHeaderBar(QString(), parent), m_session(session), m_zoom(new QLineEdit(this)), m_unit(new QLabel(QStringLiteral("%"), this))
|
||
{
|
||
m_zoom->setObjectName(QStringLiteral("zoomPercentage"));
|
||
m_zoom->setAccessibleName(QStringLiteral("Zoom percentage"));
|
||
m_zoom->setToolTip(QStringLiteral("Zoom percentage (0.1–3200%). Press Return to apply."));
|
||
m_zoom->setFixedWidth(72);
|
||
m_zoom->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||
m_zoom->installEventFilter(this);
|
||
// Swift's scrubbable unit: a drag sideways zooms.
|
||
new NumericScrub(m_unit, {.sensitivity = 1, .low = 0.1, .high = 3200, .step = std::nullopt,
|
||
.value = [this] { return m_session.viewport.zoom() * 100; }, .set = [this](double percent) { step(percent); }});
|
||
// The unit sits tight against its field: one value.
|
||
auto *value = new QHBoxLayout;
|
||
value->setSpacing(2);
|
||
value->addWidget(m_zoom);
|
||
value->addWidget(m_unit);
|
||
row->insertLayout(1, value);
|
||
connect(&m_session, &EditorSession::changed, this, &NavigationToolHeader::synchronize);
|
||
synchronize();
|
||
}
|
||
|
||
void NavigationToolHeader::synchronize()
|
||
{
|
||
const bool zooms = m_session.tool() == NavigationTool::zoom;
|
||
title->setText(zooms ? QStringLiteral("Zoom") : QStringLiteral("Pan"));
|
||
m_zoom->setVisible(zooms);
|
||
m_unit->setVisible(zooms);
|
||
m_zoom->setEnabled(m_session.document().has_value() && !m_session.showsBusy());
|
||
if (!m_zoom->hasFocus())
|
||
syncZoom();
|
||
}
|
||
|
||
void NavigationToolHeader::syncZoom()
|
||
{
|
||
static const QRegularExpression trailingZeros(QStringLiteral("\\.?0+$"));
|
||
m_displayed = QString::number(m_session.viewport.zoom() * 100, 'f', 2).remove(trailingZeros);
|
||
m_zoom->setText(m_displayed);
|
||
}
|
||
|
||
void NavigationToolHeader::applyZoom()
|
||
{
|
||
if (m_zoom->text() != m_displayed) {
|
||
// No number reads as zero; "inf" the viewport refuses.
|
||
const double value = m_zoom->text().trimmed().remove(QLatin1Char('%')).toDouble();
|
||
if (value > 0 && !m_session.isProjectBusy())
|
||
m_session.zoom(value / 100);
|
||
}
|
||
syncZoom();
|
||
}
|
||
|
||
void NavigationToolHeader::step(double percent)
|
||
{
|
||
m_zoom->setText(QString::number(std::clamp(percent, 0.1, 3200.0), 'g', 6));
|
||
applyZoom();
|
||
}
|
||
|
||
bool NavigationToolHeader::eventFilter(QObject *watched, QEvent *event)
|
||
{
|
||
if (event->type() == QEvent::FocusOut)
|
||
applyZoom();
|
||
if (event->type() != QEvent::KeyPress)
|
||
return ToolHeaderBar::eventFilter(watched, event);
|
||
const auto *key = static_cast<QKeyEvent *>(event);
|
||
// Return and Escape apply, then hand the canvas the keys.
|
||
if (key->key() == Qt::Key_Return || key->key() == Qt::Key_Enter || key->key() == Qt::Key_Escape) {
|
||
applyZoom();
|
||
m_zoom->clearFocus();
|
||
m_session.requestCanvasFocus();
|
||
return true;
|
||
}
|
||
if (key->key() != Qt::Key_Up && key->key() != Qt::Key_Down)
|
||
return ToolHeaderBar::eventFilter(watched, event);
|
||
// Up and Down step one percent, ten with Shift.
|
||
static const QRegularExpression notNumeric(QStringLiteral("[^0-9.]"));
|
||
bool isNumber = false;
|
||
const double typed = m_zoom->text().remove(notNumeric).toDouble(&isNumber);
|
||
const double amount = (key->modifiers() & Qt::ShiftModifier ? 10 : 1) * (key->key() == Qt::Key_Up ? 1 : -1);
|
||
step((isNumber ? typed : m_session.viewport.zoom() * 100) + amount);
|
||
return true;
|
||
}
|