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