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 }