File indexing completed on 2024-05-12 16:02:13
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 QList<qreal> zoomLevels; 0069 KConfigGroup config = KSharedConfig::openConfig()->group(""); 0070 bool smoothZooming = config.readEntry("SmoothZooming", false); 0071 qreal defaultZoomStep = sqrt(2); 0072 0073 if (smoothZooming) { 0074 defaultZoomStep = sqrt(1.25); 0075 zoomLevels << 1.0; 0076 } 0077 else { 0078 zoomLevels << 0.25 / 2.0; 0079 zoomLevels << 0.25 / 1.5; 0080 zoomLevels << 0.25; 0081 zoomLevels << 1.0 / 3.0; 0082 zoomLevels << 0.5; 0083 zoomLevels << 2.0 / 3.0; 0084 zoomLevels << 1.0; 0085 } 0086 0087 for (qreal zoom = zoomLevels.first() / defaultZoomStep; 0088 zoom > parent->minimumZoom(); 0089 zoom /= defaultZoomStep) { 0090 0091 zoomLevels.prepend(zoom); 0092 } 0093 0094 for (qreal zoom = zoomLevels.last() * defaultZoomStep; 0095 zoom < parent->maximumZoom(); 0096 zoom *= defaultZoomStep) { 0097 0098 zoomLevels.append(zoom); 0099 } 0100 0101 if (smoothZooming) { 0102 zoomLevels << 0.25 / 2.0; 0103 zoomLevels << 0.25 / 1.5; 0104 zoomLevels << 0.25; 0105 zoomLevels << 1.0 / 3.0; 0106 zoomLevels << 0.5; 0107 zoomLevels << 2.0 / 3.0; 0108 zoomLevels << 1.0; 0109 std::sort(zoomLevels.begin(), zoomLevels.end()); 0110 KritaUtils::makeContainerUnique(zoomLevels); 0111 } 0112 0113 return zoomLevels; 0114 } 0115 0116 QList<qreal> KoZoomAction::Private::filterMenuZoomLevels(const QList<qreal> &zoomLevels) const 0117 { 0118 QList<qreal> filteredZoomLevels; 0119 0120 Q_FOREACH (qreal zoom, zoomLevels) { 0121 if (zoom >= 0.2 && zoom <= 10) { 0122 filteredZoomLevels << zoom; 0123 } 0124 } 0125 0126 return filteredZoomLevels; 0127 } 0128 0129 KoZoomAction::KoZoomAction(KoZoomMode::Modes zoomModes, const QString& text, QObject *parent) 0130 : KSelectAction(text, parent) 0131 , d(new Private(this)) 0132 { 0133 d->zoomModes = zoomModes; 0134 setIcon(koIcon("zoom-original")); 0135 setEditable( true ); 0136 setMaxComboViewCount( 15 ); 0137 0138 d->sliderLookup = d->generateSliderZoomLevels(); 0139 0140 d->effectiveZoom = 1.0; 0141 regenerateItems(d->effectiveZoom); 0142 0143 connect( this, SIGNAL(triggered(QString)), SLOT(triggered(QString)) ); 0144 connect(&d->guiUpdateCompressor, SIGNAL(timeout()), SLOT(slotUpdateGuiAfterZoom())); 0145 } 0146 0147 KoZoomAction::~KoZoomAction() 0148 { 0149 delete d; 0150 } 0151 0152 qreal KoZoomAction::effectiveZoom() const 0153 { 0154 return d->effectiveZoom; 0155 } 0156 0157 void KoZoomAction::setZoom(qreal zoom) 0158 { 0159 setEffectiveZoom(zoom); 0160 } 0161 0162 void KoZoomAction::triggered(const QString& text) 0163 { 0164 QString zoomString = text; 0165 zoomString = zoomString.remove( '&' ); 0166 0167 KoZoomMode::Mode mode = KoZoomMode::toMode(zoomString); 0168 double zoom = 0; 0169 0170 if( mode == KoZoomMode::ZOOM_CONSTANT ) { 0171 bool ok; 0172 QRegExp regexp( ".*(\\d+(\\.\\d+)?).*" ); // "Captured" non-empty sequence of digits 0173 int pos = regexp.indexIn( zoomString ); 0174 if( pos > -1 ) { 0175 zoom = regexp.cap( 1 ).toDouble( &ok ); 0176 0177 if( !ok ) { 0178 zoom = 0; 0179 } 0180 } 0181 } 0182 0183 emit zoomChanged( mode, zoom/100.0 ); 0184 } 0185 0186 void KoZoomAction::regenerateItems(const qreal zoom) 0187 { 0188 // TODO: refactor this method to become less slow, then 0189 // we could reduce the timeout of d->guiUpdateCompressor 0190 // to at least 80ms (12.5fps) 0191 0192 QList<qreal> zoomLevels = d->filterMenuZoomLevels(d->sliderLookup); 0193 0194 if( !zoomLevels.contains( zoom ) ) 0195 zoomLevels << zoom; 0196 0197 std::sort(zoomLevels.begin(), zoomLevels.end()); 0198 0199 // update items with new sorted zoom values 0200 QStringList values; 0201 if(d->zoomModes & KoZoomMode::ZOOM_WIDTH) { 0202 values << KoZoomMode::toString(KoZoomMode::ZOOM_WIDTH); 0203 } 0204 if(d->zoomModes & KoZoomMode::ZOOM_PAGE) { 0205 values << KoZoomMode::toString(KoZoomMode::ZOOM_PAGE); 0206 } 0207 if(d->zoomModes & KoZoomMode::ZOOM_HEIGHT) { 0208 values << KoZoomMode::toString(KoZoomMode::ZOOM_HEIGHT); 0209 } 0210 0211 Q_FOREACH (qreal value, zoomLevels) { 0212 const qreal valueInPercent = value * 100; 0213 const int precision = (value > 10.0) ? 0 : 1; 0214 0215 values << i18n("%1%", QLocale().toString(valueInPercent, 'f', precision)); 0216 } 0217 0218 setItems( values ); 0219 0220 emit zoomLevelsChanged(values); 0221 0222 { 0223 const qreal zoomInPercent = zoom * 100; 0224 const int precision = (zoom > 10.0) ? 0 : 1; 0225 0226 const QString valueString = i18n("%1%", QLocale().toString(zoomInPercent, 'f', precision)); 0227 0228 setCurrentAction(valueString); 0229 0230 emit currentZoomLevelChanged(valueString); 0231 } 0232 } 0233 0234 void KoZoomAction::sliderValueChanged(int value) 0235 { 0236 if (value < d->sliderLookup.size()) { 0237 setZoom(d->sliderLookup[value]); 0238 emit zoomChanged(KoZoomMode::ZOOM_CONSTANT, d->sliderLookup[value]); 0239 } 0240 } 0241 0242 qreal KoZoomAction::nextZoomLevel() const 0243 { 0244 const qreal eps = 1e-5; 0245 int i = 0; 0246 while (i < d->sliderLookup.size() - 1 && d->effectiveZoom > d->sliderLookup[i] - eps) { 0247 i++; 0248 } 0249 0250 return qMax(d->effectiveZoom, d->sliderLookup[i]); 0251 } 0252 0253 qreal KoZoomAction::prevZoomLevel() const 0254 { 0255 const qreal eps = 1e-5; 0256 int i = d->sliderLookup.size() - 1; 0257 while (i > 0 && d->effectiveZoom < d->sliderLookup[i] + eps) i--; 0258 0259 return qMin(d->effectiveZoom, d->sliderLookup[i]); 0260 } 0261 0262 void KoZoomAction::zoomIn() 0263 { 0264 qreal zoom = nextZoomLevel(); 0265 0266 if (zoom > d->effectiveZoom) { 0267 setZoom(zoom); 0268 emit zoomChanged(KoZoomMode::ZOOM_CONSTANT, d->effectiveZoom); 0269 } 0270 } 0271 0272 void KoZoomAction::zoomOut() 0273 { 0274 qreal zoom = prevZoomLevel(); 0275 0276 if (zoom < d->effectiveZoom) { 0277 setZoom(zoom); 0278 emit zoomChanged(KoZoomMode::ZOOM_CONSTANT, d->effectiveZoom); 0279 } 0280 } 0281 0282 QWidget * KoZoomAction::createWidget(QWidget *parent) 0283 { 0284 KoZoomWidget* zoomWidget = new KoZoomWidget(parent, d->sliderLookup.size() - 1); 0285 0286 connect(this, SIGNAL(zoomLevelsChanged(QStringList)), zoomWidget, SLOT(setZoomLevels(QStringList))); 0287 connect(this, SIGNAL(sliderZoomLevelsChanged(int)), zoomWidget, SLOT(setSliderSize(int))); 0288 connect(this, SIGNAL(currentZoomLevelChanged(QString)), zoomWidget, SLOT(setCurrentZoomLevel(QString))); 0289 connect(this, SIGNAL(sliderChanged(int)), zoomWidget, SLOT(setSliderValue(int))); 0290 connect(this, SIGNAL(canvasMappingModeChanged(bool)), zoomWidget, SLOT(setCanvasMappingMode(bool))); 0291 0292 connect(zoomWidget, SIGNAL(sliderValueChanged(int)), this, SLOT(sliderValueChanged(int))); 0293 connect(zoomWidget, SIGNAL(zoomLevelChanged(QString)), this, SLOT(triggered(QString))); 0294 connect(zoomWidget, SIGNAL(canvasMappingModeChanged(bool)), this, SIGNAL(canvasMappingModeChanged(bool))); 0295 connect(zoomWidget, SIGNAL(zoomedToSelection()), this, SIGNAL(zoomedToSelection())); 0296 connect(zoomWidget, SIGNAL(zoomedToAll()), this, SIGNAL(zoomedToAll())); 0297 regenerateItems(d->effectiveZoom); 0298 syncSliderWithZoom(); 0299 return zoomWidget; 0300 } 0301 0302 void KoZoomAction::setEffectiveZoom(qreal zoom) 0303 { 0304 if(d->effectiveZoom == zoom) 0305 return; 0306 0307 zoom = clampZoom(zoom); 0308 d->effectiveZoom = zoom; 0309 d->guiUpdateCompressor.start(); 0310 } 0311 0312 void KoZoomAction::slotUpdateGuiAfterZoom() 0313 { 0314 syncSliderWithZoom(); 0315 0316 // TODO: don't regenerate when only mode changes 0317 regenerateItems(d->effectiveZoom); 0318 } 0319 0320 void KoZoomAction::slotUpdateZoomLevels() 0321 { 0322 qreal currentZoom = d->effectiveZoom; 0323 d->generateSliderZoomLevels(); 0324 d->sliderLookup = d->generateSliderZoomLevels(); 0325 regenerateItems(currentZoom); 0326 syncSliderWithZoom(); 0327 0328 emit sliderZoomLevelsChanged(d->sliderLookup.size() - 1); 0329 0330 } 0331 0332 void KoZoomAction::setSelectedZoomMode(KoZoomMode::Mode mode) 0333 { 0334 QString modeString(KoZoomMode::toString(mode)); 0335 setCurrentAction(modeString); 0336 0337 emit currentZoomLevelChanged(modeString); 0338 } 0339 0340 void KoZoomAction::setCanvasMappingMode(bool status) 0341 { 0342 emit canvasMappingModeChanged(status); 0343 } 0344 0345 void KoZoomAction::syncSliderWithZoom() 0346 { 0347 const qreal eps = 1e-5; 0348 int i = d->sliderLookup.size() - 1; 0349 while (d->effectiveZoom < d->sliderLookup[i] + eps && i > 0) i--; 0350 0351 emit sliderChanged(i); 0352 } 0353 0354 qreal KoZoomAction::minimumZoom() 0355 { 0356 if (d->minimumZoomValue < 0) { 0357 return KoZoomMode::minimumZoom(); 0358 } 0359 return d->minimumZoomValue; 0360 } 0361 0362 qreal KoZoomAction::maximumZoom() 0363 { 0364 if (d->maximumZoomValue < 0) { 0365 return KoZoomMode::maximumZoom(); 0366 } 0367 return d->maximumZoomValue; 0368 } 0369 0370 qreal KoZoomAction::clampZoom(qreal zoom) 0371 { 0372 return qMin(maximumZoom(), qMax(minimumZoom(), zoom)); 0373 } 0374 0375 void KoZoomAction::setMinimumZoom(qreal zoom) 0376 { 0377 Q_ASSERT(zoom > 0.0f); 0378 KoZoomMode::setMinimumZoom(zoom); 0379 d->minimumZoomValue = zoom; 0380 d->generateSliderZoomLevels(); 0381 d->sliderLookup = d->generateSliderZoomLevels(); 0382 regenerateItems(d->effectiveZoom); 0383 syncSliderWithZoom(); 0384 } 0385 0386 void KoZoomAction::setMaximumZoom(qreal zoom) 0387 { 0388 Q_ASSERT(zoom > 0.0f); 0389 KoZoomMode::setMaximumZoom(zoom); 0390 d->maximumZoomValue = zoom; 0391 d->sliderLookup = d->generateSliderZoomLevels(); 0392 regenerateItems(d->effectiveZoom); 0393 syncSliderWithZoom(); 0394 }