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