1 /*
2   overlaywidget.cpp
3 
4   This file is part of GammaRay, the Qt application inspection and
5   manipulation tool.
6 
7   Copyright (C) 2010-2021 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
8   Author: Tobias Koenig <tobias.koenig@kdab.com>
9 
10   Licensees holding valid commercial KDAB GammaRay licenses may use this file in
11   accordance with GammaRay Commercial License Agreement provided with the Software.
12 
13   Contact info@kdab.com if any conditions of this licensing are not clear to you.
14 
15   This program is free software; you can redistribute it and/or modify
16   it under the terms of the GNU General Public License as published by
17   the Free Software Foundation, either version 2 of the License, or
18   (at your option) any later version.
19 
20   This program is distributed in the hope that it will be useful,
21   but WITHOUT ANY WARRANTY; without even the implied warranty of
22   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23   GNU General Public License for more details.
24 
25   You should have received a copy of the GNU General Public License
26   along with this program.  If not, see <http://www.gnu.org/licenses/>.
27 */
28 
29 #include "overlaywidget.h"
30 
31 #include <QDebug>
32 #include <QDialog>
33 #include <QEvent>
34 #include <QLayout>
35 #include <QPainter>
36 #include <QSplitter>
37 
38 using namespace GammaRay;
39 
toplevelWidget(QWidget * widget)40 static QWidget *toplevelWidget(QWidget *widget)
41 {
42     Q_ASSERT(widget);
43     QWidget *parent = widget;
44     auto isTopLevel = [](QWidget *widget) {
45         return widget->isWindow();
46     };
47     auto lastSuitableParent = parent;
48     while (parent->parentWidget() &&
49             !isTopLevel(parent->parentWidget()) &&
50             !isTopLevel(parent)) {
51         parent = parent->parentWidget();
52 
53         // don't pick parents that can't take the overlay as a children
54         if (!qobject_cast<QSplitter*>(parent)) {
55             lastSuitableParent = parent;
56         }
57     }
58 
59     return lastSuitableParent;
60 }
61 
OverlayWidget()62 OverlayWidget::OverlayWidget()
63   : m_currentToplevelWidget(nullptr),
64     m_drawLayoutOutlineOnly(true)
65 {
66     setAttribute(Qt::WA_TransparentForMouseEvents);
67     setFocusPolicy(Qt::NoFocus);
68 }
69 
placeOn(const WidgetOrLayoutFacade & item)70 void OverlayWidget::placeOn(const WidgetOrLayoutFacade &item)
71 {
72     if (item.isNull()) {
73         if (!m_currentItem.isNull())
74             m_currentItem->removeEventFilter(this);
75 
76         if (m_currentToplevelWidget)
77             m_currentToplevelWidget->removeEventFilter(this);
78 
79         m_currentToplevelWidget = nullptr;
80         m_currentItem.clear();
81         m_outerRect = QRect();
82         m_layoutPath = QPainterPath();
83 
84         update();
85         return;
86     }
87 
88     if (!m_currentItem.isNull())
89         m_currentItem->removeEventFilter(this);
90 
91     m_currentItem = item;
92 
93     QWidget *toplevel = toplevelWidget(item.widget());
94     Q_ASSERT(toplevel);
95 
96     if (toplevel != m_currentToplevelWidget) {
97         if (m_currentToplevelWidget)
98             m_currentToplevelWidget->removeEventFilter(this);
99 
100         m_currentToplevelWidget = toplevel;
101 
102         setParent(toplevel);
103         move(0, 0);
104         resize(toplevel->size());
105 
106         m_currentToplevelWidget->installEventFilter(this);
107 
108         show();
109     }
110 
111     m_currentItem->installEventFilter(this);
112 
113     updatePositions();
114 }
115 
eventFilter(QObject * receiver,QEvent * event)116 bool OverlayWidget::eventFilter(QObject *receiver, QEvent *event)
117 {
118     if (!m_currentItem.isNull() && m_currentToplevelWidget != m_currentItem.widget()->window()) { // detect (un)docking
119         placeOn(m_currentItem);
120         return false;
121     }
122 
123     if (receiver == m_currentItem.data()) {
124         if (event->type() == QEvent::Resize || event->type() == QEvent::Move || event->type() == QEvent::Show || event->type() == QEvent::Hide) {
125             resizeOverlay();
126             updatePositions();
127         }
128     } else if (receiver == m_currentToplevelWidget) {
129         if (event->type() == QEvent::Resize) {
130             resizeOverlay();
131             updatePositions();
132         }
133     }
134 
135     return false;
136 }
137 
updatePositions()138 void OverlayWidget::updatePositions()
139 {
140     if (m_currentItem.isNull() || !m_currentToplevelWidget)
141         return;
142 
143     if (!m_currentItem.isVisible())
144         m_outerRectColor = Qt::green;
145     else
146         m_outerRectColor = Qt::red;
147 
148     const QPoint parentPos = m_currentItem.widget()->mapTo(m_currentToplevelWidget, m_currentItem.pos());
149     m_outerRect = QRect(parentPos.x(), parentPos.y(),
150                         m_currentItem.geometry().width(),
151                         m_currentItem.geometry().height()).adjusted(0, 0, -1, -1);
152 
153     m_layoutPath = QPainterPath();
154 
155     if (m_currentItem.layout()
156         && qstrcmp(m_currentItem.layout()->metaObject()->className(),
157                    "QMainWindowLayout") != 0) {
158         const QRect layoutGeometry = m_currentItem.layout()->geometry();
159 
160         const QRect mappedOuterRect
161             = QRect(m_currentItem.widget()->mapTo(m_currentToplevelWidget,
162                                                   layoutGeometry.topLeft()), layoutGeometry.size());
163 
164         QPainterPath outerPath;
165         outerPath.addRect(mappedOuterRect.adjusted(1, 1, -2, -2));
166 
167         QPainterPath innerPath;
168         for (int i = 0; i < m_currentItem.layout()->count(); ++i) {
169             QLayoutItem *item = m_currentItem.layout()->itemAt(i);
170             if (item->widget() && !item->widget()->isVisible())
171                 continue;
172             const QRect mappedInnerRect
173                 = QRect(m_currentItem.widget()->mapTo(m_currentToplevelWidget,
174                                                       item->geometry().topLeft()),
175                         item->geometry().size());
176             innerPath.addRect(mappedInnerRect);
177         }
178 
179         m_layoutPath.setFillRule(Qt::OddEvenFill);
180         m_layoutPath = outerPath.subtracted(innerPath);
181 
182         if (m_layoutPath.isEmpty()) {
183             m_layoutPath = outerPath;
184             m_layoutPath.addPath(innerPath);
185             m_drawLayoutOutlineOnly = true;
186         } else {
187             m_drawLayoutOutlineOnly = false;
188         }
189     }
190 
191     update();
192 }
193 
resizeOverlay()194 void OverlayWidget::resizeOverlay()
195 {
196     if (m_currentToplevelWidget) {
197         move(0, 0);
198         resize(m_currentToplevelWidget->size());
199     }
200 }
201 
paintEvent(QPaintEvent *)202 void OverlayWidget::paintEvent(QPaintEvent *)
203 {
204     QPainter p(this);
205     p.setPen(m_outerRectColor);
206     p.drawRect(m_outerRect);
207 
208     QBrush brush(Qt::BDiagPattern);
209     brush.setColor(Qt::blue);
210 
211     if (!m_drawLayoutOutlineOnly)
212         p.fillPath(m_layoutPath, brush);
213 
214     p.setPen(Qt::blue);
215     p.drawPath(m_layoutPath);
216 }
217