1 /*
2     This file is part of the Kasten Framework, made within the KDE community.
3 
4     SPDX-FileCopyrightText: 2008-2009 Friedrich W. H. Kossebau <kossebau@kde.org>
5 
6     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
7 */
8 
9 #include "zoomslider.hpp"
10 
11 // Kasten gui
12 #include <Kasten/Zoomable>
13 // Kasten core
14 #include <Kasten/AbstractModel>
15 // KF
16 #include <KLocalizedString>
17 // Qt
18 #include <QSlider>
19 #include <QToolButton>
20 #include <QLayout>
21 #include <QApplication>
22 #include <QHelpEvent>
23 
24 namespace Kasten {
25 
26 static constexpr int ZoomSliderWidth = 150;
27 
28 // TODO: look at Dolphin/Krita/KOffice zoom tool
29 
30 // TODO: different zoom strategies: fixed step size, relative step size
31 // where to put this, behind interface? or better into a zoomtool?
32 
ZoomSlider(QWidget * parent)33 ZoomSlider::ZoomSlider(QWidget* parent)
34     : QWidget(parent)
35 {
36     mZoomOutButton = new QToolButton(this);
37     mZoomOutButton->setIcon(QIcon::fromTheme(QStringLiteral("zoom-out")));
38     mZoomOutButton->setAutoRaise(true);
39 
40     mSlider = new QSlider(Qt::Horizontal, this);
41 
42     mZoomInButton = new QToolButton(this);
43     mZoomInButton->setIcon(QIcon::fromTheme(QStringLiteral("zoom-in")));
44     mZoomInButton->setAutoRaise(true);
45 
46     auto* layout = new QHBoxLayout(this);
47     layout->setSpacing(0);
48     layout->setContentsMargins(0, 0, 0, 0);
49     layout->addWidget(mZoomOutButton);
50     layout->addWidget(mSlider);
51     layout->addWidget(mZoomInButton);
52 
53     connect(mZoomOutButton, &QAbstractButton::clicked,
54             this, &ZoomSlider::zoomOut);
55     connect(mZoomInButton, &QAbstractButton::clicked,
56             this, &ZoomSlider::zoomIn);
57     connect(mSlider, &QSlider::valueChanged,
58             this, &ZoomSlider::onSliderValueChanged);
59     connect(mSlider, &QSlider::sliderMoved,
60             this, &ZoomSlider::onSliderMoved);
61 
62     setFixedWidth(ZoomSliderWidth);
63 
64     setTargetModel(nullptr);
65 }
66 
67 ZoomSlider::~ZoomSlider() = default;
68 
setTargetModel(AbstractModel * model)69 void ZoomSlider::setTargetModel(AbstractModel* model)
70 {
71     if (mModel) {
72         mModel->disconnect(this);
73     }
74 
75     mModel = model ? model->findBaseModelWithInterface<If::Zoomable*>() : nullptr;
76     mZoomControl = mModel ? qobject_cast<If::Zoomable*>(mModel) : nullptr;
77 
78     const bool hasView = (mZoomControl != nullptr);
79     if (hasView) {
80         mSlider->setSingleStep(1);   // mZoomControl->zoomLevelSingleStep()?
81         mSlider->setPageStep(5);   // mZoomControl->zoomLevelPageStep()?
82 
83         const int min = 0; // mZoomControl->minimumZoomLevel();
84         const int max = 99; // mZoomControl->maximumZoomLevel();
85         mSlider->setRange(min, max);
86 
87         onZoomLevelChange(mZoomControl->zoomLevel());
88         const int sliderValue = mSlider->value();
89         mZoomOutButton->setEnabled(sliderValue > mSlider->minimum());
90         mZoomInButton->setEnabled(sliderValue < mSlider->maximum());
91         connect(mModel, SIGNAL(zoomLevelChanged(double)), SLOT(onZoomLevelChange(double)));
92     } else {
93         mZoomOutButton->setEnabled(false);
94         mZoomInButton->setEnabled(false);
95         // put slider in the middle
96         mSlider->setRange(0, 99);
97         mSlider->setValue(50);
98     }
99 
100     mSlider->setEnabled(hasView);
101 }
102 
updateToolTip(int sliderValue)103 void ZoomSlider::updateToolTip(int sliderValue)
104 {
105     const float zoomLevel = 50.0 / (100 - sliderValue);
106     const int zoomPercent = static_cast<int>(zoomLevel * 100 + 0.5);
107     mSlider->setToolTip(i18nc("@info:tooltip", "Zoom: %1%", zoomPercent));
108 // TODO: get the text by a signal toolTipNeeded( int zoomLevel, QString* toolTipText ); ?
109 }
110 
zoomOut()111 void ZoomSlider::zoomOut()
112 {
113     const int newValue = mSlider->value() - mSlider->singleStep();
114     mSlider->setValue(newValue);
115 }
116 
zoomIn()117 void ZoomSlider::zoomIn()
118 {
119     const int newValue = mSlider->value() + mSlider->singleStep();
120     mSlider->setValue(newValue);
121 }
122 
onSliderValueChanged(int sliderValue)123 void ZoomSlider::onSliderValueChanged(int sliderValue)
124 {
125     updateToolTip(sliderValue);
126     mZoomOutButton->setEnabled(sliderValue > mSlider->minimum());
127     mZoomInButton->setEnabled(sliderValue < mSlider->maximum());
128 
129     if (mZoomControl) {
130         mZoomControl->setZoomLevel(50.0 / (100 - sliderValue));
131     }
132 }
133 
134 // TODO: which signal comes first, valueChanged or sliderMoved?
135 // ensure correct calculation of zoomLevel, best by model
136 // but can be timeconsuming?
137 // use timer to delay resize, so that sliding is not delayed by resizing
onSliderMoved(int sliderValue)138 void ZoomSlider::onSliderMoved(int sliderValue)
139 {
140     Q_UNUSED(sliderValue)
141 
142     QPoint toolTipPoint = mSlider->rect().topLeft();
143     toolTipPoint.ry() += mSlider->height() / 2;
144     toolTipPoint = mSlider->mapToGlobal(toolTipPoint);
145 
146     QHelpEvent toolTipEvent(QEvent::ToolTip, QPoint(0, 0), toolTipPoint);
147     QApplication::sendEvent(mSlider, &toolTipEvent);
148 }
149 
onZoomLevelChange(double level)150 void ZoomSlider::onZoomLevelChange(double level)
151 {
152     mZoomLevel = level;
153     const int newSliderValue = 100 - static_cast<int>(50.0 / mZoomLevel + 0.5);
154     if (newSliderValue != mSlider->value()) {
155         disconnect(mSlider, &QSlider::valueChanged,
156                    this, &ZoomSlider::onSliderValueChanged);
157         mSlider->setSliderPosition(newSliderValue);
158         updateToolTip(mSlider->value());
159         connect(mSlider, &QSlider::valueChanged,
160                 this, &ZoomSlider::onSliderValueChanged);
161     }
162 }
163 
164 }
165