FreeCAD

Форк
0
/
GraphvizView.cpp 
578 строк · 18.3 Кб
1
/***************************************************************************
2
 *   Copyright (c) 2014 Werner Mayer <wmayer[at]users.sourceforge.net>     *
3
 *                                                                         *
4
 *   This file is part of the FreeCAD CAx development system.              *
5
 *                                                                         *
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.      *
10
 *                                                                         *
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.                  *
15
 *                                                                         *
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                                *
20
 *                                                                         *
21
 ***************************************************************************/
22

23
#include "PreCompiled.h"
24

25
#ifndef _PreComp_
26
# include <QApplication>
27
# include <QFile>
28
# include <QGraphicsScene>
29
# include <QGraphicsSvgItem>
30
# include <QGraphicsView>
31
# include <QMessageBox>
32
# include <QMouseEvent>
33
# include <QPrinter>
34
# include <QPrintDialog>
35
# include <QPrintPreviewDialog>
36
# include <QProcess>
37
# include <QSvgRenderer>
38
# include <QScrollBar>
39
# include <QThread>
40
#endif
41

42
#include <App/Application.h>
43
#include <App/Document.h>
44

45
#include "GraphvizView.h"
46
#include "GraphicsViewZoom.h"
47
#include "FileDialog.h"
48
#include "MainWindow.h"
49

50

51
using namespace Gui;
52
namespace sp = std::placeholders;
53

54
namespace Gui {
55

56
/**
57
 * @brief The GraphvizWorker class
58
 *
59
 * Implements a QThread class that does the actual conversion from dot to
60
 * svg. All critical communication is done using queued signals.
61
 *
62
 */
63

64
class GraphvizWorker : public QThread {
65
    Q_OBJECT
66
public:
67
    explicit GraphvizWorker(QObject * parent = nullptr)
68
        : QThread(parent)
69
    {
70
    }
71

72
    ~GraphvizWorker() override
73
    {
74
        dotProc.moveToThread(this);
75
        unflattenProc.moveToThread(this);
76
    }
77

78
    void setData(const QByteArray & data)
79
    {
80
        str = data;
81
    }
82

83
    void startThread() {
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.
88
        run();
89
        // Can't use the finished() signal of QThread
90
        Q_EMIT emitFinished();
91
    }
92

93
    void run() override {
94
        QByteArray preprocessed = str;
95

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();
104
        } else {
105
            unflattenProc.closeWriteChannel();
106
            unflattenProc.waitForFinished();
107
        }
108

109
        dotProc.write(preprocessed);
110
        dotProc.closeWriteChannel();
111
        if (!dotProc.waitForFinished()) {
112
            Q_EMIT error();
113
            quit();
114
        }
115

116
        // Emit result; it will get queued for processing in the main thread
117
        Q_EMIT svgFileRead(dotProc.readAll());
118
    }
119

120
    QProcess * dotProcess() {
121
        return &dotProc;
122
    }
123

124
    QProcess * unflattenProcess() {
125
        return &unflattenProc;
126
    }
127

128
Q_SIGNALS:
129
    void svgFileRead(const QByteArray & data);
130
    void error();
131
    void emitFinished();
132

133
private:
134
    QProcess dotProc, unflattenProc;
135
    QByteArray str, flatStr;
136
};
137

138
// Simple wrapper around QGraphicsView to make panning possible
139
class GraphvizGraphicsView final : public QGraphicsView
140
{
141
  public:
142
    GraphvizGraphicsView(QGraphicsScene* scene, QWidget* parent);
143
    ~GraphvizGraphicsView() override = default;
144

145
    GraphvizGraphicsView(const GraphvizGraphicsView&) = delete;
146
    GraphvizGraphicsView(GraphvizGraphicsView&&) = delete;
147
    GraphvizGraphicsView& operator=(const GraphvizGraphicsView&) = delete;
148
    GraphvizGraphicsView& operator=(GraphvizGraphicsView&&) = delete;
149

150
  protected:
151
    void mousePressEvent(QMouseEvent *event) override;
152
    void mouseMoveEvent(QMouseEvent *event) override;
153
    void mouseReleaseEvent(QMouseEvent *event) override;
154

155
  private:
156
    bool   isPanning{false};
157
    QPoint panStart;
158
};
159

