From f99fd100e6e46b0df5b43ad94161c2be80277fe7 Mon Sep 17 00:00:00 2001 From: Eivind Kvedalen Date: Sun, 14 Jun 2015 11:46:18 +0200 Subject: [PATCH] Added zoom and automatic refresh of dependency graph view. --- src/App/Document.cpp | 4 +- src/App/Document.h | 3 +- src/Gui/CMakeLists.txt | 9 ++ src/Gui/CommandDoc.cpp | 68 +----------- src/Gui/GraphicsViewZoom.cpp | 94 ++++++++++++++++ src/Gui/GraphicsViewZoom.h | 90 ++++++++++++++++ src/Gui/GraphvizView.cpp | 200 ++++++++++++++++++++++++++++++++++- src/Gui/GraphvizView.h | 31 +++++- src/Gui/Qt4All.h | 3 + 9 files changed, 428 insertions(+), 74 deletions(-) create mode 100644 src/Gui/GraphicsViewZoom.cpp create mode 100644 src/Gui/GraphicsViewZoom.h diff --git a/src/App/Document.cpp b/src/App/Document.cpp index 4f04be915..be4ba45be 100644 --- a/src/App/Document.cpp +++ b/src/App/Document.cpp @@ -200,7 +200,7 @@ void Document::writeDependencyGraphViz(std::ostream &out) out << "}" << endl; } -void Document::exportGraphviz(std::ostream& out) +void Document::exportGraphviz(std::ostream& out) const { std::vector names; names.reserve(d->objectMap.size()); @@ -1437,6 +1437,8 @@ void Document::recompute() it->second->purgeTouched(); } d->vertexMap.clear(); + + signalRecomputed(*this); } const char * Document::getErrorDescription(const App::DocumentObject*Obj) const diff --git a/src/App/Document.h b/src/App/Document.h index b882ec694..51f1703e0 100644 --- a/src/App/Document.h +++ b/src/App/Document.h @@ -129,6 +129,7 @@ public: Base::XMLReader&)> signalImportObjects; boost::signal&, Base::Reader&, const std::map&)> signalImportViewObjects; + boost::signal signalRecomputed; //@} /** @name File handling of the document */ @@ -141,7 +142,7 @@ public: /// Restore the document from the file in Property Path void restore (void); void exportObjects(const std::vector&, std::ostream&); - void exportGraphviz(std::ostream&); + void exportGraphviz(std::ostream&) const; std::vector importObjects(Base::XMLReader& reader); /// Opens the document from its file name //void open (void); diff --git a/src/Gui/CMakeLists.txt b/src/Gui/CMakeLists.txt index 531706fe1..0212f523c 100644 --- a/src/Gui/CMakeLists.txt +++ b/src/Gui/CMakeLists.txt @@ -201,6 +201,7 @@ set(Gui_MOC_HDRS EditorView.h FileDialog.h Flag.h + GraphicsViewZoom.h GraphvizView.h GuiApplicationNativeEventAware.h HelpView.h @@ -253,6 +254,12 @@ set(Gui_MOC_HDRS fc_wrap_cpp(Gui_MOC_SRCS ${Gui_MOC_HDRS}) #SOURCE_GROUP("Moc" FILES ${Gui_MOC_SRCS}) +add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/moc_GraphvizView-internal.cpp + COMMAND ${QT_MOC_EXECUTABLE} -o ${CMAKE_CURRENT_BINARY_DIR}/moc_GraphvizView-internal.cpp ${CMAKE_CURRENT_SOURCE_DIR}/GraphvizView.cpp + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/GraphvizView.cpp) + +set_property(SOURCE GraphvizView.cpp APPEND PROPERTY OBJECT_DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/moc_GraphvizView-internal.cpp) + SET(Gui_UIC_SRCS AboutApplication.ui Clipping.ui @@ -915,6 +922,7 @@ SET(FreeCADGui_CPP_SRCS Document.cpp DocumentModel.cpp DocumentPyImp.cpp + GraphicsViewZoom.cpp GuiApplicationNativeEventAware.cpp GuiConsole.cpp Macro.cpp @@ -934,6 +942,7 @@ SET(FreeCADGui_SRCS Document.h DocumentModel.h FreeCADGuiInit.py + GraphicsViewZoom.h GuiApplicationNativeEventAware.h GuiConsole.h InventorAll.h diff --git a/src/Gui/CommandDoc.cpp b/src/Gui/CommandDoc.cpp index 2790a43b7..0bc3d085a 100644 --- a/src/Gui/CommandDoc.cpp +++ b/src/Gui/CommandDoc.cpp @@ -361,71 +361,9 @@ StdCmdExportGraphviz::StdCmdExportGraphviz() void StdCmdExportGraphviz::activated(int iMsg) { App::Document* doc = App::GetApplication().getActiveDocument(); - std::stringstream str; - doc->exportGraphviz(str); - - ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Paths"); - QProcess proc; - QStringList args; - args << QLatin1String("-Tpng"); -#ifdef FC_OS_LINUX - QString path = QString::fromUtf8(hGrp->GetASCII("Graphviz", "/usr/bin").c_str()); -#else - QString path = QString::fromUtf8(hGrp->GetASCII("Graphviz").c_str()); -#endif - bool pathChanged = false; -#ifdef FC_OS_WIN32 - QString exe = QString::fromAscii("\"%1/dot\"").arg(path); -#else - QString exe = QString::fromAscii("%1/dot").arg(path); -#endif - proc.setEnvironment(QProcess::systemEnvironment()); - do { - proc.start(exe, args); - if (!proc.waitForStarted()) { - int ret = QMessageBox::warning(getMainWindow(), - qApp->translate("Std_ExportGraphviz","Graphviz not found"), - qApp->translate("Std_ExportGraphviz","Graphviz couldn't be found on your system.\n" - "Do you want to specify its installation path if it's already installed?"), - QMessageBox::Yes, QMessageBox::No); - if (ret == QMessageBox::No) - return; - path = QFileDialog::getExistingDirectory(Gui::getMainWindow(), - qApp->translate("Std_ExportGraphviz","Graphviz installation path")); - if (path.isEmpty()) - return; - pathChanged = true; -#ifdef FC_OS_WIN32 - exe = QString::fromAscii("\"%1/dot\"").arg(path); -#else - exe = QString::fromAscii("%1/dot").arg(path); -#endif - } - else { - if (pathChanged) - hGrp->SetASCII("Graphviz", (const char*)path.toUtf8()); - break; - } - } - while(true); - - proc.write(str.str().c_str(), str.str().size()); - proc.closeWriteChannel(); - if (!proc.waitForFinished()) - return; - - QPixmap px; - if (px.loadFromData(proc.readAll(), "PNG")) { - Gui::GraphvizView* view = new Gui::GraphvizView(px); - view->setDependencyGraph(str.str()); - view->setWindowTitle(qApp->translate("Std_ExportGraphviz","Dependency graph")); - getMainWindow()->addWindow(view); - } - else { - QMessageBox::warning(getMainWindow(), - qApp->translate("Std_ExportGraphviz","Graphviz failed"), - qApp->translate("Std_ExportGraphviz","Graphviz failed to create an image file")); - } + Gui::GraphvizView* view = new Gui::GraphvizView(*doc); + view->setWindowTitle(qApp->translate("Std_ExportGraphviz","Dependency graph")); + getMainWindow()->addWindow(view); } bool StdCmdExportGraphviz::isActive(void) diff --git a/src/Gui/GraphicsViewZoom.cpp b/src/Gui/GraphicsViewZoom.cpp new file mode 100644 index 000000000..335d959b8 --- /dev/null +++ b/src/Gui/GraphicsViewZoom.cpp @@ -0,0 +1,94 @@ +/*************************************************************************** + * Copyright (c) 2015 Pavel Strakhov * + * Copyright (c) 2015 Eivind Kvedalen * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + +/* + * Based on + * + * http://stackoverflow.com/questions/19113532/qgraphicsview-zooming-in-and-out-under-mouse-position-using-mouse-wheel + * + * Re-licensed to LGPL after having contacted original author by e-mail. + * + */ + +#include "PreCompiled.h" +#ifndef _PreComp_ +# include +# include +# include +# include +# include +#endif + +#include + +GraphicsViewZoom::GraphicsViewZoom(QGraphicsView* view) + : QObject(view), _view(view) +{ + _view->viewport()->installEventFilter(this); + _view->setMouseTracking(true); + _modifiers = Qt::ControlModifier; + _zoom_factor_base = 1.0015; +} + +void GraphicsViewZoom::gentle_zoom(double factor) { + _view->scale(factor, factor); + _view->centerOn(target_scene_pos); + QPointF delta_viewport_pos = target_viewport_pos - QPointF(_view->viewport()->width() / 2.0, + _view->viewport()->height() / 2.0); + QPointF viewport_center = _view->mapFromScene(target_scene_pos) - delta_viewport_pos; + _view->centerOn(_view->mapToScene(viewport_center.toPoint())); +} + +void GraphicsViewZoom::set_modifiers(Qt::KeyboardModifiers modifiers) { + _modifiers = modifiers; + +} + +void GraphicsViewZoom::set_zoom_factor_base(double value) { + _zoom_factor_base = value; +} + +bool GraphicsViewZoom::eventFilter(QObject *object, QEvent *event) { + if (event->type() == QEvent::MouseMove) { + QMouseEvent* mouse_event = static_cast(event); + QPointF delta = target_viewport_pos - mouse_event->pos(); + if (qAbs(delta.x()) > 5 || qAbs(delta.y()) > 5) { + target_viewport_pos = mouse_event->pos(); + target_scene_pos = _view->mapToScene(mouse_event->pos()); + } + } else if (event->type() == QEvent::Wheel) { + QWheelEvent* wheel_event = static_cast(event); + if (QApplication::keyboardModifiers() == _modifiers) { + if (wheel_event->orientation() == Qt::Vertical) { + double angle = wheel_event->delta(); + double factor = qPow(_zoom_factor_base, angle); + gentle_zoom(factor); + return true; + } + } + } + Q_UNUSED(object); + return false; +} + +#include "moc_GraphicsViewZoom.cpp" diff --git a/src/Gui/GraphicsViewZoom.h b/src/Gui/GraphicsViewZoom.h new file mode 100644 index 000000000..a7ca0a169 --- /dev/null +++ b/src/Gui/GraphicsViewZoom.h @@ -0,0 +1,90 @@ +/*************************************************************************** + * Copyright (c) 2015 Pavel Strakhov * + * Copyright (c) 2015 Eivind Kvedalen * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + +/* + * Based on + * + * http://stackoverflow.com/questions/19113532/qgraphicsview-zooming-in-and-out-under-mouse-position-using-mouse-wheel + * + * Re-licensed to LGPL after having contacted original author by e-mail. + * + */ + +#ifndef GRAPHICSVIEWZOOM_H +#define GRAPHICSVIEWZOOM_H + +#include +#include + + + +/*! + * This class adds ability to zoom QGraphicsView using mouse wheel. The point under cursor + * remains motionless while it's possible. + * + * Note that it becomes not possible when the scene's + * size is not large enough comparing to the viewport size. QGraphicsView centers the picture + * when it's smaller than the view. And QGraphicsView's scrolls boundaries don't allow to + * put any picture point at any viewport position. + * + * When the user starts scrolling, this class remembers original scene position and + * keeps it until scrolling is completed. It's better than getting original scene position at + * each scrolling step because that approach leads to position errors due to before-mentioned + * positioning restrictions. + * + * When zommed using scroll, this class emits zoomed() signal. + * + * Usage: + * + * new GraphicsViewZoom(view); + * + * The object will be deleted automatically when the view is deleted. + * + * You can set keyboard modifiers used for zooming using set_modified(). Zooming will be + * performed only on exact match of modifiers combination. The default modifier is Ctrl. + * + * You can change zoom velocity by calling set_zoom_factor_base(). + * Zoom coefficient is calculated as zoom_factor_base^angle_delta + * (see QWheelEvent::angleDelta). + * The default zoom factor base is 1.0015. + */ + +class QGraphicsView; + +class GraphicsViewZoom : public QObject { + Q_OBJECT +public: + GraphicsViewZoom(QGraphicsView* view); + void gentle_zoom(double factor); + void set_modifiers(Qt::KeyboardModifiers modifiers); + void set_zoom_factor_base(double value); + +private: + QGraphicsView* _view; + Qt::KeyboardModifiers _modifiers; + double _zoom_factor_base; + QPointF target_scene_pos, target_viewport_pos; + bool eventFilter(QObject* object, QEvent* event); +}; + +#endif // GRAPHICSVIEWZOOM_H diff --git a/src/Gui/GraphvizView.cpp b/src/Gui/GraphvizView.cpp index 0762c468a..508be7b74 100644 --- a/src/Gui/GraphvizView.cpp +++ b/src/Gui/GraphvizView.cpp @@ -24,32 +24,120 @@ #include "PreCompiled.h" #ifndef _PreComp_ +# include # include # include # include # include # include # include -#endif +# include +# include # include # include +# include +# include +# include +#endif +#include "GraphicsViewZoom.h" #include "FileDialog.h" #include "GraphvizView.h" +#include "Application.h" +#include "MainWindow.h" #include +#include using namespace Gui; +namespace Gui { -GraphvizView::GraphvizView(const QPixmap& p, QWidget* parent) +/** + * @brief The GraphvizWorker class + * + * Implements a QThread class that does the actual conversion from dot to + * svg. All critical communcation is done using queued signals. + * + */ + +class GraphvizWorker : public QThread { + Q_OBJECT +public: + GraphvizWorker(QObject * parent = 0) + : QThread(parent) + { + proc.moveToThread(this); + } + + void setData(const QByteArray & data) + { + str = data; + } + + void run() { + // Write data to process + proc.write(str); + proc.closeWriteChannel(); + if (!proc.waitForFinished()) { + Q_EMIT error(); + quit(); + } + + // Emit result; it will get queued for processing in the main thread + Q_EMIT svgFileRead(proc.readAll()); + } + + QProcess * process() { + return &proc; + } + +Q_SIGNALS: + void svgFileRead(const QByteArray & data); + void error(); + +private: + QProcess proc; + QByteArray str; +}; + +} + +GraphvizView::GraphvizView(App::Document & _doc, QWidget* parent) : MDIView(0, parent) + , doc(_doc) + , nPending(0) { + // Create scene scene = new QGraphicsScene(); - scene->addPixmap(p); + + // Create item to hold the graph + svgItem = new QGraphicsSvgItem(); + renderer = new QSvgRenderer(this); + svgItem->setSharedRenderer(renderer); + scene->addItem(svgItem); + + // Create view and zoomer object view = new QGraphicsView(scene, this); + zoomer = new GraphicsViewZoom(view); + zoomer->set_modifiers(Qt::NoModifier); view->show(); + + // Set central widget to view setCentralWidget(view); + + // Create worker thread + thread = new GraphvizWorker(this); + connect(thread, SIGNAL(finished()), this, SLOT(done())); + connect(thread, SIGNAL(error()), this, SLOT(error())); + connect(thread, SIGNAL(svgFileRead(const QByteArray &)), this, SLOT(svgFileRead(const QByteArray &))); + + // Connect signal from document + recomputeConnection = _doc.signalRecomputed.connect(boost::bind(&GraphvizView::updateSvgItem, this, _1)); + undoConnection = _doc.signalUndo.connect(boost::bind(&GraphvizView::updateSvgItem, this, _1)); + redoConnection = _doc.signalRedo.connect(boost::bind(&GraphvizView::updateSvgItem, this, _1)); + + updateSvgItem(_doc); } GraphvizView::~GraphvizView() @@ -58,11 +146,112 @@ GraphvizView::~GraphvizView() delete view; } -void GraphvizView::setDependencyGraph(const std::string& s) +void GraphvizView::updateSvgItem(const App::Document &doc) { - graphCode = s; + nPending++; + + // Skip if thread is working now + if (nPending > 1) + return; + + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Paths"); + QProcess * proc = thread->process(); + QStringList args; + args << QLatin1String("-Tsvg"); +#ifdef FC_OS_LINUX + QString path = QString::fromUtf8(hGrp->GetASCII("Graphviz", "/usr/bin").c_str()); +#else + QString path = QString::fromUtf8(hGrp->GetASCII("Graphviz").c_str()); +#endif + bool pathChanged = false; +#ifdef FC_OS_WIN32 + QString exe = QString::fromAscii("\"%1/dot\"").arg(path); +#else + QString exe = QString::fromAscii("%1/dot").arg(path); +#endif + proc->setEnvironment(QProcess::systemEnvironment()); + do { + proc->start(exe, args); + if (!proc->waitForStarted()) { + int ret = QMessageBox::warning(Gui::getMainWindow(), + qApp->translate("Std_ExportGraphviz","Graphviz not found"), + qApp->translate("Std_ExportGraphviz","Graphviz couldn't be found on your system.\n" + "Do you want to specify its installation path if it's already installed?"), + QMessageBox::Yes, QMessageBox::No); + if (ret == QMessageBox::No) { + disconnectSignals(); + return; + } + path = QFileDialog::getExistingDirectory(Gui::getMainWindow(), + qApp->translate("Std_ExportGraphviz","Graphviz installation path")); + if (path.isEmpty()) { + disconnectSignals(); + return; + } + pathChanged = true; +#ifdef FC_OS_WIN32 + exe = QString::fromAscii("\"%1/dot\"").arg(path); +#else + exe = QString::fromAscii("%1/dot").arg(path); +#endif + } + else { + if (pathChanged) + hGrp->SetASCII("Graphviz", (const char*)path.toUtf8()); + break; + } + } + while(true); + + // Create graph in dot format + std::stringstream stream; + doc.exportGraphviz(stream); + graphCode = stream.str(); + + // Update worker thread, and start it + thread->setData(QByteArray(graphCode.c_str(), graphCode.size())); + thread->start(); } +void GraphvizView::svgFileRead(const QByteArray & data) +{ + // Update renderer with new SVG file, and give message if something went wrong + if (renderer->load(data)) + svgItem->setSharedRenderer(renderer); + else { + QMessageBox::warning(getMainWindow(), + qApp->translate("Std_ExportGraphviz","Graphviz failed"), + qApp->translate("Std_ExportGraphviz","Graphviz failed to create an image file")); + disconnectSignals(); + } +} + +void GraphvizView::error() +{ + // If the worker fails for some reason, stop giving it more data later + disconnectSignals(); +} + +void GraphvizView::done() +{ + nPending--; + if (nPending > 0) { + nPending = 0; + updateSvgItem(doc); + thread->start(); + } +} + +void GraphvizView::disconnectSignals() +{ + recomputeConnection.disconnect(); + undoConnection.disconnect(); + redoConnection.disconnect(); +} + +#include +#include + QByteArray GraphvizView::exportGraph(const QString& format) { ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Paths"); @@ -217,3 +406,4 @@ void GraphvizView::printPreview() } #include "moc_GraphvizView.cpp" +#include "moc_GraphvizView-internal.cpp" diff --git a/src/Gui/GraphvizView.h b/src/Gui/GraphvizView.h index 492ff7609..3452e5457 100644 --- a/src/Gui/GraphvizView.h +++ b/src/Gui/GraphvizView.h @@ -25,21 +25,29 @@ #define GUI_GRAPHVIZVIEW_H #include "MDIView.h" +#include +#include + class QGraphicsScene; class QGraphicsView; +class QSvgRenderer; +class QGraphicsSvgItem; +class GraphicsViewZoom; namespace Gui { + +class GraphvizWorker; + class GuiExport GraphvizView : public MDIView { Q_OBJECT public: - GraphvizView(const QPixmap&, QWidget* parent=0); + GraphvizView(App::Document &_doc, QWidget* parent=0); ~GraphvizView(); - void setDependencyGraph(const std::string&); QByteArray exportGraph(const QString& filter); /// Message handler @@ -57,10 +65,29 @@ public: virtual void printPreview(); //@} +private Q_SLOTS: + void svgFileRead(const QByteArray & data); + void error(); + void done(); + private: + void updateSvgItem(const App::Document &doc); + void disconnectSignals(); + + const App::Document& doc; std::string graphCode; QGraphicsScene* scene; QGraphicsView* view; + GraphicsViewZoom* zoomer; + QGraphicsSvgItem* svgItem; + QSvgRenderer* renderer; + GraphvizWorker* thread; + int nPending; + + typedef boost::BOOST_SIGNALS_NAMESPACE::scoped_connection Connection; + Connection recomputeConnection; + Connection undoConnection; + Connection redoConnection; }; } // namespace Gui diff --git a/src/Gui/Qt4All.h b/src/Gui/Qt4All.h index 17bfd88a3..a64b31bc9 100644 --- a/src/Gui/Qt4All.h +++ b/src/Gui/Qt4All.h @@ -169,5 +169,8 @@ //#include #endif +#include "qmath.h" +#include +#include #endif