File indexing completed on 2025-02-02 14:20:11
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"