160
GraphvizGraphicsView::GraphvizGraphicsView(QGraphicsScene* scene, QWidget* parent) : QGraphicsView(scene, parent)
161
{
162
}
163

164
void GraphvizGraphicsView::mousePressEvent(QMouseEvent* e)
165
{
166
  if (e && e->button() == Qt::LeftButton) {
167
    isPanning = true;
168
    panStart = e->pos();
169
    e->accept();
170
    QApplication::setOverrideCursor(Qt::ClosedHandCursor);
171
  }
172

173
  QGraphicsView::mousePressEvent(e);
174

175
  return;
176
}
177

178
void GraphvizGraphicsView::mouseMoveEvent(QMouseEvent *e)
179
{
180
  if (!e)
181
    return;
182

183
  if (isPanning) {
184
    auto *horizontalScrollbar = horizontalScrollBar();
185
    auto *verticalScrollbar = verticalScrollBar();
186
    if (!horizontalScrollbar || !verticalScrollbar)
187
      return;
188

189
    auto direction = e->pos() - panStart;
190
    horizontalScrollbar->setValue(horizontalScrollbar->value() - direction.x());
191
    verticalScrollbar->setValue(verticalScrollbar->value() - direction.y());
192

193
    panStart = e->pos();
194
    e->accept();
195
  }
196

197
  QGraphicsView::mouseMoveEvent(e);
198

199
  return;
200
}
201

202
void GraphvizGraphicsView::mouseReleaseEvent(QMouseEvent* e)
203
{
204
  if(e && e->button() & Qt::LeftButton)
205
  {
206
    isPanning = false;
207
    QApplication::restoreOverrideCursor();
208
    e->accept();
209
  }
210

211
  QGraphicsView::mouseReleaseEvent(e);
212

213
  return;
214
}
215

216
}
217

218
/* TRANSLATOR Gui::GraphvizView */
219

220
GraphvizView::GraphvizView(App::Document & _doc, QWidget* parent)
221
  : MDIView(nullptr, parent)
222
  , doc(_doc)
223
  , nPending(0)
224
{
225
    // Create scene
226
    scene = new QGraphicsScene();
227

228
    // Create item to hold the graph
229
    svgItem = new QGraphicsSvgItem();
230
    renderer = new QSvgRenderer(this);
231
    svgItem->setSharedRenderer(renderer);
232
    scene->addItem(svgItem);
233

234
    // Create view and zoomer object
235
    view = new GraphvizGraphicsView(scene, this);
236
    zoomer = new GraphicsViewZoom(view);
237
    zoomer->set_modifiers(Qt::NoModifier);
238
    view->show();
239

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);
244

245
    // Set central widget to view
246
    setCentralWidget(view);
247

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);
254

255
    //NOLINTBEGIN
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));
260
    //NOLINTEND
261

262
    updateSvgItem(_doc);
263
}
264

265
GraphvizView::~GraphvizView()
266
{
267
    delete scene;
268
    delete view;
269
}
270

