File indexing completed on 2024-05-19 13:22:05
0001 ////////////////////////////////////////////////////////////////////////////// 0002 // oxygenframeshadow.h 0003 // handle sunken frames' shadows 0004 // ------------------- 0005 // 0006 // SPDX-FileCopyrightText: 2010 Hugo Pereira Da Costa <hugo.pereira@free.fr> 0007 // 0008 // Largely inspired from skulpture widget style 0009 // SPDX-FileCopyrightText: 2007-2009 Christoph Feck <christoph@maxiom.de> 0010 // 0011 // SPDX-License-Identifier: MIT 0012 ////////////////////////////////////////////////////////////////////////////// 0013 0014 #include "oxygenframeshadow.h" 0015 0016 #include <QAbstractScrollArea> 0017 #include <QApplication> 0018 #include <QDebug> 0019 #include <QFrame> 0020 #include <QMouseEvent> 0021 #include <QPainter> 0022 #include <QSplitter> 0023 0024 #include <KColorUtils> 0025 0026 namespace Oxygen 0027 { 0028 0029 //____________________________________________________________________________________ 0030 bool FrameShadowFactory::registerWidget(QWidget *widget, StyleHelper &helper) 0031 { 0032 if (!widget) 0033 return false; 0034 if (isRegistered(widget)) 0035 return false; 0036 0037 // check whether widget is a frame, and has the proper shape 0038 bool accepted = false; 0039 bool flat = false; 0040 0041 // cast to frame and check 0042 QFrame *frame(qobject_cast<QFrame *>(widget)); 0043 if (frame) { 0044 // also do not install on QSplitter 0045 /* 0046 due to Qt, splitters are set with a frame style that matches the condition below, 0047 though no shadow should be installed, obviously 0048 */ 0049 if (qobject_cast<QSplitter *>(widget)) 0050 return false; 0051 0052 // further checks on frame shape, and parent 0053 if (frame->frameStyle() == (QFrame::StyledPanel | QFrame::Sunken)) 0054 accepted = true; 0055 else if (widget->parent() && widget->parent()->inherits("QComboBoxPrivateContainer")) { 0056 accepted = true; 0057 flat = true; 0058 } 0059 0060 } else if (widget->inherits("KTextEditor::View")) 0061 accepted = true; 0062 0063 if (!accepted) 0064 return false; 0065 0066 // make sure that the widget is not embedded into a KHTMLView 0067 QWidget *parent(widget->parentWidget()); 0068 while (parent && !parent->isTopLevel()) { 0069 if (parent->inherits("KHTMLView")) 0070 return false; 0071 parent = parent->parentWidget(); 0072 } 0073 0074 // store in set 0075 _registeredWidgets.insert(widget); 0076 0077 // catch object destruction 0078 connect(widget, SIGNAL(destroyed(QObject *)), SLOT(widgetDestroyed(QObject *))); 0079 0080 // install shadow 0081 installShadows(widget, helper, flat); 0082 0083 return true; 0084 } 0085 0086 //____________________________________________________________________________________ 0087 void FrameShadowFactory::unregisterWidget(QWidget *widget) 0088 { 0089 if (!isRegistered(widget)) 0090 return; 0091 _registeredWidgets.remove(widget); 0092 removeShadows(widget); 0093 } 0094 0095 //____________________________________________________________________________________ 0096 bool FrameShadowFactory::eventFilter(QObject *object, QEvent *event) 0097 { 0098 switch (event->type()) { 0099 // TODO: possibly implement ZOrderChange event, to make sure that 0100 // the shadow is always painted on top 0101 case QEvent::ZOrderChange: { 0102 raiseShadows(object); 0103 break; 0104 } 0105 0106 case QEvent::Show: 0107 updateShadowsGeometry(object); 0108 update(object); 0109 break; 0110 0111 case QEvent::Resize: 0112 updateShadowsGeometry(object); 0113 break; 0114 0115 default: 0116 break; 0117 } 0118 0119 return QObject::eventFilter(object, event); 0120 } 0121 0122 //____________________________________________________________________________________ 0123 void FrameShadowFactory::installShadows(QWidget *widget, StyleHelper &helper, bool flat) 0124 { 0125 removeShadows(widget); 0126 0127 widget->installEventFilter(this); 0128 0129 widget->installEventFilter(&_addEventFilter); 0130 if (!flat) { 0131 installShadow(widget, helper, ShadowAreaLeft); 0132 installShadow(widget, helper, ShadowAreaRight); 0133 } 0134 0135 installShadow(widget, helper, ShadowAreaTop, flat); 0136 installShadow(widget, helper, ShadowAreaBottom, flat); 0137 widget->removeEventFilter(&_addEventFilter); 0138 } 0139 0140 //____________________________________________________________________________________ 0141 void FrameShadowFactory::removeShadows(QWidget *widget) 0142 { 0143 widget->removeEventFilter(this); 0144 0145 const QList<QObject *> children = widget->children(); 0146 for (QObject *child : children) { 0147 if (FrameShadowBase *shadow = qobject_cast<FrameShadowBase *>(child)) { 0148 shadow->hide(); 0149 shadow->setParent(nullptr); 0150 shadow->deleteLater(); 0151 } 0152 } 0153 } 0154 0155 //____________________________________________________________________________________ 0156 void FrameShadowFactory::updateShadowsGeometry(QObject *object) const 0157 { 0158 const QList<QObject *> children = object->children(); 0159 for (QObject *child : children) { 0160 if (FrameShadowBase *shadow = qobject_cast<FrameShadowBase *>(child)) { 0161 shadow->updateGeometry(); 0162 } 0163 } 0164 } 0165 0166 //____________________________________________________________________________________ 0167 void FrameShadowFactory::updateShadowsGeometry(const QObject *object, QRect rect) const 0168 { 0169 const QList<QObject *> children = object->children(); 0170 for (QObject *child : children) { 0171 if (FrameShadowBase *shadow = qobject_cast<FrameShadowBase *>(child)) { 0172 shadow->updateGeometry(rect); 0173 } 0174 } 0175 } 0176 0177 //____________________________________________________________________________________ 0178 void FrameShadowFactory::raiseShadows(QObject *object) const 0179 { 0180 const QList<QObject *> children = object->children(); 0181 for (QObject *child : children) { 0182 if (FrameShadowBase *shadow = qobject_cast<FrameShadowBase *>(child)) { 0183 shadow->raise(); 0184 } 0185 } 0186 } 0187 0188 //____________________________________________________________________________________ 0189 void FrameShadowFactory::update(QObject *object) const 0190 { 0191 const QList<QObject *> children = object->children(); 0192 for (QObject *child : children) { 0193 if (FrameShadowBase *shadow = qobject_cast<FrameShadowBase *>(child)) { 0194 shadow->update(); 0195 } 0196 } 0197 } 0198 0199 //____________________________________________________________________________________ 0200 void FrameShadowFactory::setHasContrast(const QWidget *widget, bool value) const 0201 { 0202 const QList<QObject *> children = widget->children(); 0203 for (QObject *child : children) { 0204 if (FrameShadowBase *shadow = qobject_cast<FrameShadowBase *>(child)) { 0205 shadow->setHasContrast(value); 0206 } 0207 } 0208 } 0209 0210 //____________________________________________________________________________________ 0211 void FrameShadowFactory::updateState(const QWidget *widget, bool focus, bool hover, qreal opacity, AnimationMode mode) const 0212 { 0213 const QList<QObject *> children = widget->children(); 0214 for (QObject *child : children) { 0215 if (FrameShadowBase *shadow = qobject_cast<FrameShadowBase *>(child)) { 0216 shadow->updateState(focus, hover, opacity, mode); 0217 } 0218 } 0219 } 0220 0221 //____________________________________________________________________________________ 0222 void FrameShadowFactory::installShadow(QWidget *widget, StyleHelper &helper, ShadowArea area, bool flat) const 0223 { 0224 FrameShadowBase *shadow(nullptr); 0225 if (flat) 0226 shadow = new FlatFrameShadow(area, helper); 0227 else 0228 shadow = new SunkenFrameShadow(area, helper); 0229 shadow->setParent(widget); 0230 shadow->hide(); 0231 } 0232 0233 //____________________________________________________________________________________ 0234 void FrameShadowFactory::widgetDestroyed(QObject *object) 0235 { 0236 _registeredWidgets.remove(object); 0237 } 0238 0239 //____________________________________________________________________________________ 0240 void FrameShadowBase::init() 0241 { 0242 setAttribute(Qt::WA_OpaquePaintEvent, false); 0243 0244 setFocusPolicy(Qt::NoFocus); 0245 setAttribute(Qt::WA_TransparentForMouseEvents, true); 0246 setContextMenuPolicy(Qt::NoContextMenu); 0247 0248 // grab viewport widget 0249 QWidget *viewport(FrameShadowBase::viewport()); 0250 if (!viewport && parentWidget()) { 0251 viewport = parentWidget(); 0252 } 0253 0254 // set cursor from viewport 0255 if (viewport) 0256 setCursor(viewport->cursor()); 0257 } 0258 0259 //____________________________________________________________________________________ 0260 QWidget *FrameShadowBase::viewport(void) const 0261 { 0262 if (!parentWidget()) 0263 return nullptr; 0264 else if (QAbstractScrollArea *widget = qobject_cast<QAbstractScrollArea *>(parentWidget())) { 0265 return widget->viewport(); 0266 0267 } else 0268 return nullptr; 0269 } 0270 0271 //____________________________________________________________________________________ 0272 void SunkenFrameShadow::updateGeometry(QRect rect) 0273 { 0274 // show on first call 0275 if (isHidden()) 0276 show(); 0277 0278 // store offsets between passed rect and parent widget rect 0279 QRect parentRect(parentWidget()->contentsRect()); 0280 setMargins( 0281 QMargins(rect.left() - parentRect.left(), rect.top() - parentRect.top(), rect.right() - parentRect.right(), rect.bottom() - parentRect.bottom())); 0282 0283 // adjust geometry to take out part that is not rendered anyway 0284 rect.adjust(1, 1, -1, -1); 0285 0286 // adjust geometry 0287 const int shadowSize(3); 0288 switch (shadowArea()) { 0289 case ShadowAreaTop: 0290 rect.setHeight(shadowSize); 0291 break; 0292 0293 case ShadowAreaBottom: 0294 rect.setTop(rect.bottom() - shadowSize + 1); 0295 break; 0296 0297 case ShadowAreaLeft: 0298 rect.setWidth(shadowSize); 0299 rect.adjust(0, shadowSize, 0, -shadowSize); 0300 break; 0301 0302 case ShadowAreaRight: 0303 rect.setLeft(rect.right() - shadowSize + 1); 0304 rect.adjust(0, shadowSize, 0, -shadowSize); 0305 break; 0306 0307 default: 0308 return; 0309 } 0310 0311 setGeometry(rect); 0312 } 0313 0314 //____________________________________________________________________________________ 0315 void SunkenFrameShadow::updateState(bool focus, bool hover, qreal opacity, AnimationMode mode) 0316 { 0317 bool changed(false); 0318 if (_hasFocus != focus) { 0319 _hasFocus = focus; 0320 changed |= true; 0321 } 0322 if (_mouseOver != hover) { 0323 _mouseOver = hover; 0324 changed |= !_hasFocus; 0325 } 0326 if (_mode != mode) { 0327 _mode = mode; 0328 changed |= (_mode == AnimationNone) || (_mode == AnimationFocus) || (_mode == AnimationHover && !_hasFocus); 0329 } 0330 0331 if (_opacity != opacity) { 0332 _opacity = opacity; 0333 changed |= (_mode != AnimationNone); 0334 } 0335 if (changed) { 0336 if (QWidget *viewport = this->viewport()) { 0337 // need to disable viewport updates to avoid some redundant painting 0338 // besides it fixes one visual glitch (from Qt) in QTableViews 0339 viewport->setUpdatesEnabled(false); 0340 update(); 0341 viewport->setUpdatesEnabled(true); 0342 0343 } else 0344 update(); 0345 } 0346 } 0347 0348 //____________________________________________________________________________________ 0349 void SunkenFrameShadow::paintEvent(QPaintEvent *event) 0350 { 0351 // this fixes shadows in frames that change frameStyle() after polish() 0352 if (QFrame *frame = qobject_cast<QFrame *>(parentWidget())) { 0353 if (frame->frameStyle() != (QFrame::StyledPanel | QFrame::Sunken)) 0354 return; 0355 } 0356 0357 const QRect parentRect(parentWidget()->contentsRect().translated(mapFromParent(QPoint(0, 0)))); 0358 const QRect rect(parentRect.adjusted(margins().left(), margins().top(), margins().right(), margins().bottom())); 0359 0360 // render 0361 QPainter painter(this); 0362 painter.setClipRegion(event->region()); 0363 0364 StyleOptions options(HoleOutline); 0365 if (_hasFocus) 0366 options |= Focus; 0367 if (_mouseOver) 0368 options |= Hover; 0369 if (hasContrast()) 0370 options |= HoleContrast; 0371 _helper.renderHole(&painter, palette().color(QPalette::Window), rect, options, _opacity, _mode, TileSet::Ring); 0372 0373 return; 0374 } 0375 0376 //____________________________________________________________________________________ 0377 void FlatFrameShadow::updateGeometry() 0378 { 0379 if (QWidget *widget = parentWidget()) 0380 updateGeometry(widget->contentsRect()); 0381 } 0382 0383 //____________________________________________________________________________________ 0384 void FlatFrameShadow::updateGeometry(QRect rect) 0385 { 0386 // show on first call 0387 if (isHidden()) 0388 show(); 0389 0390 // store offsets between passed rect and parent widget rect 0391 QRect parentRect(parentWidget()->contentsRect()); 0392 setMargins( 0393 QMargins(rect.left() - parentRect.left(), rect.top() - parentRect.top(), rect.right() - parentRect.right(), rect.bottom() - parentRect.bottom())); 0394 0395 const int shadowSize(3); 0396 switch (shadowArea()) { 0397 case ShadowAreaTop: 0398 rect.setHeight(shadowSize); 0399 break; 0400 0401 case ShadowAreaBottom: 0402 rect.setTop(rect.bottom() - shadowSize + 1); 0403 break; 0404 0405 default: 0406 return; 0407 } 0408 0409 setGeometry(rect); 0410 } 0411 0412 //____________________________________________________________________________________ 0413 void FlatFrameShadow::paintEvent(QPaintEvent *event) 0414 { 0415 // this fixes shadows in frames that change frameStyle() after polish() 0416 if (QFrame *frame = qobject_cast<QFrame *>(parentWidget())) { 0417 if (frame->frameStyle() != QFrame::NoFrame) 0418 return; 0419 } 0420 0421 const QWidget *parent(parentWidget()); 0422 const QRect parentRect(parent->contentsRect()); 0423 const QRect rect(parentRect.adjusted(margins().left(), margins().top(), margins().right(), margins().bottom())); 0424 0425 QPixmap pixmap(_helper.highDpiPixmap(size())); 0426 { 0427 pixmap.fill(Qt::transparent); 0428 QPainter painter(&pixmap); 0429 painter.setClipRegion(event->region()); 0430 painter.setRenderHints(QPainter::Antialiasing); 0431 painter.translate(-geometry().topLeft()); 0432 painter.setCompositionMode(QPainter::CompositionMode_DestinationOver); 0433 painter.setPen(Qt::NoPen); 0434 _helper.renderMenuBackground(&painter, geometry(), parent, parent->palette()); 0435 0436 // mask 0437 painter.setCompositionMode(QPainter::CompositionMode_DestinationOut); 0438 painter.setBrush(Qt::black); 0439 painter.drawRoundedRect(QRectF(rect), 2.5, 2.5); 0440 } 0441 0442 QPainter painter(this); 0443 painter.setClipRegion(event->region()); 0444 painter.fillRect(rect, Qt::transparent); 0445 painter.drawPixmap(QPoint(0, 0), pixmap); 0446 0447 return; 0448 } 0449 }