KWidgetsAddons

ksplittercollapserbutton.cpp
1/*
2 SPDX-FileCopyrightText: 2014 Montel Laurent <montel@kde.org>
3 based on code:
4 SPDX-FileCopyrightText: 2009 Aurélien Gâteau <agateau@kde.org>
5 SPDX-FileCopyrightText: 2009 Kåre Sårs <kare.sars@iki.fi>
6
7 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
8*/
9
10#include "ksplittercollapserbutton.h"
11
12// Qt
13#include <QEvent>
14#include <QSplitter>
15#include <QStyleOptionToolButton>
16#include <QStylePainter>
17#include <QTimeLine>
18
19enum Direction {
20 LeftToRight = 0,
22 TopToBottom,
23 BottomToTop,
24};
25
26const static int TIMELINE_DURATION = 500;
27
28const static qreal MINIMUM_OPACITY = 0.3;
29
30static const struct {
31 Qt::ArrowType arrowVisible;
32 Qt::ArrowType notArrowVisible;
34
35class KSplitterCollapserButtonPrivate
36{
37public:
38 KSplitterCollapserButtonPrivate(KSplitterCollapserButton *qq);
39
41 QSplitter *splitter;
42 QWidget *childWidget;
43 Direction direction;
44 QTimeLine *opacityTimeLine;
45 QList<int> sizeAtCollapse;
46
47 bool isVertical() const;
48
49 bool isWidgetCollapsed() const;
50
51 void updatePosition();
52
53 void updateArrow();
54
55 void widgetEventFilter(QEvent *event);
56
57 void updateOpacity();
58
59 void startTimeLine();
60};
61
62KSplitterCollapserButtonPrivate::KSplitterCollapserButtonPrivate(KSplitterCollapserButton *qq)
63 : q(qq)
64 , splitter(nullptr)
65 , childWidget(nullptr)
66 , opacityTimeLine(nullptr)
67{
68}
69
70bool KSplitterCollapserButtonPrivate::isVertical() const
71{
72 return (splitter->orientation() == Qt::Vertical);
73}
74
75bool KSplitterCollapserButtonPrivate::isWidgetCollapsed() const
76{
77 const QRect widgetRect = childWidget->geometry();
78 if ((widgetRect.height() == 0) || (widgetRect.width() == 0)) {
79 return true;
80 } else {
81 return false;
82 }
83}
84
85void KSplitterCollapserButtonPrivate::updatePosition()
86{
87 int x = 0;
88 int y = 0;
89 const QRect widgetRect = childWidget->geometry();
90 const int handleWidth = splitter->handleWidth();
91
92 if (!isVertical()) {
93 const int splitterWidth = splitter->width();
94 const int width = q->sizeHint().width();
95 // FIXME: Make this configurable
96 y = 30;
97 if (direction == LeftToRight) {
98 if (!isWidgetCollapsed()) {
99 x = widgetRect.right() + handleWidth;
100 } else {
101 x = 0;
102 }
103 } else { // RightToLeft
104 if (!isWidgetCollapsed()) {
105 x = widgetRect.left() - handleWidth - width;
106 } else {
107 x = splitterWidth - handleWidth - width;
108 }
109 }
110 } else {
111 x = 30;
112 const int height = q->sizeHint().height();
113 const int splitterHeight = splitter->height();
114 if (direction == TopToBottom) {
115 if (!isWidgetCollapsed()) {
116 y = widgetRect.bottom() + handleWidth;
117 } else {
118 y = 0;
119 }
120 } else { // BottomToTop
121 if (!isWidgetCollapsed()) {
122 y = widgetRect.top() - handleWidth - height;
123 } else {
124 y = splitterHeight - handleWidth - height;
125 }
126 }
127 }
128 q->move(x, y);
129}
130
131void KSplitterCollapserButtonPrivate::updateArrow()
132{
133 q->setArrowType(isWidgetCollapsed() ? s_arrowDirection[direction].notArrowVisible : s_arrowDirection[direction].arrowVisible);
134}
135
136void KSplitterCollapserButtonPrivate::widgetEventFilter(QEvent *event)
137{
138 switch (event->type()) {
139 case QEvent::Resize:
140 case QEvent::Move:
141 case QEvent::Show:
142 case QEvent::Hide:
143 updatePosition();
144 updateOpacity();
145 updateArrow();
146 break;
147
148 default:
149 break;
150 }
151}
152
153void KSplitterCollapserButtonPrivate::updateOpacity()
154{
155 const QPoint pos = q->parentWidget()->mapFromGlobal(QCursor::pos());
156 const QRect opaqueRect = q->geometry();
157 const bool opaqueCollapser = opaqueRect.contains(pos);
158 if (opaqueCollapser) {
159 opacityTimeLine->setDirection(QTimeLine::Forward);
160 startTimeLine();
161 } else {
162 opacityTimeLine->setDirection(QTimeLine::Backward);
163 startTimeLine();
164 }
165}
166
167void KSplitterCollapserButtonPrivate::startTimeLine()
168{
169 if (opacityTimeLine->state() == QTimeLine::Running) {
170 opacityTimeLine->stop();
171 }
172 opacityTimeLine->start();
173}
174
176 : QToolButton()
177 , d(new KSplitterCollapserButtonPrivate(this))
178{
179 setObjectName(QStringLiteral("splittercollapser"));
180 // We do not want our collapser to be added as a regular widget in the
181 // splitter!
183
184 d->opacityTimeLine = new QTimeLine(TIMELINE_DURATION, this);
185 d->opacityTimeLine->setFrameRange(int(MINIMUM_OPACITY * 1000), 1000);
186 connect(d->opacityTimeLine, &QTimeLine::valueChanged, this, qOverload<>(&QWidget::update));
187
188 d->childWidget = childWidget;
189 d->childWidget->installEventFilter(this);
190
191 d->splitter = splitter;
192 setParent(d->splitter);
193
194 switch (splitter->orientation()) {
195 case Qt::Horizontal:
196 if (splitter->indexOf(childWidget) < splitter->count() / 2) {
197 d->direction = LeftToRight;
198 } else {
199 d->direction = RightToLeft;
200 }
201 break;
202 case Qt::Vertical:
203 if (splitter->indexOf(childWidget) < splitter->count() / 2) {
204 d->direction = TopToBottom;
205 } else {
206 d->direction = BottomToTop;
207 }
208 break;
209 }
210
211 connect(this, &KSplitterCollapserButton::clicked, this, &KSplitterCollapserButton::slotClicked);
212}
213
215
217{
218 return d->isWidgetCollapsed();
219}
220
221bool KSplitterCollapserButton::eventFilter(QObject *object, QEvent *event)
222{
223 if (object == d->childWidget) {
224 d->widgetEventFilter(event);
225 }
226 return QToolButton::eventFilter(object, event);
227}
228
229void KSplitterCollapserButton::enterEvent(QEnterEvent *event)
230{
231 Q_UNUSED(event)
232 d->updateOpacity();
233}
234
235void KSplitterCollapserButton::leaveEvent(QEvent *event)
236{
237 Q_UNUSED(event)
238 d->updateOpacity();
239}
240
241void KSplitterCollapserButton::showEvent(QShowEvent *event)
242{
243 Q_UNUSED(event)
244 d->updateOpacity();
245}
246
247QSize KSplitterCollapserButton::sizeHint() const
248{
249 QStyleOption opt;
250 opt.initFrom(this);
251 const int extent = style()->pixelMetric(QStyle::PM_ScrollBarExtent, &opt);
252 QSize sh(extent * 3 / 4, extent * 240 / 100);
253 if (d->isVertical()) {
254 sh.transpose();
255 }
256 return sh;
257}
258
259void KSplitterCollapserButton::slotClicked()
260{
261 QList<int> sizes = d->splitter->sizes();
262 const int index = d->splitter->indexOf(d->childWidget);
263 if (!d->isWidgetCollapsed()) {
264 d->sizeAtCollapse = sizes;
265 sizes[index] = 0;
266 } else {
267 if (!d->sizeAtCollapse.isEmpty()) {
268 sizes = d->sizeAtCollapse;
269 } else {
270 if (d->isVertical()) {
271 sizes[index] = d->childWidget->sizeHint().height();
272 } else {
273 sizes[index] = d->childWidget->sizeHint().width();
274 }
275 }
276 }
277 d->splitter->setSizes(sizes);
278 d->opacityTimeLine->setDirection(QTimeLine::Backward);
279 d->startTimeLine();
280}
281
283{
284 if (!d->isWidgetCollapsed()) {
285 slotClicked();
286 }
287 // else do nothing
288}
289
291{
292 if (d->isWidgetCollapsed()) {
293 slotClicked();
294 }
295 // else do nothing
296}
297
299{
300 if (collapse == d->isWidgetCollapsed()) {
301 slotClicked();
302 }
303 // else do nothing
304}
305
306void KSplitterCollapserButton::paintEvent(QPaintEvent *)
307{
308 QStylePainter painter(this);
309 const qreal opacity = d->opacityTimeLine->currentFrame() / 1000.;
310 painter.setOpacity(opacity);
311
313 initStyleOption(&opt);
314
315 if (d->isVertical()) {
316 if (d->direction == TopToBottom) {
317 opt.rect.setTop(-height());
318 } else {
319 opt.rect.setHeight(height() * 2);
320 }
321 } else {
322 if (d->direction == LeftToRight) {
323 opt.rect.setLeft(-width());
324 } else {
325 opt.rect.setWidth(width() * 2);
326 }
327 }
328 painter.drawPrimitive(QStyle::PE_PanelButtonTool, opt);
329
331 initStyleOption(&opt2);
332 painter.drawControl(QStyle::CE_ToolButtonLabel, opt2);
333}
334
335#include "moc_ksplittercollapserbutton.cpp"
A button which appears on the side of a splitter handle and allows easy collapsing of the widget on t...
KSplitterCollapserButton(QWidget *childWidget, QSplitter *splitter)
KSplitterCollapserButton create a splitter collapser.
~KSplitterCollapserButton() override
Destructor.
void restore()
restore, call this function to restore previous splitter position.
void setCollapsed(bool collapsed)
setCollapsed, this function allows to collapse or not the splitter.
bool isWidgetCollapsed() const
isWidgetCollapsed
void collapse()
collapse, this function collapses the splitter if splitter is not collapsed.
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
void clicked(bool checked)
QPoint pos()
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
virtual bool eventFilter(QObject *watched, QEvent *event)
void setObjectName(QAnyStringView name)
int bottom() const const
bool contains(const QPoint &point, bool proper) const const
int height() const const
int left() const const
int right() const const
int top() const const
int width() const const
int height() const const
int width() const const
int count() const const
int indexOf(QWidget *widget) const const
CE_ToolButtonLabel
PM_ScrollBarExtent
PE_PanelButtonTool
virtual int pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const const=0
void initFrom(const QWidget *widget)
ArrowType
LeftToRight
Vertical
WA_NoChildEventsForParent
void setDirection(Direction direction)
void start()
State state() const const
void stop()
void valueChanged(qreal value)
void setArrowType(Qt::ArrowType type)
virtual bool event(QEvent *event) override
virtual void initStyleOption(QStyleOptionToolButton *option) const const
QPoint mapFromGlobal(const QPoint &pos) const const
QWidget * parentWidget() const const
void move(const QPoint &)
void setAttribute(Qt::WidgetAttribute attribute, bool on)
void setParent(QWidget *parent)
QStyle * style() const const
void update()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jul 19 2024 11:51:03 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.