1 /*
2  * SPDX-FileCopyrightText: 2014 Hugo Pereira Da Costa <hugo.pereira@free.fr>
3  *
4  * SPDX-License-Identifier: GPL-2.0-or-later
5  */
6 
7 #include "breezemdiwindowshadow.h"
8 
9 #include "breezemetrics.h"
10 #include "breezeboxshadowrenderer.h"
11 #include "breezeshadowhelper.h"
12 #include "breezestyleconfigdata.h"
13 
14 #include <QMdiArea>
15 #include <QMdiSubWindow>
16 #include <QPainter>
17 #include <QTextStream>
18 
19 namespace Breeze
20 {
21 
22     //____________________________________________________________________
MdiWindowShadow(QWidget * parent,const TileSet & shadowTiles)23     MdiWindowShadow::MdiWindowShadow( QWidget* parent, const TileSet &shadowTiles ):
24         QWidget( parent ),
25         _shadowTiles( shadowTiles )
26     {
27         setAttribute( Qt::WA_OpaquePaintEvent, false );
28         setAttribute( Qt::WA_TransparentForMouseEvents, true );
29         setFocusPolicy( Qt::NoFocus );
30     }
31 
32     //____________________________________________________________________
updateGeometry()33     void MdiWindowShadow::updateGeometry()
34     {
35         if( !_widget ) return;
36 
37         // metrics
38         const CompositeShadowParams params = ShadowHelper::lookupShadowParams( StyleConfigData::shadowSize() );
39         if( params.isNone() ) return;
40 
41         const QSize boxSize = BoxShadowRenderer::calculateMinimumBoxSize(params.shadow1.radius)
42             .expandedTo(BoxShadowRenderer::calculateMinimumBoxSize(params.shadow2.radius));
43 
44         const QSize shadowSize = BoxShadowRenderer::calculateMinimumShadowTextureSize(boxSize, params.shadow1.radius, params.shadow1.offset)
45             .expandedTo(BoxShadowRenderer::calculateMinimumShadowTextureSize(boxSize, params.shadow2.radius, params.shadow2.offset));
46 
47         const QRect shadowRect(QPoint(0, 0), shadowSize);
48 
49         QRect boxRect(QPoint(0, 0), boxSize);
50         boxRect.moveCenter(shadowRect.center());
51 
52         const int topSize( boxRect.top() - shadowRect.top() - Metrics::Shadow_Overlap - params.offset.y() );
53         const int bottomSize( shadowRect.bottom() - boxRect.bottom() - Metrics::Shadow_Overlap + params.offset.y() );
54         const int leftSize( boxRect.left() - shadowRect.left() - Metrics::Shadow_Overlap - params.offset.x() );
55         const int rightSize( shadowRect.right() - boxRect.right() - Metrics::Shadow_Overlap + params.offset.x() );
56 
57         // get tileSet rect
58         auto hole = _widget->frameGeometry();
59         _shadowTilesRect = hole.adjusted( -leftSize, -topSize, rightSize, bottomSize );
60 
61         // get parent MDI area's viewport
62         auto parent( parentWidget() );
63         if (parent && !qobject_cast<QMdiArea *>(parent) && qobject_cast<QMdiArea*>(parent->parentWidget()))
64         { parent = parent->parentWidget(); }
65 
66         if( qobject_cast<QAbstractScrollArea *>( parent ) )
67         { parent = qobject_cast<QAbstractScrollArea *>( parent )->viewport(); }
68 
69         // set geometry
70         QRect geometry( _shadowTilesRect );
71         if( parent )
72         {
73             geometry &= parent->rect();
74             hole &= parent->rect();
75         }
76 
77         // update geometry and mask
78         const QRegion mask = QRegion( geometry ) - hole.adjusted( 2, 2, -2, -2 );
79         if( mask.isEmpty() ) hide();
80         else {
81 
82             setGeometry( geometry );
83             setMask( mask.translated( -geometry.topLeft() ) );
84             show();
85 
86         }
87 
88         // translate rendering rect
89         _shadowTilesRect.translate( -geometry.topLeft() );
90 
91     }
92 
93     //____________________________________________________________________
updateZOrder()94     void MdiWindowShadow::updateZOrder()
95     { stackUnder( _widget ); }
96 
97     //____________________________________________________________________
paintEvent(QPaintEvent * event)98     void MdiWindowShadow::paintEvent( QPaintEvent* event )
99     {
100 
101         if( !_shadowTiles.isValid() ) return;
102 
103         QPainter painter( this );
104         painter.setRenderHints( QPainter::Antialiasing );
105         painter.setClipRegion( event->region() );
106         _shadowTiles.render( _shadowTilesRect, &painter );
107 
108     }
109 
110     //____________________________________________________________________
MdiWindowShadowFactory(QObject * parent)111     MdiWindowShadowFactory::MdiWindowShadowFactory( QObject* parent ):
112         QObject( parent )
113     {}
114 
115     //____________________________________________________________________________________
registerWidget(QWidget * widget)116     bool MdiWindowShadowFactory::registerWidget( QWidget* widget )
117     {
118 
119         // check widget type
120         auto subwindow( qobject_cast<QMdiSubWindow*>( widget ) );
121         if( !subwindow ) return false;
122         if( subwindow->widget() && subwindow->widget()->inherits( "KMainWindow" ) ) return false;
123 
124         // make sure widget is not already registered
125         if( isRegistered( widget ) ) return false;
126 
127         // store in set
128         _registeredWidgets.insert( widget );
129 
130         // create shadow immediately if widget is already visible
131         if( widget->isVisible() )
132         {
133             installShadow( widget );
134             updateShadowGeometry( widget );
135             updateShadowZOrder( widget );
136         }
137 
138         widget->installEventFilter( this );
139 
140         // catch object destruction
141         connect( widget, &QObject::destroyed, this, &MdiWindowShadowFactory::widgetDestroyed );
142 
143         return true;
144 
145     }
146 
147     //____________________________________________________________________________________
unregisterWidget(QWidget * widget)148     void MdiWindowShadowFactory::unregisterWidget( QWidget* widget )
149     {
150         if( !isRegistered( widget ) ) return;
151         widget->removeEventFilter( this );
152         _registeredWidgets.remove( widget );
153         removeShadow( widget );
154     }
155 
156     //____________________________________________________________________________________
eventFilter(QObject * object,QEvent * event)157     bool MdiWindowShadowFactory::eventFilter( QObject* object, QEvent* event )
158     {
159 
160         switch( event->type() )
161         {
162             // TODO: possibly implement ZOrderChange event, to make sure that
163             // the shadow is always painted on top
164             case QEvent::ZOrderChange:
165             updateShadowZOrder( object );
166             break;
167 
168             case QEvent::Hide:
169             hideShadows( object );
170             break;
171 
172             case QEvent::Show:
173             installShadow( object );
174             updateShadowGeometry( object );
175             updateShadowZOrder( object );
176             break;
177 
178             case QEvent::Move:
179             case QEvent::Resize:
180             updateShadowGeometry( object );
181             break;
182 
183             default: break;
184         }
185 
186         return QObject::eventFilter( object, event );
187 
188     }
189 
190     //____________________________________________________________________________________
findShadow(QObject * object) const191     MdiWindowShadow* MdiWindowShadowFactory::findShadow( QObject* object ) const
192     {
193 
194         // check object,
195         if( !object->parent() ) return nullptr;
196 
197         // find existing window shadows
198         auto children = object->parent()->children();
199         foreach( QObject *child, children )
200         {
201             if( MdiWindowShadow* shadow = qobject_cast<MdiWindowShadow*>(child) )
202             { if( shadow->widget() == object ) return shadow; }
203         }
204 
205         return nullptr;
206 
207     }
208 
209     //____________________________________________________________________________________
installShadow(QObject * object)210     void MdiWindowShadowFactory::installShadow( QObject* object )
211     {
212 
213         // cast
214         auto widget( static_cast<QWidget*>( object ) );
215         if( !widget->parentWidget() ) return;
216 
217         // make sure shadow is not already installed
218         if( findShadow( object ) ) return;
219 
220         if ( !_shadowHelper ) return;
221 
222         // create new shadow
223         auto windowShadow( new MdiWindowShadow( widget->parentWidget(), _shadowHelper->shadowTiles() ) );
224         windowShadow->setWidget( widget );
225 
226     }
227 
228     //____________________________________________________________________________________
removeShadow(QObject * object)229     void MdiWindowShadowFactory::removeShadow( QObject* object )
230     {
231         if( MdiWindowShadow* windowShadow = findShadow( object ) )
232         {
233             windowShadow->hide();
234             windowShadow->deleteLater();
235         }
236     }
237 
238     //____________________________________________________________________________________
widgetDestroyed(QObject * object)239     void MdiWindowShadowFactory::widgetDestroyed( QObject* object )
240     {
241         _registeredWidgets.remove( object );
242         removeShadow( object );
243     }
244 
245 }
246