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"