1 /*
2   sgwireframewidget.cpp
3 
4   This file is part of GammaRay, the Qt application inspection and
5   manipulation tool.
6 
7   Copyright (C) 2014-2021 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
8   Author: Anton Kreuzkamp <anton.kreuzkamp@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 "sgwireframewidget.h"
30 #include "sggeometrymodel.h"
31 
32 #include <QApplication>
33 #include <QPainter>
34 #include <QMouseEvent>
35 #include <QItemSelectionModel>
36 
37 using namespace GammaRay;
38 
SGWireframeWidget(QWidget * parent,Qt::WindowFlags f)39 SGWireframeWidget::SGWireframeWidget(QWidget *parent, Qt::WindowFlags f)
40     : QWidget(parent, f)
41     , m_vertexModel(nullptr)
42     , m_adjacencyModel(nullptr)
43     , m_highlightModel(nullptr)
44     , m_positionColumn(-1)
45     , m_drawingMode(0)
46     , m_geometryWidth(0)
47     , m_geometryHeight(0)
48     , m_zoom(1)
49     , m_offset(10, 10)
50 {
51 }
52 
53 SGWireframeWidget::~SGWireframeWidget() = default;
54 
paintEvent(QPaintEvent *)55 void SGWireframeWidget::paintEvent(QPaintEvent *)
56 {
57     if (!m_vertexModel || m_vertices.isEmpty() || m_positionColumn == -1)
58         return;
59 
60     // Prepare painting
61     m_zoom = qMin((width() - 20) / m_geometryWidth, (height() - 20) / m_geometryHeight);
62 
63     QPainter painter(this);
64     painter.setRenderHint(QPainter::Antialiasing);
65     painter.setPen(qApp->palette().color(QPalette::WindowText));
66     painter.setBrush(QBrush(Qt::black, Qt::SolidPattern));
67 
68     // Paint
69     for (int i = 0; i < m_adjacencyList.size(); i++) {
70         // Calculate the index of the vertex we're supposed to draw a line from
71         const int index = m_adjacencyList[i];
72 
73         if (index >= m_vertices.size())
74             continue;
75 
76         // Draw highlighted faces
77         if ((m_drawingMode == GL_TRIANGLES && i % 3 == 2)
78             || (m_drawingMode == GL_TRIANGLE_STRIP && i >= 2)) {
79             drawHighlightedFace(&painter, QVector<int>() << index << m_adjacencyList[i - 1] << m_adjacencyList[i - 2]);
80         } else if (m_drawingMode == GL_TRIANGLE_FAN && i >= 2) {
81             drawHighlightedFace(&painter, QVector<int>() << index << m_adjacencyList[i - 1] << m_adjacencyList.first());
82         }
83 
84 #ifndef QT_OPENGL_ES_2
85         else if ((m_drawingMode == GL_QUADS || m_drawingMode == GL_QUAD_STRIP) && i % 4 == 3) {
86             drawHighlightedFace(&painter,
87                                 QVector<int>() << index << m_adjacencyList[i - 1] << m_adjacencyList[i - 2] << m_adjacencyList[i - 3]);
88         } else if (m_drawingMode == GL_POLYGON && i == m_adjacencyList.size() - 1) {
89             QVector<int> vertices;
90             vertices.reserve(m_adjacencyList.size());
91             for (int j = 0; j < m_adjacencyList.size(); j++)
92                 vertices << j;
93             drawHighlightedFace(&painter, vertices);
94         }
95 #endif
96 
97         // Draw wires
98 
99         // Draw a connection to the previous vertex
100         if (((m_drawingMode == GL_LINES && i % 2)
101              || m_drawingMode == GL_LINE_LOOP
102              || m_drawingMode == GL_LINE_STRIP
103              || (m_drawingMode == GL_TRIANGLES && i % 3)
104              || m_drawingMode == GL_TRIANGLE_STRIP
105              || m_drawingMode == GL_TRIANGLE_FAN
106 #ifndef QT_OPENGL_ES_2
107              || (m_drawingMode == GL_QUADS && i % 4 != 0)
108              || (m_drawingMode == GL_QUAD_STRIP && i % 2)
109              || m_drawingMode == GL_POLYGON
110 #endif
111              ) && i > 0) {
112             drawWire(&painter, index, m_adjacencyList[i - 1]);
113         }
114 
115         // Draw a connection to the second previous vertex
116         if ((m_drawingMode == GL_TRIANGLE_STRIP
117              || (m_drawingMode == GL_TRIANGLES && i % 3 == 2)
118 #ifndef QT_OPENGL_ES_2
119              || m_drawingMode == GL_QUAD_STRIP
120 #endif
121              ) && i > 1) {
122             drawWire(&painter, index, m_adjacencyList[i - 2]);
123         }
124 
125         // draw a connection to the third previous vertex
126 #ifndef QT_OPENGL_ES_2
127         if (m_drawingMode == GL_QUADS && i % 4 == 3) {
128             drawWire(&painter, index, m_adjacencyList[i - 3]);
129         }
130 
131 #endif
132 
133         // Draw a connection to the very first vertex
134         if ((m_drawingMode == GL_LINE_LOOP && i == m_adjacencyList.size() - 1)
135 #ifndef QT_OPENGL_ES_2
136             || (m_drawingMode == GL_POLYGON && i == m_adjacencyList.size() - 1)
137 #endif
138             || m_drawingMode == GL_TRIANGLE_FAN)
139             drawWire(&painter, index, m_adjacencyList.first());
140     }
141 
142     // Paint the vertices
143     for (int i = 0; i < m_vertices.size(); ++i) {
144         if (m_highlightedVertices.contains(i)) {
145             painter.save();
146 
147             // Glow
148             QRadialGradient radialGrad(m_vertices.at(i) * m_zoom + m_offset, 6);
149             radialGrad.setColorAt(0, qApp->palette().color(QPalette::Highlight));
150             radialGrad.setColorAt(1, Qt::transparent);
151 
152             painter.setBrush(QBrush(radialGrad));
153             painter.setPen(Qt::NoPen);
154             painter.drawEllipse(m_vertices.at(i) * m_zoom + m_offset, 12, 12);
155 
156             // Highlighted point
157             painter.setBrush(QBrush(qApp->palette().color(QPalette::Highlight)));
158             painter.drawEllipse(m_vertices.at(i) * m_zoom + m_offset, 3, 3);
159             painter.restore();
160         } else {
161             // Normal unhighlighted point
162             painter.drawEllipse(m_vertices.at(i) * m_zoom + m_offset, 3, 3);
163         }
164     }
165 
166     // Paint hint about which draw mode is used
167     QString drawingMode = m_drawingMode == GL_POINTS ? QStringLiteral("GL_POINTS")
168                           : m_drawingMode == GL_LINES ? QStringLiteral("GL_LINES")
169                           : m_drawingMode == GL_LINE_STRIP ? QStringLiteral("GL_LINE_STRIP")
170                           : m_drawingMode == GL_LINE_LOOP ? QStringLiteral("GL_LINE_LOOP") :
171 #ifndef QT_OPENGL_ES_2
172                           m_drawingMode == GL_POLYGON ? QStringLiteral("GL_POLYGON")
173                           : m_drawingMode == GL_QUADS ? QStringLiteral("GL_QUADS")
174                           : m_drawingMode == GL_QUAD_STRIP ? QStringLiteral("GL_QUAD_STRIP") :
175 #endif
176                           m_drawingMode == GL_TRIANGLES ? QStringLiteral("GL_TRIANGLES")
177                           : m_drawingMode == GL_TRIANGLE_STRIP ? QStringLiteral("GL_TRIANGLE_STRIP")
178                           : m_drawingMode
179                           == GL_TRIANGLE_FAN ? QStringLiteral("GL_TRIANGLE_FAN") : tr("Unknown");
180     QString text = tr("Drawing mode: %1").arg(drawingMode);
181     painter.drawText(contentsRect().width() - painter.fontMetrics().width(text),
182                      contentsRect().height() - painter.fontMetrics().height(), text);
183 }
184 
drawWire(QPainter * painter,int vertexIndex1,int vertexIndex2)185 void SGWireframeWidget::drawWire(QPainter *painter, int vertexIndex1, int vertexIndex2)
186 {
187     // Draw wire
188     if (m_highlightedVertices.contains(vertexIndex1)
189         && m_highlightedVertices.contains(vertexIndex2)) {
190         painter->save();
191         painter->setPen(qApp->palette().color(QPalette::Highlight));
192         painter->drawLine(m_vertices.at(vertexIndex1) * m_zoom + m_offset,
193                           m_vertices.at(vertexIndex2) * m_zoom + m_offset);
194         painter->restore();
195     } else if (vertexIndex1 != -1 && vertexIndex2 != -1) {
196         painter->drawLine(m_vertices.at(vertexIndex1) * m_zoom + m_offset,
197                           m_vertices.at(vertexIndex2) * m_zoom + m_offset);
198     }
199 }
200 
drawHighlightedFace(QPainter * painter,const QVector<int> & vertexIndices)201 void SGWireframeWidget::drawHighlightedFace(QPainter *painter, const QVector<int> &vertexIndices)
202 {
203     QVector<QPointF> vertices;
204     for (int index : vertexIndices) {
205         if (!m_highlightedVertices.contains(index))
206             return; // There is one vertex that is not highlighted. Don't highlight the face.
207         vertices << m_vertices.at(index) * m_zoom + m_offset;
208     }
209     painter->save();
210     QColor color = qApp->palette().color(QPalette::Highlight).lighter();
211     color.setAlphaF(0.8);
212     painter->setBrush(QBrush(color));
213     painter->setPen(Qt::NoPen);
214     painter->drawPolygon(QPolygonF(vertices));
215     painter->restore();
216 }
217 
model() const218 QAbstractItemModel *SGWireframeWidget::model() const
219 {
220     return m_vertexModel;
221 }
222 
setModel(QAbstractItemModel * vertexModel,QAbstractItemModel * adjacencyModel)223 void SGWireframeWidget::setModel(QAbstractItemModel *vertexModel, QAbstractItemModel *adjacencyModel)
224 {
225     if (m_vertexModel) {
226         disconnect(m_vertexModel, nullptr, this, nullptr);
227     }
228     m_vertexModel = vertexModel;
229     m_vertexModel->rowCount();
230     connect(m_vertexModel, &QAbstractItemModel::modelReset,
231             this, &SGWireframeWidget::onVertexModelReset);
232     connect(m_vertexModel, &QAbstractItemModel::dataChanged,
233             this, &SGWireframeWidget::onVertexModelDataChanged);
234     connect(m_vertexModel, &QAbstractItemModel::rowsInserted,
235             this, &SGWireframeWidget::onVertexModelRowsInserted);
236 
237     if (m_adjacencyModel) {
238         disconnect(m_adjacencyModel, nullptr, this, nullptr);
239     }
240     m_adjacencyModel = adjacencyModel;
241     m_adjacencyModel->rowCount();
242     connect(m_adjacencyModel, &QAbstractItemModel::modelReset,
243             this, &SGWireframeWidget::onAdjacencyModelReset);
244     connect(m_adjacencyModel, &QAbstractItemModel::dataChanged,
245             this, &SGWireframeWidget::onAdjacencyModelDataChanged);
246     connect(m_adjacencyModel, &QAbstractItemModel::rowsInserted,
247             this, &SGWireframeWidget::onAdjacencyModelRowsInserted);
248 }
249 
setHighlightModel(QItemSelectionModel * selectionModel)250 void SGWireframeWidget::setHighlightModel(QItemSelectionModel *selectionModel)
251 {
252     if (m_highlightModel)
253         disconnect(m_highlightModel, nullptr, this, nullptr);
254 
255     m_highlightModel = selectionModel;
256 
257     connect(m_highlightModel, &QItemSelectionModel::selectionChanged,
258             this, &SGWireframeWidget::onHighlightDataChanged);
259 }
260 
onVertexModelReset()261 void SGWireframeWidget::onVertexModelReset()
262 {
263     fetchVertices();
264     update();
265 }
266 
onAdjacencyModelReset()267 void SGWireframeWidget::onAdjacencyModelReset()
268 {
269     fetchAdjacencyList();
270     update();
271 }
272 
onVertexModelRowsInserted(const QModelIndex & parent,int first,int last)273 void SGWireframeWidget::onVertexModelRowsInserted(const QModelIndex &parent, int first, int last)
274 {
275     Q_UNUSED(first);
276     Q_UNUSED(last);
277     if (!parent.isValid()) {
278         fetchVertices();
279         update();
280     }
281 }
282 
onAdjacencyModelRowsInserted(const QModelIndex & parent,int first,int last)283 void SGWireframeWidget::onAdjacencyModelRowsInserted(const QModelIndex &parent, int first, int last)
284 {
285     Q_UNUSED(first);
286     Q_UNUSED(last);
287     if (!parent.isValid()) {
288         fetchAdjacencyList();
289         update();
290     }
291 }
292 
onVertexModelDataChanged(const QModelIndex & topLeft,const QModelIndex & bottomRight)293 void SGWireframeWidget::onVertexModelDataChanged(const QModelIndex &topLeft,
294                                            const QModelIndex &bottomRight)
295 {
296     if (!topLeft.isValid()
297         || !bottomRight.isValid()
298         || m_positionColumn == -1
299         || (topLeft.column() <= m_positionColumn && bottomRight.column() >= m_positionColumn)) {
300         fetchVertices();
301         update();
302     }
303 }
304 
onAdjacencyModelDataChanged(const QModelIndex & topLeft,const QModelIndex & bottomRight)305 void SGWireframeWidget::onAdjacencyModelDataChanged(const QModelIndex &topLeft,
306                                            const QModelIndex &bottomRight)
307 {
308     if (!topLeft.isValid()
309         || !bottomRight.isValid()
310         || m_positionColumn == -1
311         || (topLeft.column() <= m_positionColumn && bottomRight.column() >= m_positionColumn)) {
312         fetchAdjacencyList();
313         update();
314     }
315 }
316 
fetchVertices()317 void SGWireframeWidget::fetchVertices()
318 {
319     // Get the column in which the vertex position data is stored in
320     if (m_positionColumn == -1) {
321         for (int j = 0; j < m_vertexModel->columnCount(); j++) {
322             if (m_vertexModel->data(m_vertexModel->index(0, j),
323                                 SGVertexModel::IsCoordinateRole).toBool()) {
324                 m_positionColumn = j;
325                 break;
326             }
327         }
328     }
329 
330     // Get all the vertices
331     const int verticesCount = m_vertexModel->rowCount();
332     m_vertices.clear();
333     m_vertices.reserve(verticesCount);
334     m_geometryWidth = 0;
335     m_geometryHeight = 0;
336     for (int i = 0; i < verticesCount; i++) {
337         const QModelIndex index = m_vertexModel->index(i, m_positionColumn);
338         const QVariantList data = m_vertexModel->data(index, SGVertexModel::RenderRole).toList();
339         if (data.isEmpty()) {
340             continue; // The rest of the data will be incomplete as well,
341                       // but we need to fetch everything.
342         }
343         if (data.size() >= 2) {
344             const qreal x = data[0].toReal();
345             const qreal y = data[1].toReal();
346             m_vertices << QPointF(x, y);
347             if (x > m_geometryWidth)
348                 m_geometryWidth = x;
349             if (y > m_geometryHeight)
350                 m_geometryHeight = y;
351         }
352     }
353 }
354 
fetchAdjacencyList()355 void SGWireframeWidget::fetchAdjacencyList()
356 {
357     m_drawingMode = m_adjacencyModel->index(0, 0).data(SGAdjacencyModel::DrawingModeRole).toUInt();
358 
359     // Get all the wires
360     m_adjacencyList.clear();
361     for (int i = 0; i < m_adjacencyModel->rowCount(); i++) {
362         const QModelIndex index = m_adjacencyModel->index(i, 0);
363         const QVariant data = m_adjacencyModel->data(index, SGAdjacencyModel::RenderRole);
364         if (!data.isValid()) {
365             continue; // The rest of the data will be incomplete as well,
366                       // but we need to fetch everything.
367         }
368         const quint32 value = data.value<quint32>();
369         m_adjacencyList << value;
370     }
371 }
372 
onHighlightDataChanged(const QItemSelection & selected,const QItemSelection & deselected)373 void SGWireframeWidget::onHighlightDataChanged(const QItemSelection &selected,
374                                                const QItemSelection &deselected)
375 {
376     foreach (const QModelIndex &index, deselected.indexes()) {
377         m_highlightedVertices.remove(index.row());
378     }
379     foreach (const QModelIndex &index, selected.indexes()) {
380         if (!m_highlightedVertices.contains(index.row()))
381             m_highlightedVertices << index.row();
382     }
383 
384     update();
385 }
386 
mouseReleaseEvent(QMouseEvent * e)387 void SGWireframeWidget::mouseReleaseEvent(QMouseEvent *e)
388 {
389     if (~e->modifiers() & Qt::ControlModifier)
390         m_highlightModel->clear();
391 
392     for (int i = 0; i < m_vertices.size(); i++) {
393         int distance = QLineF(e->pos(), m_vertices.at(i) * m_zoom + m_offset).length();
394         if (distance <= 5) {
395             if (e->modifiers() & Qt::ControlModifier) {
396                 m_highlightModel->select(m_vertexModel->index(i, m_positionColumn),
397                                          QItemSelectionModel::Toggle);
398             } else {
399                 m_highlightModel->select(m_vertexModel->index(i, m_positionColumn),
400                                          QItemSelectionModel::Select);
401             }
402         }
403     }
404 
405     QWidget::mouseReleaseEvent(e);
406 }
407