File indexing completed on 2024-04-28 15:32:14

0001 /*
0002     SPDX-FileCopyrightText: 2014 Montel Laurent <montel@kde.org>
0003     based on code:
0004     SPDX-FileCopyrightText: 2009 Aurélien Gâteau <agateau@kde.org>
0005     SPDX-FileCopyrightText: 2009 Kåre Sårs <kare.sars@iki.fi>
0006 
0007     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0008 */
0009 
0010 #include "ksplittercollapserbutton.h"
0011 
0012 // Qt
0013 #include <QEvent>
0014 #include <QSplitter>
0015 #include <QStyleOptionToolButton>
0016 #include <QStylePainter>
0017 #include <QTimeLine>
0018 
0019 enum Direction {
0020     LeftToRight = 0,
0021     RightToLeft,
0022     TopToBottom,
0023     BottomToTop,
0024 };
0025 
0026 const static int TIMELINE_DURATION = 500;
0027 
0028 const static qreal MINIMUM_OPACITY = 0.3;
0029 
0030 static const struct {
0031     Qt::ArrowType arrowVisible;
0032     Qt::ArrowType notArrowVisible;
0033 } s_arrowDirection[] = {{Qt::LeftArrow, Qt::RightArrow}, {Qt::RightArrow, Qt::LeftArrow}, {Qt::UpArrow, Qt::DownArrow}, {Qt::DownArrow, Qt::UpArrow}};
0034 
0035 class KSplitterCollapserButtonPrivate
0036 {
0037 public:
0038     KSplitterCollapserButtonPrivate(KSplitterCollapserButton *qq);
0039 
0040     KSplitterCollapserButton *q;
0041     QSplitter *splitter;
0042     QWidget *childWidget;
0043     Direction direction;
0044     QTimeLine *opacityTimeLine;
0045     QList<int> sizeAtCollapse;
0046 
0047     bool isVertical() const;
0048 
0049     bool isWidgetCollapsed() const;
0050 
0051     void updatePosition();
0052 
0053     void updateArrow();
0054 
0055     void widgetEventFilter(QEvent *event);
0056 
0057     void updateOpacity();
0058 
0059     void startTimeLine();
0060 };
0061 
0062 KSplitterCollapserButtonPrivate::KSplitterCollapserButtonPrivate(KSplitterCollapserButton *qq)
0063     : q(qq)
0064     , splitter(nullptr)
0065     , childWidget(nullptr)
0066     , opacityTimeLine(nullptr)
0067 {
0068 }
0069 
0070 bool KSplitterCollapserButtonPrivate::isVertical() const
0071 {
0072     return (splitter->orientation() == Qt::Vertical);
0073 }
0074 
0075 bool KSplitterCollapserButtonPrivate::isWidgetCollapsed() const
0076 {
0077     const QRect widgetRect = childWidget->geometry();
0078     if ((widgetRect.height() == 0) || (widgetRect.width() == 0)) {
0079         return true;
0080     } else {
0081         return false;
0082     }
0083 }
0084 
0085 void KSplitterCollapserButtonPrivate::updatePosition()
0086 {
0087     int x = 0;
0088     int y = 0;
0089     const QRect widgetRect = childWidget->geometry();
0090     const int handleWidth = splitter->handleWidth();
0091 
0092     if (!isVertical()) {
0093         const int splitterWidth = splitter->width();
0094         const int width = q->sizeHint().width();
0095         // FIXME: Make this configurable
0096         y = 30;
0097         if (direction == LeftToRight) {
0098             if (!isWidgetCollapsed()) {
0099                 x = widgetRect.right() + handleWidth;
0100             } else {
0101                 x = 0;
0102             }
0103         } else { // RightToLeft
0104             if (!isWidgetCollapsed()) {
0105                 x = widgetRect.left() - handleWidth - width;
0106             } else {
0107                 x = splitterWidth - handleWidth - width;
0108             }
0109         }
0110     } else {
0111         x = 30;
0112         const int height = q->sizeHint().height();
0113         const int splitterHeight = splitter->height();
0114         if (direction == TopToBottom) {
0115             if (!isWidgetCollapsed()) {
0116                 y = widgetRect.bottom() + handleWidth;
0117             } else {
0118                 y = 0;
0119             }
0120         } else { // BottomToTop
0121             if (!isWidgetCollapsed()) {
0122                 y = widgetRect.top() - handleWidth - height;
0123             } else {
0124                 y = splitterHeight - handleWidth - height;
0125             }
0126         }
0127     }
0128     q->move(x, y);
0129 }
0130 
0131 void KSplitterCollapserButtonPrivate::updateArrow()
0132 {
0133     q->setArrowType(isWidgetCollapsed() ? s_arrowDirection[direction].notArrowVisible : s_arrowDirection[direction].arrowVisible);
0134 }
0135 
0136 void KSplitterCollapserButtonPrivate::widgetEventFilter(QEvent *event)
0137 {
0138     switch (event->type()) {
0139     case QEvent::Resize:
0140     case QEvent::Move:
0141     case QEvent::Show:
0142     case QEvent::Hide:
0143         updatePosition();
0144         updateOpacity();
0145         updateArrow();
0146         break;
0147 
0148     default:
0149         break;
0150     }
0151 }
0152 
0153 void KSplitterCollapserButtonPrivate::updateOpacity()
0154 {
0155     const QPoint pos = q->parentWidget()->mapFromGlobal(QCursor::pos());
0156     const QRect opaqueRect = q->geometry();
0157     const bool opaqueCollapser = opaqueRect.contains(pos);
0158     if (opaqueCollapser) {
0159         opacityTimeLine->setDirection(QTimeLine::Forward);
0160         startTimeLine();
0161     } else {
0162         opacityTimeLine->setDirection(QTimeLine::Backward);
0163         startTimeLine();
0164     }
0165 }
0166 
0167 void KSplitterCollapserButtonPrivate::startTimeLine()
0168 {
0169     if (opacityTimeLine->state() == QTimeLine::Running) {
0170         opacityTimeLine->stop();
0171     }
0172     opacityTimeLine->start();
0173 }
0174 
0175 KSplitterCollapserButton::KSplitterCollapserButton(QWidget *childWidget, QSplitter *splitter)
0176     : QToolButton()
0177     , d(new KSplitterCollapserButtonPrivate(this))
0178 {
0179     setObjectName(QStringLiteral("splittercollapser"));
0180     // We do not want our collapser to be added as a regular widget in the
0181     // splitter!
0182     setAttribute(Qt::WA_NoChildEventsForParent);
0183 
0184     d->opacityTimeLine = new QTimeLine(TIMELINE_DURATION, this);
0185     d->opacityTimeLine->setFrameRange(int(MINIMUM_OPACITY * 1000), 1000);
0186     connect(d->opacityTimeLine, &QTimeLine::valueChanged, this, qOverload<>(&QWidget::update));
0187 
0188     d->childWidget = childWidget;
0189     d->childWidget->installEventFilter(this);
0190 
0191     d->splitter = splitter;
0192     setParent(d->splitter);
0193 
0194     switch (splitter->orientation()) {
0195     case Qt::Horizontal:
0196         if (splitter->indexOf(childWidget) < splitter->count() / 2) {
0197             d->direction = LeftToRight;
0198         } else {
0199             d->direction = RightToLeft;
0200         }
0201         break;
0202     case Qt::Vertical:
0203         if (splitter->indexOf(childWidget) < splitter->count() / 2) {
0204             d->direction = TopToBottom;
0205         } else {
0206             d->direction = BottomToTop;
0207         }
0208         break;
0209     }
0210 
0211     connect(this, &KSplitterCollapserButton::clicked, this, &KSplitterCollapserButton::slotClicked);
0212 }
0213 
0214 KSplitterCollapserButton::~KSplitterCollapserButton() = default;
0215 
0216 bool KSplitterCollapserButton::isWidgetCollapsed() const
0217 {
0218     return d->isWidgetCollapsed();
0219 }
0220 
0221 bool KSplitterCollapserButton::eventFilter(QObject *object, QEvent *event)
0222 {
0223     if (object == d->childWidget) {
0224         d->widgetEventFilter(event);
0225     }
0226     return QToolButton::eventFilter(object, event);
0227 }
0228 
0229 void KSplitterCollapserButton::enterEvent(QEvent *event)
0230 {
0231     Q_UNUSED(event)
0232     d->updateOpacity();
0233 }
0234 
0235 void KSplitterCollapserButton::leaveEvent(QEvent *event)
0236 {
0237     Q_UNUSED(event)
0238     d->updateOpacity();
0239 }
0240 
0241 void KSplitterCollapserButton::showEvent(QShowEvent *event)
0242 {
0243     Q_UNUSED(event)
0244     d->updateOpacity();
0245 }
0246 
0247 QSize KSplitterCollapserButton::sizeHint() const
0248 {
0249     QStyleOption opt;
0250     opt.initFrom(this);
0251     const int extent = style()->pixelMetric(QStyle::PM_ScrollBarExtent, &opt);
0252     QSize sh(extent * 3 / 4, extent * 240 / 100);
0253     if (d->isVertical()) {
0254         sh.transpose();
0255     }
0256     return sh;
0257 }
0258 
0259 void KSplitterCollapserButton::slotClicked()
0260 {
0261     QList<int> sizes = d->splitter->sizes();
0262     const int index = d->splitter->indexOf(d->childWidget);
0263     if (!d->isWidgetCollapsed()) {
0264         d->sizeAtCollapse = sizes;
0265         sizes[index] = 0;
0266     } else {
0267         if (!d->sizeAtCollapse.isEmpty()) {
0268             sizes = d->sizeAtCollapse;
0269         } else {
0270             if (d->isVertical()) {
0271                 sizes[index] = d->childWidget->sizeHint().height();
0272             } else {
0273                 sizes[index] = d->childWidget->sizeHint().width();
0274             }
0275         }
0276     }
0277     d->splitter->setSizes(sizes);
0278     d->opacityTimeLine->setDirection(QTimeLine::Backward);
0279     d->startTimeLine();
0280 }
0281 
0282 void KSplitterCollapserButton::collapse()
0283 {
0284     if (!d->isWidgetCollapsed()) {
0285         slotClicked();
0286     }
0287     // else do nothing
0288 }
0289 
0290 void KSplitterCollapserButton::restore()
0291 {
0292     if (d->isWidgetCollapsed()) {
0293         slotClicked();
0294     }
0295     // else do nothing
0296 }
0297 
0298 void KSplitterCollapserButton::setCollapsed(bool collapse)
0299 {
0300     if (collapse == d->isWidgetCollapsed()) {
0301         slotClicked();
0302     }
0303     // else do nothing
0304 }
0305 
0306 void KSplitterCollapserButton::paintEvent(QPaintEvent *)
0307 {
0308     QStylePainter painter(this);
0309     const qreal opacity = d->opacityTimeLine->currentFrame() / 1000.;
0310     painter.setOpacity(opacity);
0311 
0312     QStyleOptionToolButton opt;
0313     initStyleOption(&opt);
0314 
0315     if (d->isVertical()) {
0316         if (d->direction == TopToBottom) {
0317             opt.rect.setTop(-height());
0318         } else {
0319             opt.rect.setHeight(height() * 2);
0320         }
0321     } else {
0322         if (d->direction == LeftToRight) {
0323             opt.rect.setLeft(-width());
0324         } else {
0325             opt.rect.setWidth(width() * 2);
0326         }
0327     }
0328     painter.drawPrimitive(QStyle::PE_PanelButtonTool, opt);
0329 
0330     QStyleOptionToolButton opt2;
0331     initStyleOption(&opt2);
0332     painter.drawControl(QStyle::CE_ToolButtonLabel, opt2);
0333 }
0334 
0335 #include "moc_ksplittercollapserbutton.cpp"