271
void GraphvizView::updateSvgItem(const App::Document &doc)
272
{
273
    nPending++;
274

275
    // Skip if thread is working now
276
    if (nPending > 1)
277
        return;
278

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");
285

286
#ifdef FC_OS_LINUX
287
    QString path = QString::fromUtf8(hGrp->GetASCII("Graphviz", "/usr/bin").c_str());
288
#else
289
    QString path = QString::fromUtf8(hGrp->GetASCII("Graphviz").c_str());
290
#endif
291
    bool pathChanged = false;
292
#ifdef FC_OS_WIN32
293
    QString dot = QString::fromLatin1("\"%1/dot\"").arg(path);
294
    QString unflatten = QString::fromLatin1("\"%1/unflatten\"").arg(path);
295
#else
296
    QString dot = QString::fromLatin1("%1/dot").arg(path);
297
    QString unflatten = QString::fromLatin1("%1/unflatten").arg(path);
298
#endif
299
    dotProc->setEnvironment(QProcess::systemEnvironment());
300
    flatProc->setEnvironment(QProcess::systemEnvironment());
301
    do {
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) {
317
                disconnectSignals();
318
                return;
319
            }
320
            path = QFileDialog::getExistingDirectory(Gui::getMainWindow(),
321
                                                     tr("Graphviz installation path"));
322
            if (path.isEmpty()) {
323
                disconnectSignals();
324
                return;
325
            }
326
            pathChanged = true;
327
#ifdef FC_OS_WIN32
328
            dot = QString::fromLatin1("\"%1/dot\"").arg(path);
329
            unflatten = QString::fromLatin1("\"%1/unflatten\"").arg(path);
330
#else
331
            dot = QString::fromLatin1("%1/dot").arg(path);
332
            unflatten = QString::fromLatin1("%1/unflatten").arg(path);
333
#endif
334
        }
335
        else {
336
            if (pathChanged)
337
                hGrp->SetASCII("Graphviz", (const char*)path.toUtf8());
338
            break;
339
        }
340
    }
341
    while(true);
342

343
    // Create graph in dot format
344
    std::stringstream stream;
345
    doc.exportGraphviz(stream);
346
    graphCode = stream.str();
347

348
    // Update worker thread, and start it
349
    thread->setData(QByteArray(graphCode.c_str(), graphCode.size()));
350
    thread->startThread();
351
}
352

353
void GraphvizView::svgFileRead(const QByteArray & data)
354
{
355
    // Update renderer with new SVG file, and give message if something went wrong
356
    if (renderer->load(data))
357
        svgItem->setSharedRenderer(renderer);
358
    else {
359
        QMessageBox::warning(getMainWindow(),
360
                             tr("Graphviz failed"),
361
                             tr("Graphviz failed to create an image file"));
362
        disconnectSignals();
363
    }
364
}
365

366
void GraphvizView::error()
367
{
368
    // If the worker fails for some reason, stop giving it more data later
369
    disconnectSignals();
370
}
371

372
void GraphvizView::done()
373
{
374
    nPending--;
375
    if (nPending > 0) {
376
        nPending = 0;
377
        updateSvgItem(doc);
378
        thread->startThread();
379
    }
380
}
381

382
void GraphvizView::disconnectSignals()
383
{
384
    recomputeConnection.disconnect();
385
    undoConnection.disconnect();
386
    redoConnection.disconnect();
387
}
388

389
#include <QObject>
390
#include <QGraphicsView>
391

392
QByteArray GraphvizView::exportGraph(const QString& format)
393
{
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");
399

400
#ifdef FC_OS_LINUX
401
    QString path = QString::fromUtf8(hGrp->GetASCII("Graphviz", "/usr/bin").c_str());
402
#else
403
    QString path = QString::fromUtf8(hGrp->GetASCII("Graphviz").c_str());
404
#endif
405

406
#ifdef FC_OS_WIN32
407
    QString exe = QString::fromLatin1("\"%1/dot\"").arg(path);
408
    QString unflatten = QString::fromLatin1("\"%1/unflatten\"").arg(path);
409
#else
410
    QString exe = QString::fromLatin1("%1/dot").arg(path);
411
    QString unflatten = QString::fromLatin1("%1/unflatten").arg(path);
412
#endif
413

414
    dotProc.setEnvironment(QProcess::systemEnvironment());
415
    dotProc.start(exe, args);
416
    if (!dotProc.waitForStarted()) {
417
        return {};
418
    }
419

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()) {
425
            return {};
426
        }
427
        flatProc.write(graphCode.c_str(), graphCode.size());
428
        flatProc.closeWriteChannel();
429
        if (!flatProc.waitForFinished())
430
            return {};
431

432
        dotProc.write(flatProc.readAll());
433
    }
434
    else
435
        dotProc.write(graphCode.c_str(), graphCode.size());
