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