File indexing completed on 2024-04-28 04:32:23

0001 /*
0002  * SPDX-FileCopyrightText: 2009 Aurélien Gâteau <agateau@kde.org>
0003  * SPDX-FileCopyrightText: 2009 Kåre Sårs <kare.sars@iki.fi>
0004  * SPDX-FileCopyrightText: 2014 Gregor Mitsch : port to KDE5 frameworks
0005  *
0006  * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0007  */
0008 
0009 #include "splittercollapser.h"
0010 
0011 #include <QApplication>
0012 #include <QEvent>
0013 #include <QMouseEvent>
0014 #include <QSplitter>
0015 #include <QStyleOptionToolButton>
0016 #include <QStylePainter>
0017 #include <QTimeLine>
0018 
0019 namespace KSaneIface
0020 {
0021 
0022 enum Direction {
0023     LTR      = 1 << 0,
0024     RTL      = 1 << 1,
0025     Vertical = 1 << 2,
0026     TTB      = Vertical + (1 << 0),
0027     BTT      = Vertical + (1 << 1)
0028 };
0029 
0030 const int TIMELINE_DURATION = 500;
0031 
0032 const qreal MINIMUM_OPACITY = 0.3;
0033 
0034 struct ArrowTypes {
0035     ArrowTypes()
0036         : visible(Qt::NoArrow), notVisible(Qt::NoArrow) {}
0037 
0038     ArrowTypes(Qt::ArrowType t1, Qt::ArrowType t2)
0039         : visible(t1), notVisible(t2) {}
0040 
0041     Qt::ArrowType visible,
0042     notVisible;
0043 
0044     Qt::ArrowType get(bool isVisible)
0045     {
0046         return isVisible ? visible : notVisible;
0047     }
0048 };
0049 
0050 struct SplitterCollapserPrivate {
0051     SplitterCollapser *q;
0052     QSplitter *mSplitter;
0053     QWidget *mWidget;
0054     Direction mDirection;
0055     QTimeLine *mOpacityTimeLine;
0056     int mSizeAtCollaps;
0057 
0058     bool isVertical() const
0059     {
0060         return mDirection & Vertical;
0061     }
0062 
0063     bool isVisible() const
0064     {
0065         bool isVisible = mWidget->isVisible();
0066         QRect widgetRect = mWidget->geometry();
0067         if (isVisible) {
0068             QPoint br = widgetRect.bottomRight();
0069             if ((br.x() <= 0) || (br.y() <= 0)) {
0070                 isVisible = false;
0071             }
0072         }
0073         return isVisible;
0074     }
0075 
0076     void updatePosition()
0077     {
0078         int x, y;
0079         QRect widgetRect = mWidget->geometry();
0080         int splitterWidth = mSplitter->width();
0081         int handleWidth = mSplitter->handleWidth();
0082         int width = q->width();
0083 
0084         if (!isVertical()) {
0085             // FIXME: Make this configurable
0086             y = 30;
0087             if (mDirection == LTR) {
0088                 if (isVisible()) {
0089                     x = widgetRect.right() + handleWidth;
0090                 } else {
0091                     x = 0;
0092                 }
0093             } else { // RTL
0094                 if (isVisible()) {
0095                     x = widgetRect.left() - handleWidth - width;
0096                 } else {
0097                     x = splitterWidth - handleWidth - width;
0098                 }
0099             }
0100         } else {
0101             // FIXME
0102             x = 0;
0103             y = 0;
0104         }
0105         q->move(x, y);
0106     }
0107 
0108     void updateArrow()
0109     {
0110         static QMap<Direction, ArrowTypes> arrowForDirection;
0111         if (arrowForDirection.isEmpty()) {
0112             arrowForDirection[LTR] = ArrowTypes(Qt::LeftArrow,  Qt::RightArrow);
0113             arrowForDirection[RTL] = ArrowTypes(Qt::RightArrow, Qt::LeftArrow);
0114             arrowForDirection[TTB] = ArrowTypes(Qt::UpArrow,    Qt::DownArrow);
0115             arrowForDirection[BTT] = ArrowTypes(Qt::DownArrow,  Qt::UpArrow);
0116         }
0117         q->setArrowType(arrowForDirection[mDirection].get(isVisible()));
0118     }
0119 
0120     void widgetEventFilter(QEvent *event)
0121     {
0122         switch (event->type()) {
0123         case QEvent::Resize:
0124             updatePosition();
0125             updateOpacity();
0126             break;
0127 
0128         case QEvent::Move:
0129         case QEvent::Show:
0130         case QEvent::Hide:
0131             updatePosition();
0132             updateOpacity();
0133             updateArrow();
0134             break;
0135 
0136         default:
0137             break;
0138         }
0139     }
0140 
0141     void updateOpacity()
0142     {
0143         QPoint pos = q->parentWidget()->mapFromGlobal(QCursor::pos());
0144         QRect opaqueRect = q->geometry();
0145         bool opaqueCollapser = opaqueRect.contains(pos);
0146         int frame = mOpacityTimeLine->currentFrame();
0147         if (opaqueCollapser && frame == mOpacityTimeLine->startFrame()) {
0148             mOpacityTimeLine->setDirection(QTimeLine::Forward);
0149             startTimeLine();
0150         } else if (!opaqueCollapser && frame == mOpacityTimeLine->endFrame()) {
0151             mOpacityTimeLine->setDirection(QTimeLine::Backward);
0152             startTimeLine();
0153         }
0154     }
0155 
0156     void startTimeLine()
0157     {
0158         if (mOpacityTimeLine->state() != QTimeLine::Running) {
0159             mOpacityTimeLine->start();
0160         }
0161     }
0162 };
0163 
0164 SplitterCollapser::SplitterCollapser(QSplitter *splitter, QWidget *widget)
0165     : QToolButton(),
0166       d(new SplitterCollapserPrivate)
0167 {
0168     d->q = this;
0169 
0170     // We do not want our collapser to be added as a regular widget in the
0171     // splitter!
0172     setAttribute(Qt::WA_NoChildEventsForParent);
0173 
0174     d->mOpacityTimeLine = new QTimeLine(TIMELINE_DURATION, this);
0175     d->mOpacityTimeLine->setFrameRange(int(MINIMUM_OPACITY * 1000), 1000);
0176     connect(d->mOpacityTimeLine, SIGNAL(valueChanged(qreal)), SLOT(update()));
0177 
0178     d->mWidget = widget;
0179     d->mWidget->installEventFilter(this);
0180 
0181     qApp->installEventFilter(this);
0182 
0183     d->mSplitter = splitter;
0184     setParent(d->mSplitter);
0185 
0186     if (splitter->indexOf(widget) < splitter->count() / 2) {
0187         d->mDirection = LTR;
0188     } else {
0189         d->mDirection = RTL;
0190     }
0191     if (splitter->orientation() == Qt::Vertical) {
0192         // FIXME: Ugly!
0193         d->mDirection = static_cast<Direction>(int(d->mDirection) + int(TTB));
0194     }
0195 
0196     connect(this, SIGNAL(clicked()), SLOT(slotClicked()));
0197 
0198     show();
0199 }
0200 
0201 SplitterCollapser::~SplitterCollapser()
0202 {
0203     delete d;
0204 }
0205 
0206 bool SplitterCollapser::eventFilter(QObject *object, QEvent *event)
0207 {
0208     if (object == d->mWidget) {
0209         d->widgetEventFilter(event);
0210     } else { /* app */
0211         if (event->type() == QEvent::MouseMove) {
0212             d->updateOpacity();
0213         }
0214     }
0215     return false;
0216 }
0217 
0218 QSize SplitterCollapser::sizeHint() const
0219 {
0220     int extent = style()->pixelMetric(QStyle::PM_ScrollBarExtent);
0221     QSize sh(extent * 3 / 4, extent * 240 / 100);
0222     if (d->isVertical()) {
0223         sh.transpose();
0224     }
0225     return sh;
0226 }
0227 
0228 void SplitterCollapser::slotClicked()
0229 {
0230     QList<int> sizes = d->mSplitter->sizes();
0231     int index = d->mSplitter->indexOf(d->mWidget);
0232     if (d->isVisible()) {
0233         d->mSizeAtCollaps = sizes[index];
0234         sizes[index] = 0;
0235     } else {
0236         if (d->mSizeAtCollaps != 0) {
0237             sizes[index] = d->mSizeAtCollaps;
0238         } else {
0239             if (d->isVertical()) {
0240                 sizes[index] = d->mWidget->sizeHint().height();
0241             } else {
0242                 sizes[index] = d->mWidget->sizeHint().width();
0243             }
0244         }
0245     }
0246     d->mSplitter->setSizes(sizes);
0247 }
0248 
0249 void SplitterCollapser::slotCollapse()
0250 {
0251     if (d->isVisible()) {
0252         slotClicked();
0253     }
0254     // else do nothing
0255 }
0256 
0257 void SplitterCollapser::slotRestore()
0258 {
0259     if (!d->isVisible()) {
0260         slotClicked();
0261     }
0262     // else do nothing
0263 }
0264 
0265 void SplitterCollapser::slotSetCollapsed(bool collapse)
0266 {
0267     if (collapse == d->isVisible()) {
0268         slotClicked();
0269     }
0270     // else do nothing
0271 }
0272 
0273 void SplitterCollapser::paintEvent(QPaintEvent *)
0274 {
0275     QStylePainter painter(this);
0276     qreal opacity = d->mOpacityTimeLine->currentFrame() / 1000.;
0277     painter.setOpacity(opacity);
0278 
0279     QStyleOptionToolButton opt;
0280     initStyleOption(&opt);
0281     if (d->mDirection == LTR) {
0282         opt.rect.setLeft(-width());
0283     } else {
0284         opt.rect.setWidth(width() * 2);
0285     }
0286     painter.drawPrimitive(QStyle::PE_PanelButtonTool, opt);
0287 
0288     QStyleOptionToolButton opt2;
0289     initStyleOption(&opt2);
0290     painter.drawControl(QStyle::CE_ToolButtonLabel, opt2);
0291 }
0292 
0293 } // namespace
0294 
0295 #include "moc_splittercollapser.cpp"