KSane

splittercollapser.cpp
1/*
2 * SPDX-FileCopyrightText: 2009 Aurélien Gâteau <agateau@kde.org>
3 * SPDX-FileCopyrightText: 2009 Kåre Sårs <kare.sars@iki.fi>
4 * SPDX-FileCopyrightText: 2014 Gregor Mitsch : port to KDE5 frameworks
5 *
6 * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
7 */
8
9#include "splittercollapser.h"
10
11#include <QApplication>
12#include <QEvent>
13#include <QMouseEvent>
14#include <QSplitter>
15#include <QStyleOptionToolButton>
16#include <QStylePainter>
17#include <QTimeLine>
18
19namespace KSaneIface
20{
21
22enum Direction {
23 LTR = 1 << 0,
24 RTL = 1 << 1,
25 Vertical = 1 << 2,
26 TTB = Vertical + (1 << 0),
27 BTT = Vertical + (1 << 1)
28};
29
30const int TIMELINE_DURATION = 500;
31
32const qreal MINIMUM_OPACITY = 0.3;
33
34struct ArrowTypes {
35 ArrowTypes()
36 : visible(Qt::NoArrow), notVisible(Qt::NoArrow) {}
37
38 ArrowTypes(Qt::ArrowType t1, Qt::ArrowType t2)
39 : visible(t1), notVisible(t2) {}
40
41 Qt::ArrowType visible,
42 notVisible;
43
44 Qt::ArrowType get(bool isVisible)
45 {
46 return isVisible ? visible : notVisible;
47 }
48};
49
50struct SplitterCollapserPrivate {
51 SplitterCollapser *q;
52 QSplitter *mSplitter;
53 QWidget *mWidget;
54 Direction mDirection;
55 QTimeLine *mOpacityTimeLine;
56 int mSizeAtCollaps;
57
58 bool isVertical() const
59 {
60 return mDirection & Vertical;
61 }
62
63 bool isVisible() const
64 {
65 bool isVisible = mWidget->isVisible();
66 QRect widgetRect = mWidget->geometry();
67 if (isVisible) {
68 QPoint br = widgetRect.bottomRight();
69 if ((br.x() <= 0) || (br.y() <= 0)) {
70 isVisible = false;
71 }
72 }
73 return isVisible;
74 }
75
76 void updatePosition()
77 {
78 int x, y;
79 QRect widgetRect = mWidget->geometry();
80 int splitterWidth = mSplitter->width();
81 int handleWidth = mSplitter->handleWidth();
82 int width = q->width();
83
84 if (!isVertical()) {
85 // FIXME: Make this configurable
86 y = 30;
87 if (mDirection == LTR) {
88 if (isVisible()) {
89 x = widgetRect.right() + handleWidth;
90 } else {
91 x = 0;
92 }
93 } else { // RTL
94 if (isVisible()) {
95 x = widgetRect.left() - handleWidth - width;
96 } else {
97 x = splitterWidth - handleWidth - width;
98 }
99 }
100 } else {
101 // FIXME
102 x = 0;
103 y = 0;
104 }
105 q->move(x, y);
106 }
107
108 void updateArrow()
109 {
110 static QMap<Direction, ArrowTypes> arrowForDirection;
111 if (arrowForDirection.isEmpty()) {
112 arrowForDirection[LTR] = ArrowTypes(Qt::LeftArrow, Qt::RightArrow);
113 arrowForDirection[RTL] = ArrowTypes(Qt::RightArrow, Qt::LeftArrow);
114 arrowForDirection[TTB] = ArrowTypes(Qt::UpArrow, Qt::DownArrow);
115 arrowForDirection[BTT] = ArrowTypes(Qt::DownArrow, Qt::UpArrow);
116 }
117 q->setArrowType(arrowForDirection[mDirection].get(isVisible()));
118 }
119
120 void widgetEventFilter(QEvent *event)
121 {
122 switch (event->type()) {
123 case QEvent::Resize:
124 updatePosition();
125 updateOpacity();
126 break;
127
128 case QEvent::Move:
129 case QEvent::Show:
130 case QEvent::Hide:
131 updatePosition();
132 updateOpacity();
133 updateArrow();
134 break;
135
136 default:
137 break;
138 }
139 }
140
141 void updateOpacity()
142 {
143 QPoint pos = q->parentWidget()->mapFromGlobal(QCursor::pos());
144 QRect opaqueRect = q->geometry();
145 bool opaqueCollapser = opaqueRect.contains(pos);
146 int frame = mOpacityTimeLine->currentFrame();
147 if (opaqueCollapser && frame == mOpacityTimeLine->startFrame()) {
148 mOpacityTimeLine->setDirection(QTimeLine::Forward);
149 startTimeLine();
150 } else if (!opaqueCollapser && frame == mOpacityTimeLine->endFrame()) {
151 mOpacityTimeLine->setDirection(QTimeLine::Backward);
152 startTimeLine();
153 }
154 }
155
156 void startTimeLine()
157 {
158 if (mOpacityTimeLine->state() != QTimeLine::Running) {
159 mOpacityTimeLine->start();
160 }
161 }
162};
163
164SplitterCollapser::SplitterCollapser(QSplitter *splitter, QWidget *widget)
165 : QToolButton(),
166 d(new SplitterCollapserPrivate)
167{
168 d->q = this;
169
170 // We do not want our collapser to be added as a regular widget in the
171 // splitter!
172 setAttribute(Qt::WA_NoChildEventsForParent);
173
174 d->mOpacityTimeLine = new QTimeLine(TIMELINE_DURATION, this);
175 d->mOpacityTimeLine->setFrameRange(int(MINIMUM_OPACITY * 1000), 1000);
176 connect(d->mOpacityTimeLine, SIGNAL(valueChanged(qreal)), SLOT(update()));
177
178 d->mWidget = widget;
179 d->mWidget->installEventFilter(this);
180
181 qApp->installEventFilter(this);
182
183 d->mSplitter = splitter;
184 setParent(d->mSplitter);
185
186 if (splitter->indexOf(widget) < splitter->count() / 2) {
187 d->mDirection = LTR;
188 } else {
189 d->mDirection = RTL;
190 }
191 if (splitter->orientation() == Qt::Vertical) {
192 // FIXME: Ugly!
193 d->mDirection = static_cast<Direction>(int(d->mDirection) + int(TTB));
194 }
195
196 connect(this, SIGNAL(clicked()), SLOT(slotClicked()));
197
198 show();
199}
200
201SplitterCollapser::~SplitterCollapser()
202{
203 delete d;
204}
205
206bool SplitterCollapser::eventFilter(QObject *object, QEvent *event)
207{
208 if (object == d->mWidget) {
209 d->widgetEventFilter(event);
210 } else { /* app */
211 if (event->type() == QEvent::MouseMove) {
212 d->updateOpacity();
213 }
214 }
215 return false;
216}
217
218QSize SplitterCollapser::sizeHint() const
219{
220 int extent = style()->pixelMetric(QStyle::PM_ScrollBarExtent);
221 QSize sh(extent * 3 / 4, extent * 240 / 100);
222 if (d->isVertical()) {
223 sh.transpose();
224 }
225 return sh;
226}
227
228void SplitterCollapser::slotClicked()
229{
230 QList<int> sizes = d->mSplitter->sizes();
231 int index = d->mSplitter->indexOf(d->mWidget);
232 if (d->isVisible()) {
233 d->mSizeAtCollaps = sizes[index];
234 sizes[index] = 0;
235 } else {
236 if (d->mSizeAtCollaps != 0) {
237 sizes[index] = d->mSizeAtCollaps;
238 } else {
239 if (d->isVertical()) {
240 sizes[index] = d->mWidget->sizeHint().height();
241 } else {
242 sizes[index] = d->mWidget->sizeHint().width();
243 }
244 }
245 }
246 d->mSplitter->setSizes(sizes);
247}
248
249void SplitterCollapser::slotCollapse()
250{
251 if (d->isVisible()) {
252 slotClicked();
253 }
254 // else do nothing
255}
256
257void SplitterCollapser::slotRestore()
258{
259 if (!d->isVisible()) {
260 slotClicked();
261 }
262 // else do nothing
263}
264
265void SplitterCollapser::slotSetCollapsed(bool collapse)
266{
267 if (collapse == d->isVisible()) {
268 slotClicked();
269 }
270 // else do nothing
271}
272
273void SplitterCollapser::paintEvent(QPaintEvent *)
274{
275 QStylePainter painter(this);
276 qreal opacity = d->mOpacityTimeLine->currentFrame() / 1000.;
277 painter.setOpacity(opacity);
278
280 initStyleOption(&opt);
281 if (d->mDirection == LTR) {
282 opt.rect.setLeft(-width());
283 } else {
284 opt.rect.setWidth(width() * 2);
285 }
286 painter.drawPrimitive(QStyle::PE_PanelButtonTool, opt);
287
289 initStyleOption(&opt2);
290 painter.drawControl(QStyle::CE_ToolButtonLabel, opt2);
291}
292
293} // namespace
294
295#include "moc_splittercollapser.cpp"
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
void update(Part *part, const QByteArray &data, qint64 dataSize)
KIOCORE_EXPORT TransferJob * get(const QUrl &url, LoadType reload=NoReload, JobFlags flags=DefaultFlags)
QPoint pos()
qsizetype indexOf(const AT &value, qsizetype from) const const
bool isEmpty() const const
void installEventFilter(QObject *filterObj)
int x() const const
int y() const const
QPoint bottomRight() const const
bool contains(const QPoint &point, bool proper) const const
int left() const const
int right() const const
int width() const const
int count() const const
int indexOf(QWidget *widget) const const
CE_ToolButtonLabel
PM_ScrollBarExtent
PE_PanelButtonTool
Vertical
WA_NoChildEventsForParent
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Jan 24 2025 11:56:54 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.