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 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)
int currentFrame() const const
void setDirection(Direction direction)
int endFrame() const const
void start()
int startFrame() const const
State state() const const
bool isVisible() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Nov 29 2024 11:54:50 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.