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 }