File indexing completed on 2024-05-19 04:29:36
0001 /* This file is part of the KDE libraries 0002 SPDX-FileCopyrightText: 2004 Ariya Hidayat <ariya@kde.org> 0003 SPDX-FileCopyrightText: 2006 Peter Simonsson <peter.simonsson@gmail.com> 0004 SPDX-FileCopyrightText: 2006-2007 C. Boemann <cbo@boemann.dk> 0005 0006 SPDX-License-Identifier: LGPL-2.0-only 0007 */ 0008 #include "KoZoomAction.h" 0009 #include "KoZoomMode.h" 0010 #include "KoZoomWidget.h" 0011 0012 #include <KoIcon.h> 0013 0014 #include <QString> 0015 #include <QLocale> 0016 #include <QStringList> 0017 #include <QRegExp> 0018 #include <QList> 0019 #include <QSlider> 0020 #include <QLineEdit> 0021 #include <QToolButton> 0022 #include <QLabel> 0023 #include <QGridLayout> 0024 #include <QMenu> 0025 #include <QStatusBar> 0026 #include <QButtonGroup> 0027 #include <QComboBox> 0028 0029 0030 #include <klocalizedstring.h> 0031 #include <WidgetsDebug.h> 0032 #include <kis_signal_compressor.h> 0033 #include <ksharedconfig.h> 0034 #include <kconfiggroup.h> 0035 #include "krita_container_utils.h" 0036 0037 #include <math.h> 0038 0039 class Q_DECL_HIDDEN KoZoomAction::Private 0040 { 0041 public: 0042 0043 Private(KoZoomAction *_parent) 0044 : parent(_parent) 0045 , minimumZoomValue(-1) 0046 , maximumZoomValue(-1) 0047 , guiUpdateCompressor(200, KisSignalCompressor::FIRST_ACTIVE) 0048 {} 0049 0050 KoZoomAction *parent {nullptr}; 0051 0052 KoZoomMode::Modes zoomModes; 0053 QList<qreal> sliderLookup; 0054 0055 qreal effectiveZoom {0.0}; 0056 0057 QList<qreal> generateSliderZoomLevels() const; 0058 QList<qreal> filterMenuZoomLevels(const QList<qreal> &zoomLevels) const; 0059 0060 qreal minimumZoomValue {0.0}; 0061 qreal maximumZoomValue {0.0}; 0062 0063 KisSignalCompressor guiUpdateCompressor; 0064 }; 0065 0066 QList<qreal> KoZoomAction::Private::generateSliderZoomLevels() const 0067 { 0068 KConfigGroup config = KSharedConfig::openConfig()->group(""); 0069 int steps = config.readEntry("zoomSteps", 2); 0070 qreal k = steps / M_LN2; 0071 0072 int first = ceil(log(parent->minimumZoom()) * k); 0073 int size = floor(log(parent->maximumZoom()) * k) - first + 1; 0074 QVector<qreal> zoomLevels(size); 0075 0076 // enforce zoom levels relating to thirds (33.33%, 66.67%, ...) 0077 QVector<qreal> snap(steps); 0078 if (steps > 1) { 0079 qreal third = log(4./ 3.) * k; 0080 int i = round(third); 0081 snap[(i - first) % steps] = third - i; 0082 } 0083 0084 k = 1./ k; 0085 for (int i = 0; i < steps; i++) { 0086 qreal f = exp((i + first + snap[i]) * k); 0087 f = floor(f * 0x1p48 + 0.5) / 0x1p48; // round off inaccuracies 0088 for (int j = i; j < size; j += steps, f *= 2.) { 0089 zoomLevels[j] = f; 0090 } 0091 } 0092 0093 return QList<qreal>::fromVector(zoomLevels); 0094 } 0095 0096 QList<qreal> KoZoomAction::Private::filterMenuZoomLevels(const QList<qreal> &zoomLevels) const 0097 { 0098 QList<qreal> filteredZoomLevels; 0099 0100 Q_FOREACH (qreal zoom, zoomLevels) { 0101 if (zoom >= 0.2 && zoom <= 10) { 0102 filteredZoomLevels << zoom; 0103 } 0104 } 0105 0106 return filteredZoomLevels; 0107 } 0108 0109 KoZoomAction::KoZoomAction(KoZoomMode::Modes zoomModes, const QString& text, QObject *parent) 0110 : KSelectAction(text, parent) 0111 , d(new Private(this)) 0112 { 0113 d->zoomModes = zoomModes; 0114 setIcon(koIcon("zoom-original")); 0115 setEditable( true ); 0116 setMaxComboViewCount( 15 ); 0117 0118 d->sliderLookup = d->generateSliderZoomLevels(); 0119 0120 d->effectiveZoom = 1.0; 0121 regenerateItems(d->effectiveZoom); 0122 0123 connect( this, SIGNAL(triggered(QString)), SLOT(triggered(QString)) ); 0124 connect(&d->guiUpdateCompressor, SIGNAL(timeout()), SLOT(slotUpdateGuiAfterZoom())); 0125 } 0126 0127 KoZoomAction::~KoZoomAction() 0128 { 0129 delete d; 0130 } 0131 0132 qreal KoZoomAction::effectiveZoom() const 0133 { 0134 return d->effectiveZoom; 0135 } 0136 0137 void KoZoomAction::setZoom(qreal zoom) 0138 { 0139 setEffectiveZoom(zoom); 0140 } 0141 0142 void KoZoomAction::triggered(const QString& text) 0143 { 0144 QString zoomString = text; 0145 zoomString = zoomString.remove( '&' ); 0146 0147 KoZoomMode::Mode mode = KoZoomMode::toMode(zoomString); 0148 double zoom = 0; 0149 0150 if( mode == KoZoomMode::ZOOM_CONSTANT ) { 0151 bool ok; 0152 QRegExp regexp( ".*(\\d+(\\.\\d+)?).*" ); // "Captured" non-empty sequence of digits 0153 int pos = regexp.indexIn( zoomString ); 0154 if( pos > -1 ) { 0155 zoom = regexp.cap( 1 ).toDouble( &ok ); 0156 0157 if( !ok ) { 0158 zoom = 0; 0159 } 0160 } 0161 } 0162 0163 emit zoomChanged( mode, zoom/100.0 ); 0164 } 0165 0166 void KoZoomAction::regenerateItems(const qreal zoom) 0167 { 0168 // TODO: refactor this method to become less slow, then 0169 // we could reduce the timeout of d->guiUpdateCompressor 0170 // to at least 80ms (12.5fps) 0171 0172 QList<qreal> zoomLevels = d->filterMenuZoomLevels(d->sliderLookup); 0173 0174 if( !zoomLevels.contains( zoom ) ) 0175 zoomLevels << zoom; 0176 0177 std::sort(zoomLevels.begin(), zoomLevels.end()); 0178 0179 // update items with new sorted zoom values 0180 QStringList values; 0181 if(d->zoomModes & KoZoomMode::ZOOM_PAGE) { 0182 values << KoZoomMode::toString(KoZoomMode::ZOOM_PAGE); 0183 } 0184 if(d->zoomModes & KoZoomMode::ZOOM_WIDTH) { 0185 values << KoZoomMode::toString(KoZoomMode::ZOOM_WIDTH); 0186 } 0187 if(d->zoomModes & KoZoomMode::ZOOM_HEIGHT) { 0188 values << KoZoomMode::toString(KoZoomMode::ZOOM_HEIGHT); 0189 } 0190 0191 Q_FOREACH (qreal value, zoomLevels) { 0192 const qreal valueInPercent = value * 100; 0193 const int precision = (value > 10.0) ? 0 : 1; 0194 0195 values << i18n("%1%", QLocale().toString(valueInPercent, 'f', precision)); 0196 } 0197 0198 setItems( values ); 0199 0200 emit zoomLevelsChanged(values); 0201 0202 { 0203 const qreal zoomInPercent = zoom * 100; 0204 const int precision = (zoom > 10.0) ? 0 : 1; 0205 0206 const QString valueString = i18n("%1%", QLocale().toString(zoomInPercent, 'f', precision)); 0207 0208 setCurrentAction(valueString); 0209 0210 emit currentZoomLevelChanged(valueString); 0211 } 0212 } 0213 0214 void KoZoomAction::sliderValueChanged(int value) 0215 { 0216 if (value < d->sliderLookup.size()) { 0217 setZoom(d->sliderLookup[value]); 0218 emit zoomChanged(KoZoomMode::ZOOM_CONSTANT, d->sliderLookup[value]); 0219 } 0220 } 0221 0222 qreal KoZoomAction::nextZoomLevel() const 0223 { 0224 const qreal eps = 1e-5; 0225 int i = 0; 0226 while (i < d->sliderLookup.size() - 1 && d->effectiveZoom > d->sliderLookup[i] - eps) { 0227 i++; 0228 } 0229 0230 return qMax(d->effectiveZoom, d->sliderLookup[i]); 0231 } 0232 0233 qreal KoZoomAction::prevZoomLevel() const 0234 { 0235 const qreal eps = 1e-5; 0236 int i = d->sliderLookup.size() - 1; 0237 while (i > 0 && d->effectiveZoom < d->sliderLookup[i] + eps) i--; 0238 0239 return qMin(d->effectiveZoom, d->sliderLookup[i]); 0240 } 0241 0242 void KoZoomAction::zoomIn() 0243 { 0244 qreal zoom = nextZoomLevel(); 0245 0246 if (zoom > d->effectiveZoom) { 0247 setZoom(zoom); 0248 emit zoomChanged(KoZoomMode::ZOOM_CONSTANT, d->effectiveZoom); 0249 } 0250 } 0251 0252 void KoZoomAction::zoomOut() 0253 { 0254 qreal zoom = prevZoomLevel(); 0255 0256 if (zoom < d->effectiveZoom) { 0257 setZoom(zoom); 0258 emit zoomChanged(KoZoomMode::ZOOM_CONSTANT, d->effectiveZoom); 0259 } 0260 } 0261 0262 QWidget * KoZoomAction::createWidget(QWidget *parent) 0263 { 0264 KoZoomWidget* zoomWidget = new KoZoomWidget(parent, d->sliderLookup.size() - 1); 0265 0266 connect(this, SIGNAL(zoomLevelsChanged(QStringList)), zoomWidget, SLOT(setZoomLevels(QStringList))); 0267 connect(this, SIGNAL(sliderZoomLevelsChanged(int)), zoomWidget, SLOT(setSliderSize(int))); 0268 connect(this, SIGNAL(currentZoomLevelChanged(QString)), zoomWidget, SLOT(setCurrentZoomLevel(QString))); 0269 connect(this, SIGNAL(sliderChanged(int)), zoomWidget, SLOT(setSliderValue(int))); 0270 connect(this, SIGNAL(canvasMappingModeChanged(bool)), zoomWidget, SLOT(setCanvasMappingMode(bool))); 0271 0272 connect(zoomWidget, SIGNAL(sliderValueChanged(int)), this, SLOT(sliderValueChanged(int))); 0273 connect(zoomWidget, SIGNAL(zoomLevelChanged(QString)), this, SLOT(triggered(QString))); 0274 connect(zoomWidget, SIGNAL(canvasMappingModeChanged(bool)), this, SIGNAL(canvasMappingModeChanged(bool))); 0275 connect(zoomWidget, SIGNAL(zoomedToSelection()), this, SIGNAL(zoomedToSelection())); 0276 connect(zoomWidget, SIGNAL(zoomedToAll()), this, SIGNAL(zoomedToAll())); 0277 regenerateItems(d->effectiveZoom); 0278 syncSliderWithZoom(); 0279 return zoomWidget; 0280 } 0281 0282 void KoZoomAction::setEffectiveZoom(qreal zoom) 0283 { 0284 if(d->effectiveZoom == zoom) 0285 return; 0286 0287 zoom = clampZoom(zoom); 0288 d->effectiveZoom = zoom; 0289 d->guiUpdateCompressor.start(); 0290 } 0291 0292 void KoZoomAction::slotUpdateGuiAfterZoom() 0293 { 0294 syncSliderWithZoom(); 0295 0296 // TODO: don't regenerate when only mode changes 0297 regenerateItems(d->effectiveZoom); 0298 } 0299 0300 void KoZoomAction::slotUpdateZoomLevels() 0301 { 0302 qreal currentZoom = d->effectiveZoom; 0303 d->sliderLookup = d->generateSliderZoomLevels(); 0304 regenerateItems(currentZoom); 0305 syncSliderWithZoom(); 0306 0307 emit sliderZoomLevelsChanged(d->sliderLookup.size() - 1); 0308 0309 } 0310 0311 void KoZoomAction::setSelectedZoomMode(KoZoomMode::Mode mode) 0312 { 0313 QString modeString(KoZoomMode::toString(mode)); 0314 setCurrentAction(modeString); 0315 0316 emit currentZoomLevelChanged(modeString); 0317 } 0318 0319 void KoZoomAction::setCanvasMappingMode(bool status) 0320 { 0321 emit canvasMappingModeChanged(status); 0322 } 0323 0324 void KoZoomAction::syncSliderWithZoom() 0325 { 0326 const qreal eps = 1e-5; 0327 int i = d->sliderLookup.size() - 1; 0328 while (d->effectiveZoom < d->sliderLookup[i] + eps && i > 0) i--; 0329 0330 emit sliderChanged(i); 0331 } 0332 0333 qreal KoZoomAction::minimumZoom() 0334 { 0335 if (d->minimumZoomValue < 0) { 0336 return KoZoomMode::minimumZoom(); 0337 } 0338 return d->minimumZoomValue; 0339 } 0340 0341 qreal KoZoomAction::maximumZoom() 0342 { 0343 if (d->maximumZoomValue < 0) { 0344 return KoZoomMode::maximumZoom(); 0345 } 0346 return d->maximumZoomValue; 0347 } 0348 0349 qreal KoZoomAction::clampZoom(qreal zoom) 0350 { 0351 return qMin(maximumZoom(), qMax(minimumZoom(), zoom)); 0352 } 0353 0354 void KoZoomAction::setMinimumZoom(qreal zoom) 0355 { 0356 Q_ASSERT(zoom > 0.0f); 0357 KoZoomMode::setMinimumZoom(zoom); 0358 d->minimumZoomValue = zoom; 0359 d->sliderLookup = d->generateSliderZoomLevels(); 0360 regenerateItems(d->effectiveZoom); 0361 syncSliderWithZoom(); 0362 } 0363 0364 void KoZoomAction::setMaximumZoom(qreal zoom) 0365 { 0366 Q_ASSERT(zoom > 0.0f); 0367 KoZoomMode::setMaximumZoom(zoom); 0368 d->maximumZoomValue = zoom; 0369 d->sliderLookup = d->generateSliderZoomLevels(); 0370 regenerateItems(d->effectiveZoom); 0371 syncSliderWithZoom(); 0372 } 0373 0374 void KoZoomAction::setMinMaxZoom(qreal min, qreal max) 0375 { 0376 Q_ASSERT(min > 0.0f); 0377 Q_ASSERT(max > 0.0f); 0378 KoZoomMode::setMinimumZoom(min); 0379 KoZoomMode::setMaximumZoom(max); 0380 d->minimumZoomValue = min; 0381 d->maximumZoomValue = max; 0382 d->sliderLookup = d->generateSliderZoomLevels(); 0383 regenerateItems(d->effectiveZoom); 0384 syncSliderWithZoom(); 0385 }