1
/***************************************************************************
2
* Copyright (c) 2014 Werner Mayer <wmayer[at]users.sourceforge.net> *
4
* This file is part of the FreeCAD CAx development system. *
6
* This library is free software; you can redistribute it and/or *
7
* modify it under the terms of the GNU Library General Public *
8
* License as published by the Free Software Foundation; either *
9
* version 2 of the License, or (at your option) any later version. *
11
* This library is distributed in the hope that it will be useful, *
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14
* GNU Library General Public License for more details. *
16
* You should have received a copy of the GNU Library General Public *
17
* License along with this library; see the file COPYING.LIB. If not, *
18
* write to the Free Software Foundation, Inc., 59 Temple Place, *
19
* Suite 330, Boston, MA 02111-1307, USA *
21
***************************************************************************/
23
#include "PreCompiled.h"
26
# include <QApplication>
28
# include <QGraphicsScene>
29
# include <QGraphicsSvgItem>
30
# include <QGraphicsView>
31
# include <QMessageBox>
32
# include <QMouseEvent>
34
# include <QPrintDialog>
35
# include <QPrintPreviewDialog>
37
# include <QSvgRenderer>
42
#include <App/Application.h>
43
#include <App/Document.h>
45
#include "GraphvizView.h"
46
#include "GraphicsViewZoom.h"
47
#include "FileDialog.h"
48
#include "MainWindow.h"
52
namespace sp = std::placeholders;
57
* @brief The GraphvizWorker class
59
* Implements a QThread class that does the actual conversion from dot to
60
* svg. All critical communication is done using queued signals.
64
class GraphvizWorker : public QThread {
67
explicit GraphvizWorker(QObject * parent = nullptr)
72
~GraphvizWorker() override
74
dotProc.moveToThread(this);
75
unflattenProc.moveToThread(this);
78
void setData(const QByteArray & data)
84
// This doesn't actually run a thread but calls the function
85
// directly in the main thread.
86
// This is needed because embedding a QProcess into a QThread
87
// causes some problems with Qt5.
89
// Can't use the finished() signal of QThread
90
Q_EMIT emitFinished();
94
QByteArray preprocessed = str;
96
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/DependencyGraph");
97
if(hGrp->GetBool("Unflatten", true)) {
98
// Write data to unflatten process
99
unflattenProc.write(str);
100
unflattenProc.closeWriteChannel();
101
//no error handling: unflatten is optional
102
unflattenProc.waitForFinished();
103
preprocessed = unflattenProc.readAll();
105
unflattenProc.closeWriteChannel();
106
unflattenProc.waitForFinished();
109
dotProc.write(preprocessed);
110
dotProc.closeWriteChannel();
111
if (!dotProc.waitForFinished()) {
116
// Emit result; it will get queued for processing in the main thread
117
Q_EMIT svgFileRead(dotProc.readAll());
120
QProcess * dotProcess() {
124
QProcess * unflattenProcess() {
125
return &unflattenProc;
129
void svgFileRead(const QByteArray & data);
134
QProcess dotProc, unflattenProc;
135
QByteArray str, flatStr;
138
// Simple wrapper around QGraphicsView to make panning possible
139
class GraphvizGraphicsView final : public QGraphicsView
142
GraphvizGraphicsView(QGraphicsScene* scene, QWidget* parent);
143
~GraphvizGraphicsView() override = default;
145
GraphvizGraphicsView(const GraphvizGraphicsView&) = delete;
146
GraphvizGraphicsView(GraphvizGraphicsView&&) = delete;
147
GraphvizGraphicsView& operator=(const GraphvizGraphicsView&) = delete;
148
GraphvizGraphicsView& operator=(GraphvizGraphicsView&&) = delete;
151
void mousePressEvent(QMouseEvent *event) override;
152
void mouseMoveEvent(QMouseEvent *event) override;
153
void mouseReleaseEvent(QMouseEvent *event) override;
156
bool isPanning{false};
160
GraphvizGraphicsView::GraphvizGraphicsView(QGraphicsScene* scene, QWidget* parent) : QGraphicsView(scene, parent)
164
void GraphvizGraphicsView::mousePressEvent(QMouseEvent* e)
166
if (e && e->button() == Qt::LeftButton) {
170
QApplication::setOverrideCursor(Qt::ClosedHandCursor);
173
QGraphicsView::mousePressEvent(e);
178
void GraphvizGraphicsView::mouseMoveEvent(QMouseEvent *e)
184
auto *horizontalScrollbar = horizontalScrollBar();
185
auto *verticalScrollbar = verticalScrollBar();
186
if (!horizontalScrollbar || !verticalScrollbar)
189
auto direction = e->pos() - panStart;
190
horizontalScrollbar->setValue(horizontalScrollbar->value() - direction.x());
191
verticalScrollbar->setValue(verticalScrollbar->value() - direction.y());
197
QGraphicsView::mouseMoveEvent(e);
202
void GraphvizGraphicsView::mouseReleaseEvent(QMouseEvent* e)
204
if(e && e->button() & Qt::LeftButton)
207
QApplication::restoreOverrideCursor();
211
QGraphicsView::mouseReleaseEvent(e);
218
/* TRANSLATOR Gui::GraphvizView */
220
GraphvizView::GraphvizView(App::Document & _doc, QWidget* parent)
221
: MDIView(nullptr, parent)
226
scene = new QGraphicsScene();
228
// Create item to hold the graph
229
svgItem = new QGraphicsSvgItem();
230
renderer = new QSvgRenderer(this);
231
svgItem->setSharedRenderer(renderer);
232
scene->addItem(svgItem);
234
// Create view and zoomer object
235
view = new GraphvizGraphicsView(scene, this);
236
zoomer = new GraphicsViewZoom(view);
237
zoomer->set_modifiers(Qt::NoModifier);
240
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath
241
("User parameter:BaseApp/Preferences/View");
242
bool on = hGrp->GetBool("InvertZoom", true);
243
zoomer->set_zoom_inverted(on);
245
// Set central widget to view
246
setCentralWidget(view);
248
// Create worker thread
249
thread = new GraphvizWorker(this);
250
connect(thread, &GraphvizWorker::emitFinished, this, &GraphvizView::done);
251
connect(thread, &GraphvizWorker::finished, this, &GraphvizView::done);
252
connect(thread, &GraphvizWorker::error, this, &GraphvizView::error);
253
connect(thread, &GraphvizWorker::svgFileRead, this, &GraphvizView::svgFileRead);
256
// Connect signal from document
257
recomputeConnection = _doc.signalRecomputed.connect(std::bind(&GraphvizView::updateSvgItem, this, sp::_1));
258
undoConnection = _doc.signalUndo.connect(std::bind(&GraphvizView::updateSvgItem, this, sp::_1));
259
redoConnection = _doc.signalRedo.connect(std::bind(&GraphvizView::updateSvgItem, this, sp::_1));
265
GraphvizView::~GraphvizView()
271
void GraphvizView::updateSvgItem(const App::Document &doc)
275
// Skip if thread is working now
279
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Paths");
280
QProcess * dotProc = thread->dotProcess();
281
QProcess * flatProc = thread->unflattenProcess();
282
QStringList args, flatArgs;
283
args << QLatin1String("-Tsvg");
284
flatArgs << QLatin1String("-c2 -l2");
287
QString path = QString::fromUtf8(hGrp->GetASCII("Graphviz", "/usr/bin").c_str());
289
QString path = QString::fromUtf8(hGrp->GetASCII("Graphviz").c_str());
291
bool pathChanged = false;
293
QString dot = QString::fromLatin1("\"%1/dot\"").arg(path);
294
QString unflatten = QString::fromLatin1("\"%1/unflatten\"").arg(path);
296
QString dot = QString::fromLatin1("%1/dot").arg(path);
297
QString unflatten = QString::fromLatin1("%1/unflatten").arg(path);
299
dotProc->setEnvironment(QProcess::systemEnvironment());
300
flatProc->setEnvironment(QProcess::systemEnvironment());
302
flatProc->start(unflatten, flatArgs);
303
bool value = flatProc->waitForStarted();
304
Q_UNUSED(value); // quieten code analyzer
305
dotProc->start(dot, args);
306
if (!dotProc->waitForStarted()) {
307
int ret = QMessageBox::warning(Gui::getMainWindow(),
308
tr("Graphviz not found"),
309
QString::fromLatin1("<html><head/><body>%1 "
310
"<a href=\"https://www.freecad.org/wiki/Std_DependencyGraph\">%2"
311
"</a><p>%3</p></body></html>")
312
.arg(tr("Graphviz couldn't be found on your system."),
313
tr("Read more about it here."),
314
tr("Do you want to specify its installation path if it's already installed?")),
315
QMessageBox::Yes, QMessageBox::No);
316
if (ret == QMessageBox::No) {
320
path = QFileDialog::getExistingDirectory(Gui::getMainWindow(),
321
tr("Graphviz installation path"));
322
if (path.isEmpty()) {
328
dot = QString::fromLatin1("\"%1/dot\"").arg(path);
329
unflatten = QString::fromLatin1("\"%1/unflatten\"").arg(path);
331
dot = QString::fromLatin1("%1/dot").arg(path);
332
unflatten = QString::fromLatin1("%1/unflatten").arg(path);
337
hGrp->SetASCII("Graphviz", (const char*)path.toUtf8());
343
// Create graph in dot format
344
std::stringstream stream;
345
doc.exportGraphviz(stream);
346
graphCode = stream.str();
348
// Update worker thread, and start it
349
thread->setData(QByteArray(graphCode.c_str(), graphCode.size()));
350
thread->startThread();
353
void GraphvizView::svgFileRead(const QByteArray & data)
355
// Update renderer with new SVG file, and give message if something went wrong
356
if (renderer->load(data))
357
svgItem->setSharedRenderer(renderer);
359
QMessageBox::warning(getMainWindow(),
360
tr("Graphviz failed"),
361
tr("Graphviz failed to create an image file"));
366
void GraphvizView::error()
368
// If the worker fails for some reason, stop giving it more data later
372
void GraphvizView::done()
378
thread->startThread();
382
void GraphvizView::disconnectSignals()
384
recomputeConnection.disconnect();
385
undoConnection.disconnect();
386
redoConnection.disconnect();
390
#include <QGraphicsView>
392
QByteArray GraphvizView::exportGraph(const QString& format)
394
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Paths");
395
QProcess dotProc, flatProc;
396
QStringList args, flatArgs;
397
args << QString::fromLatin1("-T%1").arg(format);
398
flatArgs << QLatin1String("-c2 -l2");
401
QString path = QString::fromUtf8(hGrp->GetASCII("Graphviz", "/usr/bin").c_str());
403
QString path = QString::fromUtf8(hGrp->GetASCII("Graphviz").c_str());
407
QString exe = QString::fromLatin1("\"%1/dot\"").arg(path);
408
QString unflatten = QString::fromLatin1("\"%1/unflatten\"").arg(path);
410
QString exe = QString::fromLatin1("%1/dot").arg(path);
411
QString unflatten = QString::fromLatin1("%1/unflatten").arg(path);
414
dotProc.setEnvironment(QProcess::systemEnvironment());
415
dotProc.start(exe, args);
416
if (!dotProc.waitForStarted()) {
420
ParameterGrp::handle depGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/DependencyGraph");
421
if(depGrp->GetBool("Unflatten", true)) {
422
flatProc.setEnvironment(QProcess::systemEnvironment());
423
flatProc.start(unflatten, flatArgs);
424
if (!flatProc.waitForStarted()) {
427
flatProc.write(graphCode.c_str(), graphCode.size());
428
flatProc.closeWriteChannel();
429
if (!flatProc.waitForFinished())
432
dotProc.write(flatProc.readAll());
435
dotProc.write(graphCode.c_str(), graphCode.size());
437
dotProc.closeWriteChannel();
438
if (!dotProc.waitForFinished())
441
return dotProc.readAll();
444
bool GraphvizView::onMsg(const char* pMsg, const char**)
446
if (strcmp("Save",pMsg) == 0 || strcmp("SaveAs",pMsg) == 0) {
447
QList< QPair<QString, QString> > formatMap;
448
formatMap << qMakePair(QString::fromLatin1("%1 (*.gv)").arg(tr("Graphviz format")), QString::fromLatin1("gv"));
449
formatMap << qMakePair(QString::fromLatin1("%1 (*.png)").arg(tr("PNG format")), QString::fromLatin1("png"));
450
formatMap << qMakePair(QString::fromLatin1("%1 (*.bmp)").arg(tr("Bitmap format")), QString::fromLatin1("bmp"));
451
formatMap << qMakePair(QString::fromLatin1("%1 (*.gif)").arg(tr("GIF format")), QString::fromLatin1("gif"));
452
formatMap << qMakePair(QString::fromLatin1("%1 (*.jpg)").arg(tr("JPG format")), QString::fromLatin1("jpg"));
453
formatMap << qMakePair(QString::fromLatin1("%1 (*.svg)").arg(tr("SVG format")), QString::fromLatin1("svg"));
454
formatMap << qMakePair(QString::fromLatin1("%1 (*.pdf)").arg(tr("PDF format")), QString::fromLatin1("pdf"));
457
for (const auto & it : std::as_const(formatMap)) {
461
QString selectedFilter;
462
QString fn = Gui::FileDialog::getSaveFileName(this, tr("Export graph"), QString(), filter.join(QLatin1String(";;")), &selectedFilter);
465
for (const auto & it : std::as_const(formatMap)) {
466
if (selectedFilter == it.first) {
472
if (format == QLatin1String("gv")) {
473
std::stringstream str;
474
doc.exportGraphviz(str);
475
buffer = QByteArray::fromStdString(str.str());
478
buffer = exportGraph(format);
480
if (buffer.isEmpty()) {
484
if (file.open(QFile::WriteOnly)) {
491
else if (strcmp("Print",pMsg) == 0) {
495
else if (strcmp("PrintPreview",pMsg) == 0) {
499
else if (strcmp("PrintPdf",pMsg) == 0) {
507
bool GraphvizView::onHasMsg(const char* pMsg) const
509
if (strcmp("Save",pMsg) == 0)
511
else if (strcmp("SaveAs",pMsg) == 0)
513
else if (strcmp("Print",pMsg) == 0)
515
else if (strcmp("PrintPreview",pMsg) == 0)
517
else if (strcmp("PrintPdf",pMsg) == 0)
519
else if (strcmp("AllowsOverlayOnHover", pMsg) == 0)
524
void GraphvizView::print(QPrinter* printer)
527
QRect rect = printer->pageLayout().paintRectPixels(printer->resolution());
528
view->scene()->render(&p, rect);
529
//QByteArray buffer = exportGraph(QString::fromLatin1("svg"));
530
//QSvgRenderer svg(buffer);
531
//svg.render(&p, rect);
535
void GraphvizView::print()
537
QPrinter printer(QPrinter::HighResolution);
538
printer.setFullPage(true);
539
printer.setPageOrientation(QPageLayout::Landscape);
540
QPrintDialog dlg(&printer, this);
541
if (dlg.exec() == QDialog::Accepted) {
546
void GraphvizView::printPdf()
549
filter << QString::fromLatin1("%1 (*.pdf)").arg(tr("PDF format"));
551
QString selectedFilter;
552
QString fn = Gui::FileDialog::getSaveFileName(this, tr("Export graph"), QString(), filter.join(QLatin1String(";;")), &selectedFilter);
554
QByteArray buffer = exportGraph(selectedFilter);
555
if (buffer.isEmpty())
558
if (file.open(QFile::WriteOnly)) {
565
void GraphvizView::printPreview()
567
QPrinter printer(QPrinter::HighResolution);
568
printer.setFullPage(true);
569
printer.setPageOrientation(QPageLayout::Landscape);
571
QPrintPreviewDialog dlg(&printer, this);
572
connect(&dlg, &QPrintPreviewDialog::paintRequested,
573
this, qOverload<QPrinter*>(&GraphvizView::print));
577
#include "moc_GraphvizView.cpp"
578
#include "moc_GraphvizView-internal.cpp"