1 ////////////////////////////////////////////////////////////////////////////// 2 // oxygenframeshadow.h 3 // handle sunken frames' shadows 4 // ------------------- 5 // 6 // SPDX-FileCopyrightText: 2010 Hugo Pereira Da Costa <hugo.pereira@free.fr> 7 // 8 // Largely inspired from skulpture widget style 9 // SPDX-FileCopyrightText: 2007-2009 Christoph Feck <christoph@maxiom.de> 10 // 11 // SPDX-License-Identifier: MIT 12 ////////////////////////////////////////////////////////////////////////////// 13 14 #include "oxygenframeshadow.h" 15 16 #include <QDebug> 17 #include <QAbstractScrollArea> 18 #include <QApplication> 19 #include <QFrame> 20 #include <QMouseEvent> 21 #include <QPainter> 22 #include <QSplitter> 23 24 #include <KColorUtils> 25 26 namespace Oxygen 27 { 28 29 //____________________________________________________________________________________ registerWidget(QWidget * widget,StyleHelper & helper)30 bool FrameShadowFactory::registerWidget( QWidget* widget, StyleHelper& helper ) 31 { 32 33 if( !widget ) return false; 34 if( isRegistered( widget ) ) return false; 35 36 // check whether widget is a frame, and has the proper shape 37 bool accepted = false; 38 bool flat = false; 39 40 // cast to frame and check 41 QFrame* frame( qobject_cast<QFrame*>( widget ) ); 42 if( frame ) 43 { 44 // also do not install on QSplitter 45 /* 46 due to Qt, splitters are set with a frame style that matches the condition below, 47 though no shadow should be installed, obviously 48 */ 49 if( qobject_cast<QSplitter*>( widget ) ) return false; 50 51 // further checks on frame shape, and parent 52 if( frame->frameStyle() == (QFrame::StyledPanel | QFrame::Sunken) ) accepted = true; 53 else if( widget->parent() && widget->parent()->inherits( "QComboBoxPrivateContainer" ) ) 54 { 55 56 accepted = true; 57 flat = true; 58 59 } 60 61 } else if( widget->inherits( "KTextEditor::View" ) ) accepted = true; 62 63 if( !accepted ) return false; 64 65 // make sure that the widget is not embedded into a KHTMLView 66 QWidget* parent( widget->parentWidget() ); 67 while( parent && !parent->isTopLevel() ) 68 { 69 if( parent->inherits( "KHTMLView" ) ) return false; 70 parent = parent->parentWidget(); 71 } 72 73 // store in set 74 _registeredWidgets.insert( widget ); 75 76 // catch object destruction 77 connect( widget, SIGNAL(destroyed(QObject*)), SLOT(widgetDestroyed(QObject*)) ); 78 79 // install shadow 80 installShadows( widget, helper, flat ); 81 82 return true; 83 84 } 85 86 //____________________________________________________________________________________ unregisterWidget(QWidget * widget)87 void FrameShadowFactory::unregisterWidget( QWidget* widget ) 88 { 89 if( !isRegistered( widget ) ) return; 90 _registeredWidgets.remove( widget ); 91 removeShadows( widget ); 92 } 93 94 //____________________________________________________________________________________ eventFilter(QObject * object,QEvent * event)95 bool FrameShadowFactory::eventFilter( QObject* object, QEvent* event ) 96 { 97 98 switch( event->type() ) 99 { 100 // TODO: possibly implement ZOrderChange event, to make sure that 101 // the shadow is always painted on top 102 case QEvent::ZOrderChange: 103 { 104 raiseShadows( object ); 105 break; 106 } 107 108 case QEvent::Show: 109 updateShadowsGeometry( object ); 110 update( object ); 111 break; 112 113 case QEvent::Resize: 114 updateShadowsGeometry( object ); 115 break; 116 117 default: break; 118 } 119 120 return QObject::eventFilter( object, event ); 121 122 } 123 124 //____________________________________________________________________________________ installShadows(QWidget * widget,StyleHelper & helper,bool flat)125 void FrameShadowFactory::installShadows( QWidget* widget, StyleHelper& helper, bool flat ) 126 { 127 128 removeShadows(widget); 129 130 widget->installEventFilter(this); 131 132 widget->installEventFilter( &_addEventFilter ); 133 if( !flat ) 134 { 135 installShadow( widget, helper, ShadowAreaLeft ); 136 installShadow( widget, helper, ShadowAreaRight ); 137 } 138 139 installShadow( widget, helper, ShadowAreaTop, flat ); 140 installShadow( widget, helper, ShadowAreaBottom, flat ); 141 widget->removeEventFilter( &_addEventFilter ); 142 143 } 144 145 //____________________________________________________________________________________ removeShadows(QWidget * widget)146 void FrameShadowFactory::removeShadows( QWidget* widget ) 147 { 148 149 widget->removeEventFilter( this ); 150 151 const QList<QObject* > children = widget->children(); 152 foreach( QObject *child, children ) 153 { 154 if( FrameShadowBase* shadow = qobject_cast<FrameShadowBase*>(child) ) 155 { 156 shadow->hide(); 157 shadow->setParent(0); 158 shadow->deleteLater(); 159 } 160 } 161 162 } 163 164 //____________________________________________________________________________________ updateShadowsGeometry(QObject * object) const165 void FrameShadowFactory::updateShadowsGeometry( QObject* object ) const 166 { 167 168 const QList<QObject *> children = object->children(); 169 foreach( QObject *child, children ) 170 { 171 if( FrameShadowBase* shadow = qobject_cast<FrameShadowBase *>(child) ) 172 { shadow->updateGeometry(); } 173 } 174 175 } 176 177 //____________________________________________________________________________________ updateShadowsGeometry(const QObject * object,QRect rect) const178 void FrameShadowFactory::updateShadowsGeometry( const QObject* object, QRect rect ) const 179 { 180 181 const QList<QObject *> children = object->children(); 182 foreach( QObject *child, children ) 183 { 184 if( FrameShadowBase* shadow = qobject_cast<FrameShadowBase *>(child) ) 185 { shadow->updateGeometry( rect ); } 186 } 187 188 } 189 190 //____________________________________________________________________________________ raiseShadows(QObject * object) const191 void FrameShadowFactory::raiseShadows( QObject* object ) const 192 { 193 194 const QList<QObject *> children = object->children(); 195 foreach( QObject *child, children ) 196 { 197 if( FrameShadowBase* shadow = qobject_cast<FrameShadowBase *>(child) ) 198 { shadow->raise(); } 199 } 200 201 } 202 203 //____________________________________________________________________________________ update(QObject * object) const204 void FrameShadowFactory::update( QObject* object ) const 205 { 206 207 const QList<QObject* > children = object->children(); 208 foreach( QObject *child, children ) 209 { 210 if( FrameShadowBase* shadow = qobject_cast<FrameShadowBase *>(child) ) 211 { shadow->update();} 212 } 213 214 } 215 216 //____________________________________________________________________________________ setHasContrast(const QWidget * widget,bool value) const217 void FrameShadowFactory::setHasContrast( const QWidget* widget, bool value ) const 218 { 219 220 const QList<QObject *> children = widget->children(); 221 foreach( QObject *child, children ) 222 { 223 if( FrameShadowBase* shadow = qobject_cast<FrameShadowBase *>(child) ) 224 { shadow->setHasContrast( value ); } 225 } 226 227 } 228 229 //____________________________________________________________________________________ updateState(const QWidget * widget,bool focus,bool hover,qreal opacity,AnimationMode mode) const230 void FrameShadowFactory::updateState( const QWidget* widget, bool focus, bool hover, qreal opacity, AnimationMode mode ) const 231 { 232 233 const QList<QObject *> children = widget->children(); 234 foreach( QObject *child, children ) 235 { 236 if( FrameShadowBase* shadow = qobject_cast<FrameShadowBase *>(child) ) 237 { shadow->updateState( focus, hover, opacity, mode ); } 238 } 239 240 } 241 242 //____________________________________________________________________________________ installShadow(QWidget * widget,StyleHelper & helper,ShadowArea area,bool flat) const243 void FrameShadowFactory::installShadow( QWidget* widget, StyleHelper& helper, ShadowArea area, bool flat ) const 244 { 245 FrameShadowBase *shadow(0); 246 if( flat ) shadow = new FlatFrameShadow( area, helper ); 247 else shadow = new SunkenFrameShadow( area, helper ); 248 shadow->setParent(widget); 249 shadow->hide(); 250 } 251 252 //____________________________________________________________________________________ widgetDestroyed(QObject * object)253 void FrameShadowFactory::widgetDestroyed( QObject* object ) 254 { _registeredWidgets.remove( object ); } 255 256 //____________________________________________________________________________________ init()257 void FrameShadowBase::init() 258 { 259 260 setAttribute(Qt::WA_OpaquePaintEvent, false); 261 262 setFocusPolicy(Qt::NoFocus); 263 setAttribute(Qt::WA_TransparentForMouseEvents, true); 264 setContextMenuPolicy(Qt::NoContextMenu); 265 266 // grab viewport widget 267 QWidget *viewport( FrameShadowBase::viewport() ); 268 if( !viewport && parentWidget() ) 269 { viewport = parentWidget(); } 270 271 // set cursor from viewport 272 if (viewport) setCursor(viewport->cursor()); 273 274 } 275 276 //____________________________________________________________________________________ viewport(void) const277 QWidget* FrameShadowBase::viewport( void ) const 278 { 279 280 if( !parentWidget() ) return nullptr; 281 else if( QAbstractScrollArea *widget = qobject_cast<QAbstractScrollArea *>(parentWidget()) ) { 282 283 return widget->viewport(); 284 285 } else return nullptr; 286 287 } 288 289 //____________________________________________________________________________________ updateGeometry(QRect rect)290 void SunkenFrameShadow::updateGeometry( QRect rect ) 291 { 292 293 // show on first call 294 if( isHidden() ) show(); 295 296 // store offsets between passed rect and parent widget rect 297 QRect parentRect( parentWidget()->contentsRect() ); 298 setMargins( QMargins( 299 rect.left() - parentRect.left(), 300 rect.top() - parentRect.top(), 301 rect.right() - parentRect.right(), 302 rect.bottom() - parentRect.bottom() ) ); 303 304 // adjust geometry to take out part that is not rendered anyway 305 rect.adjust( 1, 1, -1, -1 ); 306 307 // adjust geometry 308 const int shadowSize( 3 ); 309 switch( shadowArea() ) 310 { 311 312 case ShadowAreaTop: 313 rect.setHeight( shadowSize ); 314 break; 315 316 case ShadowAreaBottom: 317 rect.setTop( rect.bottom() - shadowSize + 1 ); 318 break; 319 320 case ShadowAreaLeft: 321 rect.setWidth(shadowSize); 322 rect.adjust(0, shadowSize, 0, -shadowSize ); 323 break; 324 325 326 case ShadowAreaRight: 327 rect.setLeft(rect.right() - shadowSize + 1 ); 328 rect.adjust(0, shadowSize, 0, -shadowSize ); 329 break; 330 331 default: 332 return; 333 } 334 335 setGeometry(rect); 336 337 } 338 339 //____________________________________________________________________________________ updateState(bool focus,bool hover,qreal opacity,AnimationMode mode)340 void SunkenFrameShadow::updateState( bool focus, bool hover, qreal opacity, AnimationMode mode ) 341 { 342 bool changed( false ); 343 if( _hasFocus != focus ) { _hasFocus = focus; changed |= true; } 344 if( _mouseOver != hover ) { _mouseOver = hover; changed |= !_hasFocus; } 345 if( _mode != mode ) 346 { 347 348 _mode = mode; 349 changed |= 350 (_mode == AnimationNone) || 351 (_mode == AnimationFocus) || 352 (_mode == AnimationHover && !_hasFocus ); 353 354 } 355 356 if( _opacity != opacity ) { _opacity = opacity; changed |= (_mode != AnimationNone ); } 357 if( changed ) 358 { 359 360 if( QWidget* viewport = this->viewport() ) 361 { 362 363 // need to disable viewport updates to avoid some redundant painting 364 // besides it fixes one visual glitch (from Qt) in QTableViews 365 viewport->setUpdatesEnabled( false ); 366 update() ; 367 viewport->setUpdatesEnabled( true ); 368 369 } else update(); 370 371 } 372 } 373 374 //____________________________________________________________________________________ paintEvent(QPaintEvent * event)375 void SunkenFrameShadow::paintEvent(QPaintEvent *event ) 376 { 377 378 // this fixes shadows in frames that change frameStyle() after polish() 379 if( QFrame *frame = qobject_cast<QFrame *>( parentWidget() ) ) 380 { if (frame->frameStyle() != (QFrame::StyledPanel | QFrame::Sunken)) return; } 381 382 const QRect parentRect( parentWidget()->contentsRect().translated( mapFromParent( QPoint( 0, 0 ) ) ) ); 383 const QRect rect( parentRect.adjusted( margins().left(), margins().top(), margins().right(), margins().bottom() ) ); 384 385 // render 386 QPainter painter(this); 387 painter.setClipRegion( event->region() ); 388 389 StyleOptions options( HoleOutline ); 390 if( _hasFocus ) options |= Focus; 391 if( _mouseOver ) options |= Hover; 392 if( hasContrast() ) options |= HoleContrast; 393 _helper.renderHole( &painter, palette().color( QPalette::Window ), rect, options, _opacity, _mode, TileSet::Ring ); 394 395 return; 396 397 } 398 399 //____________________________________________________________________________________ updateGeometry()400 void FlatFrameShadow::updateGeometry() 401 { if( QWidget *widget = parentWidget() ) updateGeometry( widget->contentsRect() ); } 402 403 //____________________________________________________________________________________ updateGeometry(QRect rect)404 void FlatFrameShadow::updateGeometry( QRect rect ) 405 { 406 407 // show on first call 408 if( isHidden() ) show(); 409 410 // store offsets between passed rect and parent widget rect 411 QRect parentRect( parentWidget()->contentsRect() ); 412 setMargins( QMargins( 413 rect.left() - parentRect.left(), 414 rect.top() - parentRect.top(), 415 rect.right() - parentRect.right(), 416 rect.bottom() - parentRect.bottom() ) ); 417 418 const int shadowSize( 3 ); 419 switch( shadowArea() ) 420 { 421 422 case ShadowAreaTop: 423 rect.setHeight( shadowSize ); 424 break; 425 426 case ShadowAreaBottom: 427 rect.setTop( rect.bottom() - shadowSize + 1 ); 428 break; 429 430 default: 431 return; 432 } 433 434 setGeometry(rect); 435 } 436 437 438 //____________________________________________________________________________________ paintEvent(QPaintEvent * event)439 void FlatFrameShadow::paintEvent(QPaintEvent *event ) 440 { 441 442 // this fixes shadows in frames that change frameStyle() after polish() 443 if( QFrame *frame = qobject_cast<QFrame *>( parentWidget() ) ) 444 { if( frame->frameStyle() != QFrame::NoFrame ) return; } 445 446 const QWidget* parent( parentWidget() ); 447 const QRect parentRect( parent->contentsRect() ); 448 const QRect rect( parentRect.adjusted( margins().left(), margins().top(), margins().right(), margins().bottom() ) ); 449 450 QPixmap pixmap( _helper.highDpiPixmap( size() ) ); 451 { 452 453 pixmap.fill( Qt::transparent ); 454 QPainter painter( &pixmap ); 455 painter.setClipRegion( event->region() ); 456 painter.setRenderHints( QPainter::Antialiasing ); 457 painter.translate( -geometry().topLeft() ); 458 painter.setCompositionMode(QPainter::CompositionMode_DestinationOver); 459 painter.setPen( Qt::NoPen ); 460 _helper.renderMenuBackground( &painter, geometry(), parent, parent->palette() ); 461 462 // mask 463 painter.setCompositionMode(QPainter::CompositionMode_DestinationOut); 464 painter.setBrush( Qt::black ); 465 painter.drawRoundedRect( QRectF(rect), 2.5, 2.5 ); 466 467 } 468 469 QPainter painter( this ); 470 painter.setClipRegion( event->region() ); 471 painter.fillRect( rect, Qt::transparent ); 472 painter.drawPixmap( QPoint(0,0), pixmap ); 473 474 return; 475 476 } 477 478 } 479