File indexing completed on 2024-05-19 04:36:39
0001 /* This file is part of the TikZKit project. 0002 * 0003 * Copyright (C) 2016 Dominik Haumann <dhaumann@kde.org> 0004 * 0005 * This library is free software; you can redistribute it and/or modify 0006 * it under the terms of the GNU Library General Public License as published 0007 * by the Free Software Foundation, either version 2 of the License, or 0008 * (at your option) any later version. 0009 * 0010 * This library is distributed in the hope that it will be useful, 0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0013 * GNU Library General Public License for more details. 0014 * 0015 * You should have received a copy of the GNU Library General Public License 0016 * along with this library; see the file COPYING.LIB. If not, see 0017 * <http://www.gnu.org/licenses/>. 0018 */ 0019 #include "ZoomController.h" 0020 0021 #include <QComboBox> 0022 #include <QLineEdit> 0023 #include <QPinchGesture> 0024 #include <QTimeLine> 0025 #include <QValidator> 0026 #include <QWheelEvent> 0027 0028 #include <cmath> 0029 0030 namespace { 0031 QString zoomToString(qreal zoom) 0032 { 0033 const qreal z = qRound(zoom * 10000.0) / 100.0; 0034 return QString(QLatin1String("%1 %")).arg(z); 0035 } 0036 } 0037 0038 namespace tikz { 0039 namespace ui { 0040 0041 ZoomController::ZoomController(QObject *parent) 0042 : QObject(parent) 0043 // NOTE: add pre-defined zoom entries here: 0044 , m_zoomFactors({ 0045 5.0, 0046 4.0, 0047 3.0, 0048 2.0, 0049 1.5, 0050 1.0, 0051 0.75, 0052 0.5, 0053 0.33, 0054 0.25, 0055 0.10}) 0056 , m_inputRegExp(QLatin1String("^\\s*((\\d+)(\\.\\d+)?)\\s*%?\\s*$")) 0057 { 0058 // zoom animation should take 100ms 0059 m_animationTimeLine = new QTimeLine(100, this); 0060 connect(m_animationTimeLine, &QTimeLine::valueChanged, this, &ZoomController::setZoomAnimated); 0061 connect(m_animationTimeLine, &QTimeLine::finished, this, &ZoomController::syncComboBox); 0062 } 0063 0064 ZoomController::~ZoomController() 0065 { 0066 } 0067 0068 void ZoomController::setZoom(qreal z) 0069 { 0070 if (z == m_currentZoom) { 0071 return; 0072 } 0073 0074 m_currentZoom = z; 0075 0076 syncComboBox(); 0077 0078 Q_EMIT zoomChanged(m_currentZoom); 0079 } 0080 0081 qreal ZoomController::currentZoom() const 0082 { 0083 return m_currentZoom; 0084 } 0085 0086 bool ZoomController::canZoomIn() const 0087 { 0088 return m_currentZoom < m_zoomFactors.first(); 0089 } 0090 0091 bool ZoomController::canZoomOut() const 0092 { 0093 return m_currentZoom > m_zoomFactors.back(); 0094 } 0095 0096 void ZoomController::processWheelEvent(int delta) 0097 { 0098 if (delta < 0) { 0099 zoomOut(true); 0100 } else if (delta >= 0) { 0101 zoomIn(true); 0102 } 0103 } 0104 0105 void ZoomController::zoomIn(bool animated) 0106 { 0107 if (m_animationTimeLine->state() == QTimeLine::Running) { 0108 return; 0109 } 0110 0111 for (int i = m_zoomFactors.count() - 1; i >= 0; --i) { 0112 if (m_zoomFactors[i] > m_currentZoom) { 0113 if (animated) { 0114 startZoomAnimation(m_currentZoom, m_zoomFactors[i]); 0115 } else { 0116 setZoom(m_zoomFactors[i]); 0117 } 0118 break; 0119 } 0120 } 0121 } 0122 0123 void ZoomController::zoomOut(bool animated) 0124 { 0125 if (m_animationTimeLine->state() == QTimeLine::Running) { 0126 return; 0127 } 0128 0129 for (qreal z : m_zoomFactors) { 0130 if (z < m_currentZoom) { 0131 if (animated) { 0132 startZoomAnimation(m_currentZoom, z); 0133 } else { 0134 setZoom(z); 0135 } 0136 break; 0137 } 0138 } 0139 } 0140 0141 void ZoomController::startZoomAnimation(qreal startZoom, qreal endZoom) 0142 { 0143 if (m_animationTimeLine->state() == QTimeLine::Running) { 0144 return; 0145 } 0146 0147 m_startZoom = startZoom; 0148 m_endZoom = endZoom; 0149 m_animationTimeLine->start(); 0150 } 0151 0152 void ZoomController::setZoomAnimated(qreal value) 0153 { 0154 setZoom((1.0 - value) * m_startZoom + value * m_endZoom); 0155 } 0156 0157 void ZoomController::resetZoom() 0158 { 0159 setZoom(1); 0160 } 0161 0162 void ZoomController::setZoomFactors(const QVector<qreal>& factors) 0163 { 0164 m_zoomFactors = factors; 0165 } 0166 0167 void ZoomController::attachToComboBox(QComboBox *comboBox) 0168 { 0169 if (m_comboBox == comboBox) { 0170 return; 0171 } 0172 0173 if (m_comboBox) { 0174 m_comboBox->removeEventFilter(this); 0175 m_comboBox->disconnect(this); 0176 if (m_comboBox->lineEdit()) { 0177 m_comboBox->lineEdit()->disconnect(this); 0178 } 0179 m_comboBox->setValidator(nullptr); 0180 } 0181 0182 m_comboBox = comboBox; 0183 0184 if (m_comboBox) { 0185 m_comboBox->clear(); 0186 for (qreal z : m_zoomFactors) { 0187 m_comboBox->addItem(zoomToString(z), z); 0188 } 0189 syncComboBox(); 0190 connect(m_comboBox, SIGNAL(activated(int)), 0191 this, SLOT(comboBoxActivated(int))); 0192 0193 m_comboBox->setEditable(true); 0194 m_comboBox->setInsertPolicy(QComboBox::NoInsert); 0195 connect(m_comboBox->lineEdit(), SIGNAL(editingFinished()), 0196 this, SLOT(comboBoxEdited())); 0197 0198 if (!m_inputValidator) { 0199 m_inputValidator = new QRegExpValidator(m_inputRegExp, this); 0200 } 0201 m_comboBox->setValidator(m_inputValidator); 0202 m_comboBox->installEventFilter(this); 0203 } 0204 } 0205 0206 void ZoomController::comboBoxActivated(int index) 0207 { 0208 startZoomAnimation(m_currentZoom, m_comboBox->itemData(index).toReal()); 0209 } 0210 0211 void ZoomController::comboBoxEdited() 0212 { 0213 if (m_animationTimeLine->state() == QTimeLine::Running) { 0214 return; 0215 } 0216 0217 const int index = m_inputRegExp.indexIn(m_comboBox->currentText()); 0218 Q_ASSERT(index >= 0); // validator ensures this 0219 0220 if (index >= 0) { 0221 const qreal z = qBound(m_zoomFactors.back(), 0222 qreal(m_inputRegExp.cap(1).toDouble() / 100.0), 0223 m_zoomFactors.first()); 0224 0225 setZoom(z); 0226 } 0227 } 0228 0229 void ZoomController::syncComboBox() 0230 { 0231 if (!m_comboBox || m_animationTimeLine->state() == QTimeLine::Running) { 0232 return; 0233 } 0234 0235 // Set current index to -1 for custom zooms 0236 const int index = m_comboBox->findData(m_currentZoom); 0237 m_comboBox->setCurrentIndex(index); 0238 m_comboBox->setEditText(zoomToString(m_currentZoom)); 0239 } 0240 0241 bool ZoomController::eventFilter(QObject *watched, QEvent *event) 0242 { 0243 // filter correct watched object 0244 if (!m_comboBox || m_comboBox != watched) { 0245 return QObject::eventFilter(watched, event); 0246 } 0247 0248 // we only want wheel events 0249 if (event->type() != QEvent::Wheel) { 0250 return QObject::eventFilter(watched, event); 0251 } 0252 0253 // if currentIndex() is != -1, then scrolling works as expected 0254 if (m_comboBox->currentIndex() != -1) { 0255 return QObject::eventFilter(watched, event); 0256 } 0257 0258 // we want vertical wheel events 0259 auto evWheel = static_cast<QWheelEvent*>(event); 0260 if (!evWheel || evWheel->angleDelta().y() > 0) { 0261 return QObject::eventFilter(watched, event); 0262 } 0263 0264 // zoom in/out 0265 if (evWheel->angleDelta().y() < 0) { 0266 zoomOut(true); 0267 } else if (evWheel->angleDelta().y() > 0) { 0268 zoomIn(true); 0269 } 0270 0271 return true; 0272 } 0273 0274 } 0275 }