436

437
    dotProc.closeWriteChannel();
438
    if (!dotProc.waitForFinished())
439
        return {};
440

441
    return dotProc.readAll();
442
}
443

444
bool GraphvizView::onMsg(const char* pMsg, const char**)
445
{
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"));
455

456
        QStringList filter;
457
        for (const auto & it : std::as_const(formatMap)) {
458
            filter << it.first;
459
        }
460

461
        QString selectedFilter;
462
        QString fn = Gui::FileDialog::getSaveFileName(this, tr("Export graph"), QString(), filter.join(QLatin1String(";;")), &selectedFilter);
463
        if (!fn.isEmpty()) {
464
            QString format;
465
            for (const auto & it : std::as_const(formatMap)) {
466
                if (selectedFilter == it.first) {
467
                    format = it.second;
468
                    break;
469
                }
470
            }
471
            QByteArray buffer;
472
            if (format == QLatin1String("gv")) {
473
                std::stringstream str;
474
                doc.exportGraphviz(str);
475
                buffer = QByteArray::fromStdString(str.str());
476
            }
477
            else {
478
                buffer = exportGraph(format);
479
            }
480
            if (buffer.isEmpty()) {
481
                return true;
482
            }
483
            QFile file(fn);
484
            if (file.open(QFile::WriteOnly)) {
485
                file.write(buffer);
486
                file.close();
487
            }
488
        }
489
        return true;
490
    }
491
    else if (strcmp("Print",pMsg) == 0) {
492
        print();
493
        return true;
494
    }
495
    else if (strcmp("PrintPreview",pMsg) == 0) {
496
        printPreview();
497
        return true;
498
    }
499
    else if (strcmp("PrintPdf",pMsg) == 0) {
500
        printPdf();
501
        return true;
502
    }
503

504
    return false;
505
}
506

507
bool GraphvizView::onHasMsg(const char* pMsg) const
508
{
509
    if (strcmp("Save",pMsg) == 0)
510
        return true;
511
    else if (strcmp("SaveAs",pMsg) == 0)
512
        return true;
513
    else if (strcmp("Print",pMsg) == 0)
514
        return true;
515
    else if (strcmp("PrintPreview",pMsg) == 0)
516
        return true;
517
    else if (strcmp("PrintPdf",pMsg) == 0)
518
        return true;
519
    else if (strcmp("AllowsOverlayOnHover", pMsg) == 0)
520
        return true;
521
    return false;
522
}
523

524
void GraphvizView::print(QPrinter* printer)
525
{
526
    QPainter p(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);
532
    p.end();
533
}
534

535
void GraphvizView::print()
536
{
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) {
542
        print(&printer);
543
    }
544
}
545

546
void GraphvizView::printPdf()
547
{
548
    QStringList filter;
549
    filter << QString::fromLatin1("%1 (*.pdf)").arg(tr("PDF format"));
550

551
    QString selectedFilter;
552
    QString fn = Gui::FileDialog::getSaveFileName(this, tr("Export graph"), QString(), filter.join(QLatin1String(";;")), &selectedFilter);
553
    if (!fn.isEmpty()) {
554
        QByteArray buffer = exportGraph(selectedFilter);
555
        if (buffer.isEmpty())
556
            return;
557
        QFile file(fn);
558
        if (file.open(QFile::WriteOnly)) {
559
            file.write(buffer);
560
            file.close();
561
        }
562
    }
563
}
564

565
void GraphvizView::printPreview()
566
{
567
    QPrinter printer(QPrinter::HighResolution);
568
    printer.setFullPage(true);
569
    printer.setPageOrientation(QPageLayout::Landscape);
570

571
    QPrintPreviewDialog dlg(&printer, this);
572
    connect(&dlg, &QPrintPreviewDialog::paintRequested,
573
            this, qOverload<QPrinter*>(&GraphvizView::print));
574
    dlg.exec();
575
}
576

577
#include "moc_GraphvizView.cpp"
578
#include "moc_GraphvizView-internal.cpp"
579

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.