File indexing completed on 2024-05-19 15:27:48

0001 /* This file is part of KGraphViewer.
0002    Copyright (C) 2005-2007 Gael de Chalendar <kleag@free.fr>
0003 
0004    KGraphViewer is free software; you can redistribute it and/or
0005    modify it under the terms of the GNU General Public
0006    License as published by the Free Software Foundation, version 2.
0007 
0008    This program is distributed in the hope that it will be useful,
0009    but WITHOUT ANY WARRANTY; without even the implied warranty of
0010    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0011    General Public License for more details.
0012 
0013    You should have received a copy of the GNU General Public License
0014    along with this program; if not, write to the Free Software
0015    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
0016    02110-1301, USA
0017 */
0018 
0019 /* This file was callgraphview.cpp, part of KCachegrind.
0020    Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de>
0021 
0022    KCachegrind is free software; you can redistribute it and/or
0023    modify it under the terms of the GNU General Public
0024    License as published by the Free Software Foundation, version 2.
0025 */
0026 
0027 /*
0028  * Callgraph View
0029  */
0030 
0031 #include "dotgraphview.h"
0032 
0033 #include "FontsCache.h"
0034 #include "canvasedge.h"
0035 #include "canvasnode.h"
0036 #include "canvassubgraph.h"
0037 #include "dot2qtconsts.h"
0038 #include "dotgraph.h"
0039 #include "graphedge.h"
0040 #include "graphelement.h"
0041 #include "graphexporter.h"
0042 #include "graphnode.h"
0043 #include "kgraphviewer_partsettings.h"
0044 #include "kgraphviewerlib_debug.h"
0045 #include "layoutagraphthread.h"
0046 #include "loadagraphthread.h"
0047 #include "pannerview.h"
0048 #include "simpleprintingcommand.h"
0049 
0050 #include <iostream>
0051 #include <math.h>
0052 #include <stdlib.h>
0053 
0054 #include <QApplication>
0055 #include <QBitmap>
0056 #include <QContextMenuEvent>
0057 #include <QDebug>
0058 #include <QFileDialog>
0059 #include <QFocusEvent>
0060 #include <QGraphicsSimpleTextItem>
0061 #include <QImage>
0062 #include <QImageWriter>
0063 #include <QInputDialog>
0064 #include <QKeyEvent>
0065 #include <QMenu>
0066 #include <QMouseEvent>
0067 #include <QPainter>
0068 #include <QPixmap>
0069 #include <QResizeEvent>
0070 #include <QScrollBar>
0071 #include <QStandardPaths>
0072 #include <QStyle>
0073 #include <QSvgGenerator>
0074 #include <QTransform>
0075 #include <QUuid>
0076 #include <QWheelEvent>
0077 
0078 #include <kwidgetsaddons_version.h>
0079 #include <kactionmenu.h>
0080 #include <klocalizedstring.h>
0081 #include <kmessagebox.h>
0082 #include <kselectaction.h>
0083 #include <ktoggleaction.h>
0084 
0085 // DotGraphView defaults
0086 
0087 #define DEFAULT_ZOOMPOS KGraphViewerInterface::Auto
0088 #define KGV_MAX_PANNER_NODES 100
0089 
0090 namespace KGraphViewer
0091 {
0092 class DotGraphViewPrivate
0093 {
0094 public:
0095     DotGraphViewPrivate(KActionCollection *actions, DotGraphView *parent)
0096         : m_labelViews()
0097         , m_popup(nullptr)
0098         , m_zoom(1)
0099         , m_isMoving(false)
0100         , m_exporter()
0101         , m_zoomPosition(DEFAULT_ZOOMPOS)
0102         , m_lastAutoPosition(KGraphViewerInterface::TopLeft)
0103         , m_graph(nullptr)
0104         , m_printCommand(nullptr)
0105         , m_actions(actions)
0106         , m_detailLevel(DEFAULT_DETAILLEVEL)
0107         , m_defaultNewElement(nullptr)
0108         , m_defaultNewElementPixmap(QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kgraphviewerpart/pics/kgraphviewer-newnode.png"))
0109         , m_editingMode(DotGraphView::None)
0110         , m_newEdgeSource(nullptr)
0111         , m_newEdgeDraft(nullptr)
0112         , m_readWrite(false)
0113         , m_leavedTimer(std::numeric_limits<int>::max())
0114         , m_highlighting(false)
0115         , m_loadThread()
0116         , m_layoutThread()
0117         , m_backgroundColor(QColor("white"))
0118         , q_ptr(parent)
0119     {
0120     }
0121     ~DotGraphViewPrivate()
0122     {
0123         delete m_birdEyeView;
0124         m_birdEyeView = nullptr;
0125         delete m_popup;
0126         if (m_canvas) {
0127             Q_Q(DotGraphView);
0128             q->setScene(nullptr);
0129             delete m_canvas;
0130         }
0131         delete m_graph;
0132     }
0133 
0134     void updateSizes(QSizeF s = QSizeF(0, 0));
0135     void updateBirdEyeView();
0136     void setupPopup();
0137     void exportToImage();
0138     KActionCollection *actionCollection()
0139     {
0140         return m_actions;
0141     }
0142     double detailAdjustedScale();
0143     int displaySubgraph(GraphSubgraph *gsubgraph, int zValue, CanvasElement *parent = nullptr);
0144 
0145     QSet<QGraphicsSimpleTextItem *> m_labelViews;
0146     QGraphicsScene *m_canvas;
0147     QMenu *m_popup;
0148     KSelectAction *m_bevPopup;
0149     KSelectAction *m_layoutAlgoSelectAction;
0150     int m_xMargin, m_yMargin;
0151     PannerView *m_birdEyeView;
0152     double m_cvZoom;
0153     double m_zoom;
0154     bool m_isMoving;
0155     QPoint m_lastPos;
0156 
0157     GraphExporter m_exporter;
0158 
0159     // widget options
0160     KGraphViewerInterface::PannerPosition m_zoomPosition, m_lastAutoPosition;
0161 
0162     DotGraph *m_graph;
0163 
0164     KGVSimplePrintingCommand *m_printCommand;
0165 
0166     KToggleAction *m_bevEnabledAction;
0167     KActionCollection *m_actions;
0168 
0169     int m_detailLevel;
0170 
0171     GraphElement *m_defaultNewElement;
0172 
0173     /** image used for a new node just added in an edited graph because this new node has
0174      *  still no attribute and thus no render operation */
0175     QPixmap m_defaultNewElementPixmap;
0176     DotGraphView::EditingMode m_editingMode;
0177 
0178     CanvasElement *m_newEdgeSource;
0179     QGraphicsLineItem *m_newEdgeDraft;
0180 
0181     bool m_readWrite;
0182 
0183     QMap<QString, QString> m_newElementAttributes;
0184 
0185     /// identifier of the timer started when the mouse leaves the view during
0186     /// edge drawing
0187     int m_leavedTimer;
0188 
0189     DotGraphView::ScrollDirection m_scrollDirection;
0190 
0191     QPoint m_pressPos;
0192     QPoint m_pressScrollBarsPos;
0193 
0194     /// true if elements should be highlighted on hover; false otherwise
0195     bool m_highlighting;
0196 
0197     /// A thread to load graphviz agraph files
0198     LoadAGraphThread m_loadThread;
0199 
0200     /// A thread to layout graphviz agraph files
0201     LayoutAGraphThread m_layoutThread;
0202 
0203     /// The graph background color
0204     QColor m_backgroundColor;
0205 
0206     DotGraphView *const q_ptr;
0207     Q_DECLARE_PUBLIC(DotGraphView);
0208 };
0209 
0210 void DotGraphViewPrivate::updateSizes(QSizeF s)
0211 {
0212     Q_Q(DotGraphView);
0213     if (m_canvas == nullptr)
0214         return;
0215     if (s == QSizeF(0, 0))
0216         s = q->size();
0217 
0218     // the part of the canvas that should be visible
0219     qreal cWidth = m_canvas->width() - 2 * m_xMargin + 100;
0220     qreal cHeight = m_canvas->height() - 2 * m_yMargin + 100;
0221 
0222     // hide birds eye view if no overview needed
0223     if ( //!_data || !_activeItem ||
0224         !KGraphViewerPartSettings::birdsEyeViewEnabled() || (((cWidth * m_zoom) < s.width()) && (cHeight * m_zoom) < s.height())) {
0225         m_birdEyeView->hide();
0226         return;
0227     }
0228     m_birdEyeView->hide();
0229 
0230     // first, assume use of 1/3 of width/height (possible larger)
0231     double zoom = .33 * s.width() / cWidth;
0232     if (zoom * cHeight < .33 * s.height())
0233         zoom = .33 * s.height() / cHeight;
0234 
0235     // fit to widget size
0236     if (cWidth * zoom > s.width())
0237         zoom = s.width() / (double)cWidth;
0238     if (cHeight * zoom > s.height())
0239         zoom = s.height() / (double)cHeight;
0240 
0241     // scale to never use full height/width
0242     zoom = zoom * 3 / 4;
0243 
0244     // at most a zoom of 1/3
0245     if (zoom > .33)
0246         zoom = .33;
0247 
0248     if (zoom != m_cvZoom) {
0249         m_cvZoom = zoom;
0250 
0251         QTransform wm;
0252         wm.scale(zoom, zoom);
0253         m_birdEyeView->setTransform(wm);
0254 
0255         // make it a little bigger to compensate for widget frame
0256         m_birdEyeView->resize((cWidth * zoom) + 4, (cHeight * zoom) + 4);
0257     }
0258     updateBirdEyeView();
0259     m_birdEyeView->setZoomRect(q->mapToScene(q->viewport()->rect()).boundingRect());
0260     m_birdEyeView->show();
0261     QSizeF newCanvasSize = m_canvas->sceneRect().size();
0262     if (newCanvasSize.width() < q->viewport()->width()) {
0263         newCanvasSize.setWidth(q->viewport()->width());
0264     } else if (q->viewport()->width() < m_canvas->sceneRect().size().width()) {
0265         newCanvasSize.setWidth(m_canvas->sceneRect().size().width());
0266     }
0267     if (newCanvasSize.height() < q->viewport()->height()) {
0268         newCanvasSize.setHeight(q->viewport()->height());
0269     } else if (q->viewport()->height() < m_canvas->sceneRect().size().height()) {
0270         newCanvasSize.setHeight(m_canvas->sceneRect().size().height());
0271     }
0272     //   std::cerr << "done." << std::endl;
0273 }
0274 
0275 void DotGraphViewPrivate::updateBirdEyeView()
0276 {
0277     Q_Q(DotGraphView);
0278     qreal cvW = m_birdEyeView->width();
0279     qreal cvH = m_birdEyeView->height();
0280     qreal x = q->width() - cvW - q->verticalScrollBar()->width() - 2;
0281     qreal y = q->height() - cvH - q->horizontalScrollBar()->height() - 2;
0282     QPoint oldZoomPos = m_birdEyeView->pos();
0283     QPoint newZoomPos = QPoint(0, 0);
0284     KGraphViewerInterface::PannerPosition zp = m_zoomPosition;
0285     if (zp == KGraphViewerInterface::Auto) {
0286         QPointF tl1Pos = q->mapToScene(QPoint(0, 0));
0287         QPointF tl2Pos = q->mapToScene(QPoint(cvW, cvH));
0288         QPointF tr1Pos = q->mapToScene(QPoint(x, 0));
0289         QPointF tr2Pos = q->mapToScene(QPoint(x + cvW, cvH));
0290         QPointF bl1Pos = q->mapToScene(QPoint(0, y));
0291         QPointF bl2Pos = q->mapToScene(QPoint(cvW, y + cvH));
0292         QPointF br1Pos = q->mapToScene(QPoint(x, y));
0293         QPointF br2Pos = q->mapToScene(QPoint(x + cvW, y + cvH));
0294         int tlCols = m_canvas->items(QRectF(tl1Pos.x(), tl1Pos.y(), tl2Pos.x(), tl2Pos.y())).count();
0295         qCDebug(KGRAPHVIEWERLIB_LOG) << tr1Pos.x() << tr1Pos.y() << tr2Pos.x() << tr2Pos.y();
0296         int trCols = m_canvas->items(QRectF(tr1Pos.x(), tr1Pos.y(), tr2Pos.x(), tr2Pos.y())).count();
0297         int blCols = m_canvas->items(QRectF(bl1Pos.x(), bl1Pos.y(), bl2Pos.x(), bl2Pos.y())).count();
0298         int brCols = m_canvas->items(QRectF(br1Pos.x(), br1Pos.y(), br2Pos.x(), br2Pos.y())).count();
0299         int minCols = tlCols;
0300         zp = m_lastAutoPosition;
0301         switch (zp) {
0302         case KGraphViewerInterface::TopRight:
0303             minCols = trCols;
0304             break;
0305         case KGraphViewerInterface::BottomLeft:
0306             minCols = blCols;
0307             break;
0308         case KGraphViewerInterface::BottomRight:
0309             minCols = brCols;
0310             break;
0311         default:
0312         case KGraphViewerInterface::TopLeft:
0313             minCols = tlCols;
0314             break;
0315         }
0316         if (minCols > tlCols) {
0317             minCols = tlCols;
0318             zp = KGraphViewerInterface::TopLeft;
0319         }
0320         if (minCols > trCols) {
0321             minCols = trCols;
0322             zp = KGraphViewerInterface::TopRight;
0323         }
0324         if (minCols > blCols) {
0325             minCols = blCols;
0326             zp = KGraphViewerInterface::BottomLeft;
0327         }
0328         if (minCols > brCols) {
0329             minCols = brCols;
0330             zp = KGraphViewerInterface::BottomRight;
0331         }
0332 
0333         m_lastAutoPosition = zp;
0334     }
0335 
0336     switch (zp) {
0337     case KGraphViewerInterface::TopRight:
0338         newZoomPos = QPoint(x, 0);
0339         break;
0340     case KGraphViewerInterface::BottomLeft:
0341         newZoomPos = QPoint(0, y);
0342         break;
0343     case KGraphViewerInterface::BottomRight:
0344         newZoomPos = QPoint(x, y);
0345         break;
0346     default:
0347         break;
0348     }
0349     if (newZoomPos != oldZoomPos)
0350         m_birdEyeView->move(newZoomPos);
0351 }
0352 
0353 double DotGraphViewPrivate::detailAdjustedScale()
0354 {
0355     double scale = m_graph->scale();
0356     switch (m_detailLevel) {
0357     case 0:
0358         scale *= 0.7;
0359         break;
0360     case 2:
0361         scale *= 1.3;
0362         break;
0363     }
0364     return scale;
0365 }
0366 
0367 int DotGraphViewPrivate::displaySubgraph(GraphSubgraph *gsubgraph, int zValue, CanvasElement *parent)
0368 {
0369     Q_Q(DotGraphView);
0370     double scale = detailAdjustedScale();
0371 
0372     qreal gh = m_graph->height();
0373 
0374     if (gsubgraph->canvasSubgraph() == nullptr) {
0375         qCDebug(KGRAPHVIEWERLIB_LOG) << "Creating canvas subgraph for" << gsubgraph->id();
0376         CanvasSubgraph *csubgraph = new CanvasSubgraph(q, gsubgraph, m_canvas, parent);
0377         csubgraph->initialize(scale, scale, m_xMargin, m_yMargin, gh);
0378         gsubgraph->setCanvasSubgraph(csubgraph);
0379         //       csubgraph->setZValue(gsubgraph->z());
0380         csubgraph->setZValue(zValue += 2);
0381         csubgraph->show();
0382         m_canvas->addItem(csubgraph);
0383         qCDebug(KGRAPHVIEWERLIB_LOG) << " one CanvasSubgraph... Done";
0384     }
0385     for (GraphElement *element : gsubgraph->content()) {
0386         GraphNode *gnode = dynamic_cast<GraphNode *>(element);
0387         if (gnode->canvasNode() == nullptr) {
0388             qCDebug(KGRAPHVIEWERLIB_LOG) << "Creating canvas node for:" << gnode->id();
0389             CanvasNode *cnode = new CanvasNode(q, gnode, m_canvas);
0390             if (cnode == nullptr)
0391                 continue;
0392             cnode->initialize(scale, scale, m_xMargin, m_yMargin, gh);
0393             gnode->setCanvasNode(cnode);
0394             m_canvas->addItem(cnode);
0395             //       cnode->setZValue(gnode->z());
0396             cnode->setZValue(zValue + 1);
0397             cnode->show();
0398         }
0399         gnode->canvasNode()->computeBoundingRect();
0400     }
0401     gsubgraph->canvasSubgraph()->computeBoundingRect();
0402 
0403     int newZvalue = zValue;
0404     for (GraphSubgraph *ssg : gsubgraph->subgraphs()) {
0405         int hereZvalue = displaySubgraph(ssg, zValue, gsubgraph->canvasSubgraph());
0406         if (hereZvalue > newZvalue)
0407             newZvalue = hereZvalue;
0408     }
0409     return newZvalue;
0410 }
0411 
0412 void DotGraphViewPrivate::setupPopup()
0413 {
0414     Q_Q(DotGraphView);
0415     if (m_popup) {
0416         return;
0417     }
0418     qCDebug(KGRAPHVIEWERLIB_LOG) << "DotGraphView::setupPopup";
0419     m_popup = new QMenu();
0420 
0421     m_layoutAlgoSelectAction = new KSelectAction(i18n("Select Layout Algo"), q);
0422     actionCollection()->addAction("view_layout_algo", m_layoutAlgoSelectAction);
0423 
0424     QAction *lda = new QAction(i18n("Dot"), q);
0425     lda->setWhatsThis(i18n("Layout the graph using the dot program."));
0426     actionCollection()->addAction("layout_dot", lda);
0427     lda->setCheckable(true);
0428 
0429     QAction *lna = new QAction(i18n("Neato"), q);
0430     lna->setWhatsThis(i18n("Layout the graph using the neato program."));
0431     actionCollection()->addAction("layout_neato", lna);
0432     lna->setCheckable(true);
0433 
0434     QAction *lta = new QAction(i18n("Twopi"), q);
0435     lta->setWhatsThis(i18n("Layout the graph using the twopi program."));
0436     actionCollection()->addAction("layout_twopi", lta);
0437     lta->setCheckable(true);
0438 
0439     QAction *lfa = new QAction(i18n("Fdp"), q);
0440     lfa->setWhatsThis(i18n("Layout the graph using the fdp program."));
0441     actionCollection()->addAction("layout_fdp", lfa);
0442     lfa->setCheckable(true);
0443 
0444     QAction *lca = new QAction(i18n("Circo"), q);
0445     lca->setWhatsThis(i18n("Layout the graph using the circo program."));
0446     actionCollection()->addAction("layout_c", lca);
0447     lca->setCheckable(true);
0448 
0449     m_layoutAlgoSelectAction->addAction(lda);
0450     m_layoutAlgoSelectAction->addAction(lna);
0451     m_layoutAlgoSelectAction->addAction(lta);
0452     m_layoutAlgoSelectAction->addAction(lfa);
0453     m_layoutAlgoSelectAction->addAction(lca);
0454 
0455     m_layoutAlgoSelectAction->setCurrentAction(lda);
0456     m_layoutAlgoSelectAction->setEditable(true);
0457     m_layoutAlgoSelectAction->setToolTip(i18n("Choose a Graphviz layout algorithm or edit your own one."));
0458     m_layoutAlgoSelectAction->setWhatsThis(
0459         i18n("Choose a Graphviz layout algorithm or type in your own command that will "
0460              "generate a graph in the xdot format on its standard output. For example, to "
0461              "manually specify the <tt>G</tt> option to the dot command, type in: "
0462              "<tt>dot -Gname=MyGraphName -Txdot </tt>"));
0463     QObject::connect(m_layoutAlgoSelectAction, &KSelectAction::textTriggered, q, &DotGraphView::slotSelectLayoutAlgo);
0464 
0465     QMenu *layoutPopup = m_popup->addMenu(i18n("Layout"));
0466     layoutPopup->addAction(m_layoutAlgoSelectAction);
0467     QAction *slc = layoutPopup->addAction(i18n("Specify layout command"), q, SLOT(slotLayoutSpecify()));
0468     slc->setWhatsThis(i18n("Specify yourself the layout command to use. Given a dot file, it should produce an xdot file on its standard output."));
0469     QAction *rlc = layoutPopup->addAction(i18n("Reset layout command to default"), q, SLOT(slotLayoutReset()));
0470     rlc->setWhatsThis(i18n("Resets the layout command to use to the default depending on the graph type (directed or not)."));
0471 
0472     m_popup->addAction(QIcon::fromTheme("zoom-in"), i18n("Zoom In"), q, SLOT(zoomIn()));
0473     m_popup->addAction(QIcon::fromTheme("zoom-out"), i18n("Zoom Out"), q, SLOT(zoomOut()));
0474 
0475     m_popup->addSeparator();
0476 
0477     KActionMenu *file_exportMenu = new KActionMenu(i18n("Export Graph"), q);
0478     actionCollection()->addAction("file_export", file_exportMenu);
0479     file_exportMenu->setToolTip(i18n("Allows the graph to be exported in another format."));
0480     file_exportMenu->setWhatsThis(
0481         i18n("Use the Export Graph menu to export the graph in another format. "
0482              "There is currently only one export format supported: as a PNG image."));
0483 
0484     m_popup->addAction(file_exportMenu);
0485     QAction *exportToImageAction = new QAction(i18n("As Image..."), q);
0486     exportToImageAction->setWhatsThis(i18n("Export the graph to an image file."));
0487     actionCollection()->addAction("export_image", exportToImageAction);
0488     QObject::connect(exportToImageAction, &QAction::triggered, q, &DotGraphView::slotExportImage);
0489 
0490     file_exportMenu->addAction(exportToImageAction);
0491 
0492     m_popup->addSeparator();
0493 
0494     m_bevEnabledAction = new KToggleAction(QIcon(QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kgraphviewerpart/pics/kgraphviewer-bev.png")), i18n("Enable Bird's-eye View"), q);
0495     actionCollection()->addAction("view_bev_enabled", m_bevEnabledAction);
0496     actionCollection()->setDefaultShortcut(m_bevEnabledAction, Qt::CTRL + Qt::Key_B);
0497     m_bevEnabledAction->setWhatsThis(i18n("Enables or disables the Bird's-eye View"));
0498     QObject::connect(m_bevEnabledAction, &QAction::triggered, q, &DotGraphView::slotBevToggled);
0499     m_bevEnabledAction->setCheckable(true);
0500     m_popup->addAction(m_bevEnabledAction);
0501 
0502     m_bevPopup = new KSelectAction(i18n("Birds-eye View"), q);
0503     m_bevPopup->setWhatsThis(i18n("Allows the Bird's-eye View to be setup."));
0504     m_popup->addAction(m_bevPopup);
0505     actionCollection()->addAction("view_bev", m_bevPopup);
0506 
0507     QAction *btla = new QAction(i18n("Top Left"), q);
0508     btla->setWhatsThis(i18n("Puts the Bird's-eye View in the top-left corner."));
0509     btla->setCheckable(true);
0510     actionCollection()->addAction("bev_top_left", btla);
0511     QObject::connect(btla, &QAction::triggered, q, &DotGraphView::slotBevTopLeft);
0512     QAction *btra = new QAction(i18n("Top Right"), q);
0513     btra->setWhatsThis(i18n("Puts the Bird's-eye View in the top-right corner."));
0514     btra->setCheckable(true);
0515     actionCollection()->addAction("bev_top_right", btra);
0516     QObject::connect(btra, &QAction::triggered, q, &DotGraphView::slotBevTopRight);
0517     QAction *bbla = new QAction(i18n("Bottom Left"), q);
0518     bbla->setWhatsThis(i18n("Puts the Bird's-eye View in the bottom-left corner."));
0519     bbla->setCheckable(true);
0520     actionCollection()->addAction("bev_bottom_left", bbla);
0521     QObject::connect(bbla, &QAction::triggered, q, &DotGraphView::slotBevBottomLeft);
0522     QAction *bbra = new QAction(i18n("Bottom Right"), q);
0523     bbra->setWhatsThis(i18n("Puts the Bird's-eye View in the bottom-right corner."));
0524     bbra->setCheckable(true);
0525     actionCollection()->addAction("bev_bottom_right", bbra);
0526     QObject::connect(bbra, &QAction::triggered, q, &DotGraphView::slotBevBottomRight);
0527     QAction *bba = new QAction(i18n("Automatic"), q);
0528     bba->setWhatsThis(i18n("Let KGraphViewer automatically choose the position of the Bird's-eye View."));
0529     bba->setCheckable(true);
0530     actionCollection()->addAction("bev_automatic", bba);
0531     QObject::connect(bba, &QAction::triggered, q, &DotGraphView::slotBevAutomatic);
0532     m_bevPopup->addAction(btla);
0533     m_bevPopup->addAction(btra);
0534     m_bevPopup->addAction(bbla);
0535     m_bevPopup->addAction(bbra);
0536     m_bevPopup->addAction(bba);
0537     switch (m_zoomPosition) {
0538     case KGraphViewerInterface::TopLeft:
0539         btla->setChecked(true);
0540         break;
0541     case KGraphViewerInterface::TopRight:
0542         btra->setChecked(true);
0543         break;
0544     case KGraphViewerInterface::BottomLeft:
0545         bbla->setChecked(true);
0546         break;
0547     case KGraphViewerInterface::BottomRight:
0548         bbra->setChecked(true);
0549         break;
0550     case KGraphViewerInterface::Auto:
0551         bba->setChecked(true);
0552         break;
0553     }
0554 
0555     qCDebug(KGRAPHVIEWERLIB_LOG) << "    m_bevEnabledAction setting checked to : " << KGraphViewerPartSettings::birdsEyeViewEnabled();
0556     m_bevEnabledAction->setChecked(KGraphViewerPartSettings::birdsEyeViewEnabled());
0557     m_bevPopup->setEnabled(KGraphViewerPartSettings::birdsEyeViewEnabled());
0558 }
0559 
0560 void DotGraphViewPrivate::exportToImage()
0561 {
0562     // write current content of canvas as image to file
0563     if (!m_canvas)
0564         return;
0565 
0566     QStringList writableMimetypes;
0567     for (const QByteArray &mimeType : QImageWriter::supportedMimeTypes()) {
0568         writableMimetypes.append(QString::fromLatin1(mimeType));
0569     }
0570     const QString svgMimetype = QStringLiteral("image/svg+xml");
0571     if (!writableMimetypes.contains(svgMimetype)) {
0572         writableMimetypes.append(svgMimetype);
0573     }
0574 
0575     QFileDialog fileDialog(nullptr, i18n("Select file"));
0576     fileDialog.setMimeTypeFilters(writableMimetypes);
0577     fileDialog.setAcceptMode(QFileDialog::AcceptSave);
0578     if (fileDialog.exec() != QFileDialog::Accepted) {
0579         return;
0580     }
0581     const QString fn = fileDialog.selectedFiles().at(0);
0582 
0583     if (!fn.isEmpty()) {
0584         if (fn.toLower().endsWith(QLatin1String(".svg"))) {
0585             QSvgGenerator generator;
0586             generator.setFileName(fn);
0587             generator.setSize(m_canvas->sceneRect().size().toSize());
0588             //       generator.setViewBox();
0589             generator.setTitle(i18n("Graph SVG Generated by KGraphViewer"));
0590             generator.setDescription(i18n("Graph SVG Generated by KGraphViewer."));
0591             QPainter painter;
0592             painter.begin(&generator);
0593             m_canvas->render(&painter);
0594             painter.end();
0595         } else {
0596             QPixmap pix(m_canvas->sceneRect().size().toSize());
0597             QPainter p(&pix);
0598             m_canvas->render(&p);
0599             pix.save(fn, nullptr, 100);
0600         }
0601     }
0602 }
0603 
0604 //
0605 // DotGraphView
0606 //
0607 DotGraphView::DotGraphView(KActionCollection *actions, QWidget *parent)
0608     : QGraphicsView(parent)
0609     , d_ptr(new DotGraphViewPrivate(actions, this))
0610 {
0611     // qCDebug(KGRAPHVIEWERLIB_LOG) << "New node pic=" << KGlobal::dirs()->findResource("data","kgraphviewerpart/pics/kgraphviewer-newnode.png");
0612     Q_D(DotGraphView);
0613     d->m_canvas = nullptr;
0614     d->m_xMargin = d->m_yMargin = 0;
0615     d->m_birdEyeView = new PannerView(this);
0616     d->m_cvZoom = 1;
0617 
0618     // if there are ever graphic glitches to be found, remove this again
0619     setOptimizationFlags(QGraphicsView::DontAdjustForAntialiasing | QGraphicsView::DontClipPainter | QGraphicsView::DontSavePainterState);
0620 
0621     setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
0622     setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
0623 
0624     d->m_birdEyeView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0625     d->m_birdEyeView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0626     d->m_birdEyeView->raise();
0627     d->m_birdEyeView->hide();
0628 
0629     setFocusPolicy(Qt::StrongFocus);
0630     setBackgroundRole(QPalette::Window);
0631     //   viewport()->setMouseTracking(true);
0632 
0633     connect(d->m_birdEyeView, &PannerView::zoomRectMovedTo, this, &DotGraphView::zoomRectMovedTo);
0634     connect(d->m_birdEyeView, &PannerView::zoomRectMoveFinished, this, &DotGraphView::zoomRectMoveFinished);
0635 
0636     setWhatsThis(
0637         i18n("<h1>Graphviz DOT format graph visualization</h1>"
0638              "<p>If the graph is larger than the widget area, an overview "
0639              "panner is shown in one edge. Choose through the context menu "
0640              "if the optimal position of this overview should be automatically "
0641              "computed or put it where you want.</p>"
0642              "<h2>How to work with it?</h2>"
0643              "<ul>"
0644              "<li>To move the graph, you can:"
0645              "  <ul>"
0646              "    <li>click & drag it</li>"
0647              "    <li>use the elevators</li>"
0648              "    <li>press the arrows keys</li>"
0649              "    <li>click somewhere in the panner view</li>"
0650              "    <li>use the mouse wheel (up and down with no modifier, left and right with the <Alt> key pressed)</li>"
0651              "    <li>or click & drag the panner view</li>"
0652              "  </ul>"
0653              "</li>"
0654              "<li>To zoom, you can either use the zoom in and zoom out toolbar buttons, or click on the <Shift> key while rolling your mouse wheel.</li>"
0655              "<li>Try the contextual menu (usually by right-clicking) to discover other "
0656              "possibilities.</li>"
0657              "<li>Try the <tt>Print preview</tt> or the <tt>Page setup</tt> buttons to explore the printing options.</li>"
0658              "</ul>"));
0659 
0660     readViewConfig();
0661 
0662     QTransform m;
0663     m.scale(d->m_zoom, d->m_zoom);
0664     setTransform(m);
0665     d->setupPopup();
0666     setInteractive(true);
0667     setDragMode(NoDrag);
0668     setRenderHint(QPainter::Antialiasing);
0669 
0670     connect(&d->m_loadThread, &LoadAGraphThread::finished, this, &DotGraphView::slotAGraphReadFinished);
0671     connect(&d->m_layoutThread, &LoadAGraphThread::finished, this, &DotGraphView::slotAGraphLayoutFinished);
0672 }
0673 
0674 DotGraphView::~DotGraphView()
0675 {
0676     saveViewConfig();
0677     Q_D(DotGraphView);
0678     delete d;
0679 }
0680 
0681 KGraphViewerInterface::PannerPosition DotGraphView::zoomPos() const
0682 {
0683     Q_D(const DotGraphView);
0684     return d->m_zoomPosition;
0685 }
0686 
0687 double DotGraphView::zoom() const
0688 {
0689     Q_D(const DotGraphView);
0690     return d->m_zoom;
0691 }
0692 KSelectAction *DotGraphView::bevPopup()
0693 {
0694     Q_D(DotGraphView);
0695     return d->m_bevPopup;
0696 }
0697 
0698 DotGraph *DotGraphView::graph()
0699 {
0700     Q_D(DotGraphView);
0701     return d->m_graph;
0702 }
0703 const DotGraph *DotGraphView::graph() const
0704 {
0705     Q_D(const DotGraphView);
0706     return d->m_graph;
0707 }
0708 
0709 const GraphElement *DotGraphView::defaultNewElement() const
0710 {
0711     Q_D(const DotGraphView);
0712     return d->m_defaultNewElement;
0713 }
0714 QPixmap DotGraphView::defaultNewElementPixmap() const
0715 {
0716     Q_D(const DotGraphView);
0717     return d->m_defaultNewElementPixmap;
0718 }
0719 
0720 void DotGraphView::setDefaultNewElement(GraphElement *elem)
0721 {
0722     Q_D(DotGraphView);
0723     d->m_defaultNewElement = elem;
0724 }
0725 void DotGraphView::setDefaultNewElementPixmap(const QPixmap &pm)
0726 {
0727     Q_D(DotGraphView);
0728     d->m_defaultNewElementPixmap = pm;
0729 }
0730 
0731 bool DotGraphView::isReadWrite() const
0732 {
0733     Q_D(const DotGraphView);
0734     return d->m_readWrite;
0735 }
0736 bool DotGraphView::isReadOnly() const
0737 {
0738     Q_D(const DotGraphView);
0739     return !d->m_readWrite;
0740 }
0741 
0742 bool DotGraphView::highlighting()
0743 {
0744     Q_D(DotGraphView);
0745     return d->m_highlighting;
0746 }
0747 void DotGraphView::setHighlighting(bool highlightingValue)
0748 {
0749     Q_D(DotGraphView);
0750     d->m_highlighting = highlightingValue;
0751 }
0752 
0753 DotGraphView::EditingMode DotGraphView::editingMode() const
0754 {
0755     Q_D(const DotGraphView);
0756     return d->m_editingMode;
0757 }
0758 
0759 void DotGraphView::setBackgroundColor(const QColor &color)
0760 {
0761     Q_D(DotGraphView);
0762     d->m_backgroundColor = color;
0763     d->m_canvas->setBackgroundBrush(QBrush(d->m_backgroundColor));
0764 }
0765 
0766 bool DotGraphView::initEmpty()
0767 {
0768     Q_D(DotGraphView);
0769     d->m_birdEyeView->hide();
0770     d->m_birdEyeView->setScene(nullptr);
0771 
0772     if (d->m_canvas) {
0773         delete d->m_canvas;
0774         d->m_canvas = nullptr;
0775     }
0776 
0777     delete d->m_graph;
0778     d->m_graph = new DotGraph();
0779     connect(d->m_graph, &DotGraph::readyToDisplay, this, &DotGraphView::displayGraph);
0780 
0781     if (d->m_readWrite) {
0782         d->m_graph->setReadWrite();
0783     }
0784 
0785     //   qCDebug(KGRAPHVIEWERLIB_LOG) << "Parsing " << m_graph->dotFileName() << " with " << m_graph->layoutCommand();
0786     d->m_xMargin = 50;
0787     d->m_yMargin = 50;
0788 
0789     QGraphicsScene *newCanvas = new QGraphicsScene();
0790     QGraphicsSimpleTextItem *item = newCanvas->addSimpleText(i18n("no graph loaded"));
0791     //   qCDebug(KGRAPHVIEWERLIB_LOG) << "Created canvas " << newCanvas;
0792 
0793     d->m_birdEyeView->setScene(newCanvas);
0794     //   std::cerr << "After m_birdEyeView set canvas" << std::endl;
0795 
0796     setScene(newCanvas);
0797     d->m_canvas = newCanvas;
0798     centerOn(item);
0799 
0800     d->m_cvZoom = 0;
0801 
0802     return true;
0803 }
0804 
0805 bool DotGraphView::slotLoadLibrary(graph_t *graph)
0806 {
0807     qCDebug(KGRAPHVIEWERLIB_LOG) << "graph_t";
0808     Q_D(DotGraphView);
0809     d->m_birdEyeView->setScene(nullptr);
0810 
0811     if (d->m_canvas) {
0812         d->m_canvas->deleteLater();
0813         d->m_canvas = nullptr;
0814     }
0815 
0816     QString layoutCommand = (d->m_graph ? d->m_graph->layoutCommand() : QString());
0817     delete d->m_graph;
0818 
0819     if (layoutCommand.isEmpty())
0820         layoutCommand = "dot";
0821 
0822     qCDebug(KGRAPHVIEWERLIB_LOG) << "layoutCommand:" << layoutCommand;
0823     d->m_graph = new DotGraph(layoutCommand, "");
0824     d->m_graph->setUseLibrary(true);
0825 
0826     connect(d->m_graph, &DotGraph::readyToDisplay, this, &DotGraphView::displayGraph);
0827     connect(this, &DotGraphView::removeEdge, d->m_graph, &DotGraph::removeEdge);
0828     connect(this, &DotGraphView::removeNodeNamed, d->m_graph, &DotGraph::removeNodeNamed);
0829     connect(this, &DotGraphView::removeElement, d->m_graph, &DotGraph::removeElement);
0830 
0831     if (d->m_readWrite) {
0832         d->m_graph->setReadWrite();
0833     }
0834 
0835     if (layoutCommand.isEmpty()) {
0836         layoutCommand = d->m_graph->chooseLayoutProgramForFile(d->m_graph->dotFileName());
0837     }
0838     d->m_graph->layoutCommand(layoutCommand);
0839 
0840     GVC_t *gvc = gvContext();
0841     threadsafe_wrap_gvLayout(gvc, graph, layoutCommand.toUtf8().data());
0842     threadsafe_wrap_gvRender(gvc, graph, "xdot", nullptr);
0843 
0844     d->m_xMargin = 50;
0845     d->m_yMargin = 50;
0846 
0847     QGraphicsScene *newCanvas = new QGraphicsScene();
0848     qCDebug(KGRAPHVIEWERLIB_LOG) << "Created canvas " << newCanvas;
0849 
0850     d->m_birdEyeView->setScene(newCanvas);
0851     // std::cerr << "After m_birdEyeView set canvas" << std::endl;
0852 
0853     setScene(newCanvas);
0854     connect(newCanvas, &QGraphicsScene::selectionChanged, this, &DotGraphView::slotSelectionChanged);
0855     d->m_canvas = newCanvas;
0856 
0857     d->m_cvZoom = 0;
0858 
0859     d->m_graph->updateWithGraph(graph);
0860     d->m_layoutAlgoSelectAction->setCurrentAction(d->m_graph->layoutCommand(), Qt::CaseInsensitive);
0861 
0862     gvFreeLayout(gvc, graph);
0863     gvFreeContext(gvc);
0864     return true;
0865 }
0866 
0867 bool DotGraphView::loadDot(const QString &dotFileName)
0868 {
0869     qCDebug(KGRAPHVIEWERLIB_LOG) << "'" << dotFileName << "'";
0870     Q_D(DotGraphView);
0871     d->m_birdEyeView->setScene(nullptr);
0872 
0873     if (d->m_canvas) {
0874         d->m_canvas->deleteLater();
0875         d->m_canvas = nullptr;
0876     }
0877 
0878     QString layoutCommand = (d->m_graph ? d->m_graph->layoutCommand() : QString());
0879     delete d->m_graph;
0880 
0881     d->m_graph = new DotGraph(layoutCommand, dotFileName);
0882     connect(d->m_graph, &DotGraph::readyToDisplay, this, &DotGraphView::displayGraph);
0883 
0884     if (d->m_readWrite) {
0885         d->m_graph->setReadWrite();
0886     }
0887     if (layoutCommand.isEmpty()) {
0888         layoutCommand = d->m_graph->chooseLayoutProgramForFile(d->m_graph->dotFileName());
0889     }
0890     d->m_graph->layoutCommand(layoutCommand);
0891 
0892     //   qCDebug(KGRAPHVIEWERLIB_LOG) << "Parsing " << m_graph->dotFileName() << " with " << m_graph->layoutCommand();
0893     d->m_xMargin = 50;
0894     d->m_yMargin = 50;
0895 
0896     QGraphicsScene *newCanvas = new QGraphicsScene();
0897     qCDebug(KGRAPHVIEWERLIB_LOG) << "Created canvas " << newCanvas;
0898 
0899     d->m_birdEyeView->setScene(newCanvas);
0900     //   std::cerr << "After m_birdEyeView set canvas" << std::endl;
0901 
0902     setScene(newCanvas);
0903     connect(newCanvas, &QGraphicsScene::selectionChanged, this, &DotGraphView::slotSelectionChanged);
0904     d->m_canvas = newCanvas;
0905 
0906     QGraphicsSimpleTextItem *loadingLabel = newCanvas->addSimpleText(i18n("graph %1 is getting loaded...", dotFileName));
0907     loadingLabel->setZValue(100);
0908     centerOn(loadingLabel);
0909 
0910     d->m_cvZoom = 0;
0911 
0912     if (!d->m_graph->parseDot(d->m_graph->dotFileName())) {
0913         qCWarning(KGRAPHVIEWERLIB_LOG) << "NOT successfully parsed!";
0914         loadingLabel->setText(i18n("error parsing file %1", dotFileName));
0915         return false;
0916     }
0917     d->m_layoutAlgoSelectAction->setCurrentAction(d->m_graph->layoutCommand(), Qt::CaseInsensitive);
0918     return true;
0919 }
0920 
0921 bool DotGraphView::loadLibrarySync(const QString &dotFileName)
0922 {
0923     qCDebug(KGRAPHVIEWERLIB_LOG) << "loading sync: '" << dotFileName << "'";
0924     Q_D(DotGraphView);
0925     if (d->m_canvas)
0926         d->m_canvas->clear();
0927     QGraphicsSimpleTextItem *loadingLabel = d->m_canvas->addSimpleText(i18n("graph %1 is getting loaded...", dotFileName));
0928     loadingLabel->setZValue(100);
0929     centerOn(loadingLabel);
0930 
0931     // HACK: store filename in m_loadThread data structure for pick up by slotAGraphLayoutFinished()
0932     // Both methods loadLibrarySync(QString) and loadLibrary(QString) after loading the file
0933     // activate the m_layoutThread. And it is only the handler of that thread's finished signal.
0934     // slotAGraphLayoutFinished(), which then completes the loading of the file and stores the
0935     // filename with the graph data, taking it from m_loadThread.
0936     // As intermediate solution to a rewrite of the loading code, for now the filename is simply
0937     // also stored in the m_loadThread object from this code path, so the rest of the existing code
0938     // does not need to be further adapted.
0939     d->m_loadThread.setDotFileName(dotFileName);
0940 
0941     qCDebug(KGRAPHVIEWERLIB_LOG) << dotFileName;
0942     FILE *fp = fopen(dotFileName.toUtf8().data(), "r");
0943     if (!fp) {
0944         qCWarning(KGRAPHVIEWERLIB_LOG) << "Failed to open file " << dotFileName;
0945         return false;
0946     }
0947     graph_t *graph = agread(fp, nullptr);
0948     if (!graph) {
0949         qCWarning(KGRAPHVIEWERLIB_LOG) << "Failed to read file, retrying to work around graphviz bug(?)";
0950         rewind(fp);
0951         graph = agread(fp, nullptr);
0952     }
0953     fclose(fp);
0954     if (!graph) {
0955         qCWarning(KGRAPHVIEWERLIB_LOG) << "Failed to read file " << dotFileName;
0956         return false;
0957     }
0958 
0959     QString layoutCommand = (d->m_graph ? d->m_graph->layoutCommand() : QString());
0960     if (layoutCommand.isEmpty()) {
0961         layoutCommand = d->m_graph ? d->m_graph->chooseLayoutProgramForFile(dotFileName) : "dot";
0962     }
0963     d->m_layoutThread.layoutGraph(graph, layoutCommand);
0964 
0965     return true;
0966 }
0967 
0968 bool DotGraphView::loadLibrary(const QString &dotFileName)
0969 {
0970     qCDebug(KGRAPHVIEWERLIB_LOG) << "'" << dotFileName << "'";
0971     Q_D(DotGraphView);
0972     if (d->m_canvas)
0973         d->m_canvas->clear();
0974     QGraphicsSimpleTextItem *loadingLabel = d->m_canvas->addSimpleText(i18n("graph %1 is getting loaded...", dotFileName));
0975     loadingLabel->setZValue(100);
0976     centerOn(loadingLabel);
0977 
0978     d->m_loadThread.loadFile(dotFileName);
0979 
0980     return true;
0981 }
0982 
0983 bool DotGraphView::loadLibrary(graph_t *graph, const QString &layoutCommand)
0984 {
0985     qCDebug(KGRAPHVIEWERLIB_LOG) << "graph_t";
0986     Q_D(DotGraphView);
0987     d->m_birdEyeView->setScene(nullptr);
0988 
0989     if (d->m_canvas) {
0990         d->m_canvas->deleteLater();
0991         d->m_canvas = nullptr;
0992     }
0993 
0994     delete d->m_graph;
0995     d->m_graph = nullptr;
0996 
0997     if (!graph)
0998         return false;
0999 
1000     qCDebug(KGRAPHVIEWERLIB_LOG) << "layoutCommand:" << layoutCommand;
1001     d->m_graph = new DotGraph(layoutCommand, "");
1002     d->m_graph->setUseLibrary(true);
1003 
1004     connect(d->m_graph, &DotGraph::readyToDisplay, this, &DotGraphView::displayGraph);
1005 
1006     if (d->m_readWrite) {
1007         d->m_graph->setReadWrite();
1008     }
1009 
1010     d->m_xMargin = 50;
1011     d->m_yMargin = 50;
1012 
1013     QGraphicsScene *newCanvas = new QGraphicsScene();
1014     qCDebug(KGRAPHVIEWERLIB_LOG) << "Created canvas " << newCanvas;
1015 
1016     d->m_birdEyeView->setScene(newCanvas);
1017     setScene(newCanvas);
1018     connect(newCanvas, &QGraphicsScene::selectionChanged, this, &DotGraphView::slotSelectionChanged);
1019     d->m_canvas = newCanvas;
1020 
1021     d->m_cvZoom = 0;
1022 
1023     d->m_graph->updateWithGraph(graph);
1024     d->m_layoutAlgoSelectAction->setCurrentAction(d->m_graph->layoutCommand(), Qt::CaseInsensitive);
1025 
1026     return true;
1027 }
1028 
1029 void DotGraphView::slotSelectionChanged()
1030 {
1031     qCDebug(KGRAPHVIEWERLIB_LOG) << scene()->selectedItems().size();
1032 }
1033 
1034 bool DotGraphView::displayGraph()
1035 {
1036     Q_D(DotGraphView);
1037     qCDebug(KGRAPHVIEWERLIB_LOG) << d->m_graph->backColor();
1038     //   hide();
1039     viewport()->setUpdatesEnabled(false);
1040 
1041     if (d->m_graph->backColor().size() != 0) {
1042         setBackgroundColor(QColor(d->m_graph->backColor()));
1043     }
1044 
1045     if (d->m_graph->nodes().size() > KGV_MAX_PANNER_NODES) {
1046         d->m_birdEyeView->setDrawingEnabled(false);
1047     }
1048     //  QCanvasEllipse* eItem;
1049     double scale = d->detailAdjustedScale();
1050 
1051     qreal gh = d->m_graph->height();
1052 
1053     d->m_xMargin = 50;
1054     d->m_yMargin = 50;
1055 
1056     //   m_canvas->setSceneRect(0,0,w+2*m_xMargin, h+2*m_yMargin);
1057     //   m_canvas->setBackgroundBrush(QBrush(QColor(m_graph->backColor())));
1058     d->m_canvas->setBackgroundBrush(QBrush(d->m_backgroundColor));
1059 
1060     //   qCDebug(KGRAPHVIEWERLIB_LOG) << "sceneRect is now " << m_canvas->sceneRect();
1061 
1062     qCDebug(KGRAPHVIEWERLIB_LOG) << "Creating" << d->m_graph->subgraphs().size() << "CanvasSubgraphs from" << d->m_graph;
1063     int zvalue = -1;
1064     for (GraphSubgraph *gsubgraph : d->m_graph->subgraphs()) {
1065         int newZvalue = d->displaySubgraph(gsubgraph, zvalue);
1066         if (newZvalue > zvalue)
1067             zvalue = newZvalue;
1068     }
1069 
1070     qCDebug(KGRAPHVIEWERLIB_LOG) << "Creating" << d->m_graph->nodes().size() << "nodes from" << d->m_graph;
1071     GraphNodeMap::const_iterator it = d->m_graph->nodes().constBegin();
1072     for (; it != d->m_graph->nodes().constEnd(); it++) {
1073         const QString &id = it.key();
1074         GraphNode *gnode = it.value();
1075         qCDebug(KGRAPHVIEWERLIB_LOG) << "Handling" << id << (void *)gnode;
1076         qCDebug(KGRAPHVIEWERLIB_LOG) << "  gnode id=" << gnode->id();
1077         qCDebug(KGRAPHVIEWERLIB_LOG) << "  canvasNode=" << (void *)gnode->canvasNode();
1078         if (gnode->canvasNode() == nullptr) {
1079             qCDebug(KGRAPHVIEWERLIB_LOG) << "Creating canvas node for" << gnode->id();
1080             CanvasNode *cnode = new CanvasNode(this, gnode, d->m_canvas);
1081             if (cnode == nullptr)
1082                 continue;
1083             cnode->initialize(scale, scale, d->m_xMargin, d->m_yMargin, gh);
1084             gnode->setCanvasNode(cnode);
1085             d->m_canvas->addItem(cnode);
1086             //       cnode->setZValue(gnode->z());
1087             cnode->setZValue(zvalue + 1);
1088             cnode->show();
1089         }
1090         gnode->canvasNode()->computeBoundingRect();
1091     }
1092 
1093     qCDebug(KGRAPHVIEWERLIB_LOG) << "Creating" << d->m_graph->edges().size() << "edges from" << d->m_graph;
1094     for (GraphEdge *gedge : d->m_graph->edges()) {
1095         qCDebug(KGRAPHVIEWERLIB_LOG) << "One GraphEdge:" << gedge->id();
1096         if (gedge->canvasEdge() == nullptr && gedge->fromNode() && gedge->toNode()) {
1097             qCDebug(KGRAPHVIEWERLIB_LOG) << "New CanvasEdge for" << gedge->id();
1098             qCDebug(KGRAPHVIEWERLIB_LOG) << "edge fromNode=" << (void *)gedge->fromNode();
1099             qCDebug(KGRAPHVIEWERLIB_LOG) << "              " << gedge->fromNode()->id();
1100             qCDebug(KGRAPHVIEWERLIB_LOG) << "edge toNode=" << (void *)gedge->toNode();
1101             qCDebug(KGRAPHVIEWERLIB_LOG) << "              " << gedge->toNode()->id();
1102             CanvasEdge *cedge = new CanvasEdge(this, gedge, scale, scale, d->m_xMargin, d->m_yMargin, gh, d->m_graph->wdhcf(), d->m_graph->hdvcf());
1103 
1104             gedge->setCanvasEdge(cedge);
1105             //     std::cerr << "setting z = " << gedge->z() << std::endl;
1106             //    cedge->setZValue(gedge->z());
1107             cedge->setZValue(zvalue + 2);
1108             cedge->show();
1109             d->m_canvas->addItem(cedge);
1110         }
1111         if (gedge->canvasEdge())
1112             gedge->canvasEdge()->computeBoundingRect();
1113     }
1114     qCDebug(KGRAPHVIEWERLIB_LOG) << "Adding graph render operations: " << d->m_graph->renderOperations().size();
1115     for (const DotRenderOp &dro : d->m_graph->renderOperations()) {
1116         if (dro.renderop == "T") {
1117             //       std::cerr << "Adding graph label '"<<dro.str<<"'" << std::endl;
1118             const QString &str = dro.str;
1119             int stringWidthGoal = int(dro.integers[3] * scale);
1120             int fontSize = d->m_graph->fontSize();
1121             QFont *font = FontsCache::changeable().fromName(d->m_graph->fontName());
1122             font->setPointSize(fontSize);
1123             QFontMetrics fm(*font);
1124             while (fm.horizontalAdvance(str) > stringWidthGoal && fontSize > 1) {
1125                 fontSize--;
1126                 font->setPointSize(fontSize);
1127                 fm = QFontMetrics(*font);
1128             }
1129             QGraphicsSimpleTextItem *labelView = new QGraphicsSimpleTextItem(str, d->m_canvas->activePanel());
1130             labelView->setFont(*font);
1131             labelView->setPos((scale * ((dro.integers[0]) + (((dro.integers[2]) * (dro.integers[3])) / 2) - ((dro.integers[3]) / 2)) + d->m_xMargin), ((gh - (dro.integers[1])) * scale) + d->m_yMargin);
1132             /// @todo port that ; how to set text color ?
1133             labelView->setPen(QPen(Dot2QtConsts::componentData().qtColor(d->m_graph->fontColor())));
1134             labelView->setFont(*font);
1135             d->m_labelViews.insert(labelView);
1136         }
1137     }
1138 
1139     qCDebug(KGRAPHVIEWERLIB_LOG) << "Finalizing";
1140     d->m_cvZoom = 0;
1141     d->updateSizes();
1142 
1143     centerOn(d->m_canvas->sceneRect().center());
1144 
1145     viewport()->setUpdatesEnabled(true);
1146     QSet<QGraphicsSimpleTextItem *>::iterator labelViewsIt, labelViewsIt_end;
1147     labelViewsIt = d->m_labelViews.begin();
1148     labelViewsIt_end = d->m_labelViews.end();
1149     for (; labelViewsIt != labelViewsIt_end; labelViewsIt++) {
1150         (*labelViewsIt)->show();
1151     }
1152     d->m_canvas->update();
1153 
1154     emit graphLoaded();
1155 
1156     return true;
1157 }
1158 
1159 void DotGraphView::focusInEvent(QFocusEvent *)
1160 {
1161     Q_D(DotGraphView);
1162     if (!d->m_canvas)
1163         return;
1164 
1165     //   m_canvas->update();
1166 }
1167 
1168 void DotGraphView::focusOutEvent(QFocusEvent *e)
1169 {
1170     // trigger updates as in focusInEvent
1171     focusInEvent(e);
1172 }
1173 
1174 void DotGraphView::scrollViewPercent(bool horizontal, int percent)
1175 {
1176     QScrollBar *scrollbar = horizontal ? horizontalScrollBar() : verticalScrollBar();
1177     int amount = horizontal ? viewport()->width() : viewport()->height();
1178     amount = amount * percent / 100;
1179     scrollbar->setValue(scrollbar->value() + amount);
1180 }
1181 
1182 void DotGraphView::keyPressEvent(QKeyEvent *e)
1183 {
1184     Q_D(DotGraphView);
1185     if (!d->m_canvas) {
1186         e->ignore();
1187         return;
1188     }
1189 
1190     // move canvas...
1191     if (e->key() == Qt::Key_Home)
1192         verticalScrollBar()->setValue(verticalScrollBar()->minimum());
1193     else if (e->key() == Qt::Key_End)
1194         verticalScrollBar()->setValue(verticalScrollBar()->maximum());
1195     else if (e->key() == Qt::Key_PageUp)
1196         scrollViewPercent(false, -50);
1197     else if (e->key() == Qt::Key_PageDown)
1198         scrollViewPercent(false, 50);
1199     else if (e->key() == Qt::Key_Left)
1200         scrollViewPercent(true, -10);
1201     else if (e->key() == Qt::Key_Right)
1202         scrollViewPercent(true, 10);
1203     else if (e->key() == Qt::Key_Down)
1204         scrollViewPercent(false, 10);
1205     else if (e->key() == Qt::Key_Up)
1206         scrollViewPercent(false, -10);
1207     else {
1208         e->ignore();
1209         return;
1210     }
1211 }
1212 
1213 void DotGraphView::wheelEvent(QWheelEvent *e)
1214 {
1215     Q_D(DotGraphView);
1216     if (!d->m_canvas) {
1217         e->ignore();
1218         return;
1219     }
1220     e->accept();
1221     if (QApplication::keyboardModifiers() == Qt::ShiftModifier || QApplication::keyboardModifiers() == Qt::ControlModifier) {
1222         qCDebug(KGRAPHVIEWERLIB_LOG) << " + Shift/Ctrl: zooming";
1223         // move canvas...
1224         if (e->delta() < 0) {
1225             zoomOut();
1226         } else {
1227             zoomIn();
1228         }
1229     } else {
1230         qCDebug(KGRAPHVIEWERLIB_LOG) << " : scrolling ";
1231         scrollViewPercent(e->orientation() == Qt::Horizontal, e->delta() < 0 ? 10 : -10);
1232     }
1233 }
1234 
1235 void DotGraphView::zoomIn()
1236 {
1237     applyZoom(1.10);
1238 }
1239 
1240 void DotGraphView::zoomOut()
1241 {
1242     applyZoom(.90);
1243 }
1244 
1245 void DotGraphView::setZoomFactor(double newZoom)
1246 {
1247     Q_D(DotGraphView);
1248     if (newZoom < 0.1 || newZoom > 10)
1249         return;
1250     d->m_zoom = newZoom;
1251     if (d->m_zoom > 1.0 && d->m_zoom < 1.1) {
1252         d->m_zoom = 1;
1253     }
1254 
1255     setUpdatesEnabled(false);
1256     QTransform m;
1257     m.scale(d->m_zoom, d->m_zoom);
1258     setTransform(m);
1259     emit zoomed(d->m_zoom);
1260     setUpdatesEnabled(true);
1261     d->updateSizes();
1262 }
1263 
1264 void DotGraphView::applyZoom(double factor)
1265 {
1266     Q_D(DotGraphView);
1267     setZoomFactor(d->m_zoom * factor);
1268 }
1269 
1270 void DotGraphView::scrollContentsBy(int dx, int dy)
1271 {
1272     Q_D(DotGraphView);
1273     QGraphicsView::scrollContentsBy(dx, dy);
1274     if (d->m_birdEyeView && scene()) { // we might be shutting down
1275         d->m_birdEyeView->moveZoomRectTo(mapToScene(viewport()->rect()).boundingRect().center(), false);
1276     }
1277 }
1278 
1279 void DotGraphView::resizeEvent(QResizeEvent *e)
1280 {
1281     Q_D(DotGraphView);
1282     qCDebug(KGRAPHVIEWERLIB_LOG) << "resizeEvent";
1283     QGraphicsView::resizeEvent(e);
1284     if (d->m_canvas)
1285         d->updateSizes(e->size());
1286     //   std::cerr << "resizeEvent end" << std::endl;
1287 }
1288 
1289 void DotGraphView::zoomRectMovedTo(QPointF newZoomPos)
1290 {
1291     //   qCDebug(KGRAPHVIEWERLIB_LOG) << "DotGraphView::zoomRectMovedTo " << newZoomPos;
1292     centerOn(newZoomPos);
1293 }
1294 
1295 void DotGraphView::zoomRectMoveFinished()
1296 {
1297     Q_D(DotGraphView);
1298     //    qCDebug(KGRAPHVIEWERLIB_LOG) << "zoomRectMoveFinished";
1299     d->updateBirdEyeView();
1300     //   std::cerr << "zoomRectMoveFinished end" << std::endl;
1301 }
1302 
1303 void DotGraphView::mousePressEvent(QMouseEvent *e)
1304 {
1305     Q_D(DotGraphView);
1306     if (e->button() != Qt::LeftButton) {
1307         return;
1308     }
1309     qCDebug(KGRAPHVIEWERLIB_LOG) << e << d->m_editingMode;
1310     QGraphicsView::mousePressEvent(e);
1311 
1312     if (d->m_editingMode == AddNewElement) {
1313         double scale = d->detailAdjustedScale();
1314 
1315         qreal gh = d->m_graph->height();
1316 
1317         QPointF pos = mapToScene(e->pos().x() - d->m_defaultNewElementPixmap.width() / 2, e->pos().y() - d->m_defaultNewElementPixmap.height() / 2);
1318         GraphNode *newNode = new GraphNode();
1319         newNode->attributes() = d->m_newElementAttributes;
1320         if (newNode->attributes().find("id") == newNode->attributes().end()) {
1321             newNode->setId(QString("NewNode%1").arg(d->m_graph->nodes().size()));
1322         }
1323         if (newNode->attributes().find("label") == newNode->attributes().end()) {
1324             newNode->setLabel(newNode->id());
1325         }
1326         d->m_graph->nodes().insert(newNode->id(), newNode);
1327         CanvasNode *newCNode = new CanvasNode(this, newNode, d->m_canvas);
1328         newCNode->initialize(scale, scale, d->m_xMargin, d->m_yMargin, gh);
1329         newNode->setCanvasNode(newCNode);
1330         scene()->addItem(newCNode);
1331         qCDebug(KGRAPHVIEWERLIB_LOG) << "setting pos to " << pos;
1332         newCNode->setPos(pos);
1333         newCNode->setZValue(100);
1334         newCNode->show();
1335 
1336         d->m_editingMode = None;
1337         unsetCursor();
1338         emit newNodeAdded(newNode->id());
1339     } else if (d->m_editingMode == SelectingElements) {
1340     } else {
1341         if (d->m_editingMode != None && itemAt(e->pos()) == nullptr) // click outside any item: unselect all
1342         {
1343             if (d->m_editingMode == DrawNewEdge) // was drawing an edge; cancel it
1344             {
1345                 if (d->m_newEdgeDraft) {
1346                     d->m_newEdgeDraft->hide();
1347                     scene()->removeItem(d->m_newEdgeDraft);
1348                     delete d->m_newEdgeDraft;
1349                     d->m_newEdgeDraft = nullptr;
1350                 }
1351                 d->m_newEdgeSource = nullptr;
1352                 d->m_editingMode = None;
1353             } else if (d->m_editingMode == AddNewEdge) {
1354                 d->m_editingMode = None;
1355             }
1356             for (GraphEdge *e : d->m_graph->edges()) {
1357                 if (e->isSelected()) {
1358                     e->setSelected(false);
1359                     e->canvasEdge()->update();
1360                 }
1361             }
1362             for (GraphNode *n : d->m_graph->nodes()) {
1363                 if (n->isSelected()) {
1364                     n->setSelected(false);
1365                     n->canvasElement()->update();
1366                 }
1367             }
1368             for (GraphSubgraph *s : d->m_graph->subgraphs()) {
1369                 if (s->isSelected()) {
1370                     s->setSelected(false);
1371                     s->canvasElement()->update();
1372                 }
1373             }
1374             emit selectionIs(QList<QString>(), QPoint());
1375         }
1376         d->m_pressPos = e->globalPos();
1377         d->m_pressScrollBarsPos = QPoint(horizontalScrollBar()->value(), verticalScrollBar()->value());
1378     }
1379     d->m_isMoving = true;
1380 }
1381 
1382 void DotGraphView::mouseMoveEvent(QMouseEvent *e)
1383 {
1384     Q_D(DotGraphView);
1385     QGraphicsView::mouseMoveEvent(e);
1386     //   qCDebug(KGRAPHVIEWERLIB_LOG) << scene()->selectedItems().size();
1387 
1388     if (d->m_editingMode == DrawNewEdge) {
1389         if (d->m_newEdgeDraft) {
1390             QPointF src = d->m_newEdgeDraft->line().p1();
1391             QPointF tgt = mapToScene(e->pos());
1392 
1393             //     qCDebug(KGRAPHVIEWERLIB_LOG) << "Setting new edge draft line to" << QLineF(src,tgt);
1394             d->m_newEdgeDraft->setLine(QLineF(src, tgt));
1395         }
1396     } else if (d->m_editingMode == SelectingElements) {
1397         //     qCDebug(KGRAPHVIEWERLIB_LOG) << "selecting";
1398     } else if (e->buttons().testFlag(Qt::LeftButton)) {
1399         //     qCDebug(KGRAPHVIEWERLIB_LOG) << (e->globalPos() - d->m_pressPos);
1400         QPoint diff = e->globalPos() - d->m_pressPos;
1401         horizontalScrollBar()->setValue(d->m_pressScrollBarsPos.x() - diff.x());
1402         verticalScrollBar()->setValue(d->m_pressScrollBarsPos.y() - diff.y());
1403     }
1404 }
1405 
1406 void DotGraphView::mouseReleaseEvent(QMouseEvent *e)
1407 {
1408     Q_D(DotGraphView);
1409     qCDebug(KGRAPHVIEWERLIB_LOG) << e << d->m_editingMode;
1410     //   qCDebug(KGRAPHVIEWERLIB_LOG) << "setDragMode(NoDrag)";
1411     //   setDragMode(NoDrag);
1412     if (d->m_editingMode == AddNewElement) {
1413         d->m_editingMode = None;
1414         unsetCursor();
1415     } else if (d->m_editingMode == SelectingElements) {
1416         QGraphicsView::mouseReleaseEvent(e);
1417         qCDebug(KGRAPHVIEWERLIB_LOG) << "Stopping selection" << scene() << d->m_canvas;
1418         QList<QGraphicsItem *> items = scene()->selectedItems();
1419         QList<QString> selection;
1420         for (QGraphicsItem *item : items) {
1421             CanvasElement *element = dynamic_cast<CanvasElement *>(item);
1422             element->element()->setSelected(true);
1423             if (element) {
1424                 selection.push_back(element->element()->id());
1425             }
1426         }
1427         d->m_editingMode = None;
1428         unsetCursor();
1429         setDragMode(NoDrag);
1430         if (!selection.isEmpty()) {
1431             update();
1432             emit selectionIs(selection, mapToGlobal(e->pos()));
1433         }
1434     } else {
1435         QGraphicsView::mouseReleaseEvent(e);
1436     }
1437     d->m_isMoving = false;
1438 }
1439 
1440 void DotGraphView::mouseDoubleClickEvent(QMouseEvent *e)
1441 {
1442     QGraphicsView::mouseDoubleClickEvent(e);
1443 }
1444 
1445 void DotGraphView::contextMenuEvent(QContextMenuEvent *e)
1446 {
1447     Q_D(DotGraphView);
1448     //   QList<QGraphicsItem *> l = scene()->collidingItems(scene()->itemAt(e->pos()));
1449 
1450     d->m_popup->exec(e->globalPos());
1451 }
1452 
1453 void DotGraphView::slotContextMenuEvent(const QString &id, const QPoint &p)
1454 {
1455     //   QList<QGraphicsItem *> l = scene()->collidingItems(scene()->itemAt(e->pos()));
1456 
1457     emit(contextMenuEvent(id, p));
1458 }
1459 
1460 void DotGraphView::slotElementHoverEnter(CanvasElement *element)
1461 {
1462     qCDebug(KGRAPHVIEWERLIB_LOG) << element->element()->id();
1463     //   QList<QGraphicsItem *> l = scene()->collidingItems(scene()->itemAt(e->pos()));
1464 
1465     emit(hoverEnter(element->element()->id()));
1466 }
1467 
1468 void DotGraphView::slotElementHoverLeave(CanvasElement *element)
1469 {
1470     qCDebug(KGRAPHVIEWERLIB_LOG) << element->element()->id();
1471     //   QList<QGraphicsItem *> l = scene()->collidingItems(scene()->itemAt(e->pos()));
1472 
1473     emit(hoverLeave(element->element()->id()));
1474 }
1475 
1476 void DotGraphView::slotElementHoverEnter(CanvasEdge *element)
1477 {
1478     qCDebug(KGRAPHVIEWERLIB_LOG) << element->edge()->id();
1479     //   QList<QGraphicsItem *> l = scene()->collidingItems(scene()->itemAt(e->pos()));
1480 
1481     emit(hoverEnter(element->edge()->id()));
1482 }
1483 
1484 void DotGraphView::slotElementHoverLeave(CanvasEdge *element)
1485 {
1486     qCDebug(KGRAPHVIEWERLIB_LOG) << element->edge()->id();
1487     //   QList<QGraphicsItem *> l = scene()->collidingItems(scene()->itemAt(e->pos()));
1488 
1489     emit(hoverLeave(element->edge()->id()));
1490 }
1491 
1492 void DotGraphView::setLayoutCommand(const QString &command)
1493 {
1494     Q_D(DotGraphView);
1495     d->m_graph->layoutCommand(command);
1496     reload();
1497 }
1498 
1499 KGraphViewerInterface::PannerPosition DotGraphView::zoomPos(const QString &s)
1500 {
1501     KGraphViewerInterface::PannerPosition res = DEFAULT_ZOOMPOS;
1502     if (s == QString("KGraphViewerInterface::TopLeft"))
1503         res = KGraphViewerInterface::TopLeft;
1504     if (s == QString("KGraphViewerInterface::TopRight"))
1505         res = KGraphViewerInterface::TopRight;
1506     if (s == QString("KGraphViewerInterface::BottomLeft"))
1507         res = KGraphViewerInterface::BottomLeft;
1508     if (s == QString("KGraphViewerInterface::BottomRight"))
1509         res = KGraphViewerInterface::BottomRight;
1510     if (s == QString("Automatic"))
1511         res = KGraphViewerInterface::Auto;
1512 
1513     return res;
1514 }
1515 
1516 void DotGraphView::setPannerEnabled(bool enabled)
1517 {
1518     Q_UNUSED(enabled);
1519     Q_D(DotGraphView);
1520     d->m_bevPopup->setEnabled(d->m_bevEnabledAction->isChecked());
1521     KGraphViewerPartSettings::setBirdsEyeViewEnabled(d->m_bevEnabledAction->isChecked());
1522     KGraphViewerPartSettings::self()->save();
1523     d->updateSizes();
1524 }
1525 
1526 void DotGraphView::viewBevActivated(int newZoomPos)
1527 {
1528     Q_D(DotGraphView);
1529     d->m_zoomPosition = (KGraphViewerInterface::PannerPosition)newZoomPos;
1530     d->updateSizes();
1531     emit(sigViewBevActivated(newZoomPos));
1532 }
1533 
1534 QString DotGraphView::zoomPosString(KGraphViewerInterface::PannerPosition p)
1535 {
1536     if (p == KGraphViewerInterface::TopRight)
1537         return QString("KGraphViewerInterface::TopRight");
1538     if (p == KGraphViewerInterface::BottomLeft)
1539         return QString("KGraphViewerInterface::BottomLeft");
1540     if (p == KGraphViewerInterface::BottomRight)
1541         return QString("KGraphViewerInterface::BottomRight");
1542     if (p == KGraphViewerInterface::Auto)
1543         return QString("Automatic");
1544 
1545     return QString("KGraphViewerInterface::TopLeft");
1546 }
1547 
1548 void DotGraphView::readViewConfig()
1549 {
1550     Q_D(DotGraphView);
1551     KConfigGroup g(KSharedConfig::openConfig(), "GraphViewLayout");
1552 
1553     QVariant dl = DEFAULT_DETAILLEVEL;
1554     d->m_detailLevel = g.readEntry("DetailLevel", dl).toInt();
1555     d->m_zoomPosition = zoomPos(g.readEntry("KGraphViewerInterface::PannerPosition", zoomPosString(DEFAULT_ZOOMPOS)));
1556     emit(sigViewBevActivated(d->m_zoomPosition));
1557 }
1558 
1559 void DotGraphView::saveViewConfig()
1560 {
1561     Q_D(DotGraphView);
1562     //   qCDebug(KGRAPHVIEWERLIB_LOG) << "Saving view config";
1563     KConfigGroup g(KSharedConfig::openConfig(), "GraphViewLayout");
1564 
1565     writeConfigEntry(&g, "DetailLevel", d->m_detailLevel, DEFAULT_DETAILLEVEL);
1566     writeConfigEntry(&g, "KGraphViewerInterface::PannerPosition", zoomPosString(d->m_zoomPosition), zoomPosString(DEFAULT_ZOOMPOS).toUtf8().data());
1567     g.sync();
1568 }
1569 
1570 void DotGraphView::pageSetup()
1571 {
1572     Q_D(DotGraphView);
1573     if (d->m_printCommand == nullptr) {
1574         d->m_printCommand = new KGVSimplePrintingCommand(this, 0);
1575     }
1576     d->m_printCommand->showPageSetup(d->m_graph->dotFileName());
1577     return;
1578 }
1579 
1580 void DotGraphView::print()
1581 {
1582     Q_D(DotGraphView);
1583     if (d->m_printCommand == nullptr) {
1584         d->m_printCommand = new KGVSimplePrintingCommand(this, 0);
1585     }
1586     d->m_printCommand->print(d->m_graph->dotFileName());
1587     return;
1588 }
1589 
1590 void DotGraphView::printPreview()
1591 {
1592     Q_D(DotGraphView);
1593     if (d->m_printCommand == nullptr) {
1594         d->m_printCommand = new KGVSimplePrintingCommand(this, 0);
1595     }
1596     d->m_printCommand->showPrintPreview(d->m_graph->dotFileName(), false);
1597     return;
1598 }
1599 
1600 bool DotGraphView::reload()
1601 {
1602     Q_D(DotGraphView);
1603     QString fileName = d->m_graph->dotFileName();
1604     if (d->m_graph->useLibrary())
1605         return loadLibrary(fileName);
1606     else
1607         return loadDot(fileName);
1608 }
1609 
1610 void DotGraphView::dirty(const QString &dotFileName)
1611 {
1612     Q_D(DotGraphView);
1613     //   std::cerr << "SLOT dirty for " << dotFileName << std::endl;
1614     if (dotFileName == d->m_graph->dotFileName()) {
1615         if (QMessageBox::question(this, i18n("Reload Confirmation"), i18n("The file %1 has been modified on disk.\nDo you want to reload it?", dotFileName)) == QMessageBox::Yes) {
1616             if (d->m_graph->useLibrary())
1617                 loadLibrary(dotFileName);
1618             else
1619                 loadDot(dotFileName);
1620         }
1621     }
1622 }
1623 
1624 KConfigGroup *DotGraphView::configGroup(KConfig *c, const QString &group, const QString &post)
1625 {
1626     QStringList gList = c->groupList();
1627     QString res = group;
1628     if (gList.contains(group + post))
1629         res += post;
1630     return new KConfigGroup(c, res);
1631 }
1632 
1633 void DotGraphView::writeConfigEntry(KConfigGroup *c, const char *pKey, const QString &value, const char *def)
1634 {
1635     if (!c)
1636         return;
1637     if ((value.isEmpty() && ((def == nullptr) || (*def == 0))) || (value == QString(def)))
1638         c->deleteEntry(pKey);
1639     else
1640         c->writeEntry(pKey, value);
1641 }
1642 
1643 void DotGraphView::writeConfigEntry(KConfigGroup *c, const char *pKey, int value, int def)
1644 {
1645     if (!c)
1646         return;
1647     if (value == def)
1648         c->deleteEntry(pKey);
1649     else
1650         c->writeEntry(pKey, value);
1651 }
1652 
1653 void DotGraphView::writeConfigEntry(KConfigGroup *c, const char *pKey, double value, double def)
1654 {
1655     if (!c)
1656         return;
1657     if (value == def)
1658         c->deleteEntry(pKey);
1659     else
1660         c->writeEntry(pKey, value);
1661 }
1662 
1663 void DotGraphView::writeConfigEntry(KConfigGroup *c, const char *pKey, bool value, bool def)
1664 {
1665     if (!c)
1666         return;
1667     if (value == def)
1668         c->deleteEntry(pKey);
1669     else
1670         c->writeEntry(pKey, value);
1671 }
1672 
1673 const QString &DotGraphView::dotFileName()
1674 {
1675     Q_D(DotGraphView);
1676     return d->m_graph->dotFileName();
1677 }
1678 
1679 void DotGraphView::hideToolsWindows()
1680 {
1681     Q_D(DotGraphView);
1682     if (d->m_printCommand) {
1683         d->m_printCommand->hidePageSetup();
1684         d->m_printCommand->hidePrintPreview();
1685     }
1686 }
1687 
1688 void DotGraphView::slotExportImage()
1689 {
1690     Q_D(DotGraphView);
1691     d->exportToImage();
1692 }
1693 
1694 void DotGraphView::slotLayoutSpecify()
1695 {
1696     {
1697         Q_D(DotGraphView);
1698         bool ok = false;
1699         QString currentLayoutCommand = d->m_graph->layoutCommand();
1700         QString layoutCommand = QInputDialog::getText(this,
1701                                                       i18n("Layout Command"),
1702                                                       i18n("Specify here the command that will be used to layout the graph.\n"
1703                                                            "The command MUST write its results on stdout in xdot format."),
1704                                                       QLineEdit::Normal,
1705                                                       currentLayoutCommand,
1706                                                       &ok);
1707         //       std::cerr << "Got layout command: " << layoutCommand << std::endl;
1708         if (ok && layoutCommand != currentLayoutCommand) {
1709             //         std::cerr << "Setting new layout command: " << layoutCommand << std::endl;
1710             if (!d->m_layoutAlgoSelectAction->setCurrentAction(layoutCommand, Qt::CaseInsensitive)) {
1711                 QAction *new_action = d->m_layoutAlgoSelectAction->addAction(layoutCommand);
1712                 d->m_layoutAlgoSelectAction->setCurrentAction(new_action);
1713                 slotSelectLayoutAlgo(layoutCommand);
1714             }
1715         }
1716     }
1717 }
1718 
1719 void DotGraphView::slotLayoutReset()
1720 {
1721     Q_D(DotGraphView);
1722     d->m_layoutAlgoSelectAction->setCurrentAction("Dot");
1723     slotSelectLayoutAlgo("Dot");
1724 }
1725 
1726 void DotGraphView::slotSelectLayoutAlgo(const QString &ttext)
1727 {
1728     QString text = ttext; //.mid(1);
1729     qCDebug(KGRAPHVIEWERLIB_LOG) << "DotGraphView::slotSelectLayoutAlgo '" << text << "'";
1730     if (text == "Dot") {
1731         setLayoutCommand("dot");
1732     } else if (text == "Neato") {
1733         setLayoutCommand("neato");
1734     } else if (text == "Twopi") {
1735         setLayoutCommand("twopi");
1736     } else if (text == "Fdp") {
1737         setLayoutCommand("fdp");
1738     } else if (text == "Circo") {
1739         setLayoutCommand("circo");
1740     } else {
1741         setLayoutCommand(text);
1742     }
1743 }
1744 
1745 void DotGraphView::slotSelectLayoutDot()
1746 {
1747     qCDebug(KGRAPHVIEWERLIB_LOG) << "DotGraphView::slotSelectLayoutDot";
1748     setLayoutCommand("dot -Txdot");
1749 }
1750 
1751 void DotGraphView::slotSelectLayoutNeato()
1752 {
1753     qCDebug(KGRAPHVIEWERLIB_LOG) << "DotGraphView::slotSelectLayoutNeato";
1754     setLayoutCommand("neato -Txdot");
1755 }
1756 
1757 void DotGraphView::slotSelectLayoutTwopi()
1758 {
1759     qCDebug(KGRAPHVIEWERLIB_LOG) << "DotGraphView::slotSelectLayoutTwopi";
1760     setLayoutCommand("twopi -Txdot");
1761 }
1762 
1763 void DotGraphView::slotSelectLayoutFdp()
1764 {
1765     qCDebug(KGRAPHVIEWERLIB_LOG) << "DotGraphView::slotSelectLayoutFdp";
1766     setLayoutCommand("fdp -Txdot");
1767 }
1768 
1769 void DotGraphView::slotSelectLayoutCirco()
1770 {
1771     qCDebug(KGRAPHVIEWERLIB_LOG) << "DotGraphView::slotSelectLayoutCirco";
1772     setLayoutCommand("circo -Txdot");
1773 }
1774 
1775 void DotGraphView::slotBevToggled()
1776 {
1777     Q_D(DotGraphView);
1778     qCDebug(KGRAPHVIEWERLIB_LOG) << "DotGraphView::slotBevToggled";
1779     qCDebug(KGRAPHVIEWERLIB_LOG) << "    d->m_bevEnabledAction is checked ? " << d->m_bevEnabledAction->isChecked();
1780     setPannerEnabled(d->m_bevEnabledAction->isChecked());
1781 }
1782 
1783 void DotGraphView::slotBevTopLeft()
1784 {
1785     viewBevActivated(KGraphViewerInterface::TopLeft);
1786 }
1787 
1788 void DotGraphView::slotBevTopRight()
1789 {
1790     viewBevActivated(KGraphViewerInterface::TopRight);
1791 }
1792 
1793 void DotGraphView::slotBevBottomLeft()
1794 {
1795     viewBevActivated(KGraphViewerInterface::BottomLeft);
1796 }
1797 
1798 void DotGraphView::slotBevBottomRight()
1799 {
1800     viewBevActivated(KGraphViewerInterface::BottomRight);
1801 }
1802 
1803 void DotGraphView::slotBevAutomatic()
1804 {
1805     viewBevActivated(KGraphViewerInterface::Auto);
1806 }
1807 
1808 void DotGraphView::slotUpdate()
1809 {
1810     Q_D(DotGraphView);
1811     d->m_graph->update();
1812     d->m_layoutAlgoSelectAction->setCurrentAction(d->m_graph->layoutCommand(), Qt::CaseInsensitive);
1813 }
1814 
1815 void DotGraphView::prepareAddNewElement(QMap<QString, QString> attribs)
1816 {
1817     Q_D(DotGraphView);
1818     d->m_editingMode = AddNewElement;
1819     d->m_newElementAttributes = attribs;
1820     unsetCursor();
1821     setCursor(QCursor(d->m_defaultNewElementPixmap));
1822 }
1823 
1824 void DotGraphView::prepareAddNewEdge(QMap<QString, QString> attribs)
1825 {
1826     Q_D(DotGraphView);
1827     qCDebug(KGRAPHVIEWERLIB_LOG) << attribs;
1828     bool anySelected = false;
1829     for (GraphEdge *edge : d->m_graph->edges()) {
1830         if (edge->isSelected()) {
1831             anySelected = true;
1832             QMap<QString, QString>::const_iterator it = attribs.constBegin();
1833             for (; it != attribs.constEnd(); it++) {
1834                 edge->attributes()[it.key()] = it.value();
1835             }
1836         }
1837     }
1838     if (anySelected) {
1839         return;
1840     }
1841     d->m_editingMode = AddNewEdge;
1842     d->m_newElementAttributes = attribs;
1843     unsetCursor();
1844     QBitmap bm(QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kgraphviewerpart/pics/kgraphviewer-newedge.png"));
1845     setCursor(QCursor(bm, bm, 32, 16));
1846 }
1847 
1848 void DotGraphView::prepareSelectElements()
1849 {
1850     Q_D(DotGraphView);
1851     d->m_editingMode = SelectingElements;
1852     setCursor(Qt::CrossCursor);
1853     setDragMode(RubberBandDrag);
1854 }
1855 
1856 void DotGraphView::createNewEdgeDraftFrom(CanvasElement *node)
1857 {
1858     Q_D(DotGraphView);
1859     qCDebug(KGRAPHVIEWERLIB_LOG) << node->element()->id();
1860     d->m_editingMode = DrawNewEdge;
1861     unsetCursor();
1862     d->m_newEdgeSource = node;
1863 
1864     if (d->m_newEdgeDraft) {
1865         qCDebug(KGRAPHVIEWERLIB_LOG) << "removing new edge draft";
1866         d->m_newEdgeDraft->hide();
1867         scene()->removeItem(d->m_newEdgeDraft);
1868         delete d->m_newEdgeDraft;
1869         d->m_newEdgeDraft = nullptr;
1870     }
1871     d->m_newEdgeDraft = new QGraphicsLineItem(QLineF(node->boundingRect().center() + node->pos(), node->boundingRect().center() + node->pos() + QPointF(10, 10)));
1872     scene()->addItem(d->m_newEdgeDraft);
1873     d->m_newEdgeDraft->setZValue(1000);
1874     d->m_newEdgeDraft->show();
1875     qCDebug(KGRAPHVIEWERLIB_LOG) << d->m_newEdgeDraft->line();
1876 }
1877 
1878 void DotGraphView::finishNewEdgeTo(CanvasElement *node)
1879 {
1880     Q_D(DotGraphView);
1881     qCDebug(KGRAPHVIEWERLIB_LOG) << node->element()->id();
1882     d->m_editingMode = None;
1883     unsetCursor();
1884 
1885     if (d->m_newEdgeDraft) {
1886         qCDebug(KGRAPHVIEWERLIB_LOG) << "removing new edge draft";
1887         d->m_newEdgeDraft->hide();
1888         scene()->removeItem(d->m_newEdgeDraft);
1889         delete d->m_newEdgeDraft;
1890         d->m_newEdgeDraft = nullptr;
1891     }
1892 
1893     emit newEdgeFinished(d->m_newEdgeSource->element()->id(), node->element()->id(), d->m_newElementAttributes);
1894 
1895     d->m_newEdgeSource = nullptr;
1896 }
1897 
1898 // void DotGraphView::slotFinishNewEdge(
1899 //       const QString& srcId,
1900 //       const QString& tgtId,
1901 //       const QMap<QString, QString> newElementAttributes)
1902 // {
1903 //   qCDebug(KGRAPHVIEWERLIB_LOG) ;
1904 //
1905 //   GraphEdge* gedge  = new GraphEdge();
1906 //   gedge->setFromNode(d->m_graph->nodes()[srcId]);
1907 //   gedge->setToNode(d->m_graph->nodes()[tgtId]);
1908 //   gedge->attributes() = newElementAttributes;
1909 //   foreach (const QString &attrib, newElementAttributes.keys())
1910 //   {
1911 //     if (attrib == "z")
1912 //     {
1913 //       bool ok;
1914 //       gedge->setZ(newElementAttributes[attrib].toDouble(&ok));
1915 //     }
1916 //   }
1917 //   gedge->setId(srcId+tgtId+QString::number(d->m_graph->edges().size()));
1918 //   d->m_graph->edges().insert(gedge->id(), gedge);
1919 //
1920 //   double scale = detailAdjustedScale();
1921 //
1922 //   qreal gh = d->m_graph->height();
1923 //   CanvasEdge* cedge = new CanvasEdge(this, gedge, scale, scale, d->m_xMargin,
1924 //         d->m_yMargin, gh, d->m_graph->wdhcf(), d->m_graph->hdvcf());
1925 //
1926 //   gedge->setCanvasEdge(cedge);
1927 // //     std::cerr << "setting z = " << gedge->z() << std::endl;
1928 //   cedge->setZValue(gedge->z());
1929 //   cedge->show();
1930 //   scene()->addItem(cedge);
1931 //
1932 //   emit newEdgeAdded(gedge->fromNode()->id(),gedge->toNode()->id());
1933 // }
1934 
1935 void DotGraphView::setReadOnly()
1936 {
1937     Q_D(DotGraphView);
1938     d->m_readWrite = false;
1939     if (d->m_graph) {
1940         d->m_graph->setReadOnly();
1941     }
1942 }
1943 
1944 void DotGraphView::setReadWrite()
1945 {
1946     Q_D(DotGraphView);
1947     d->m_readWrite = true;
1948     if (d->m_graph) {
1949         d->m_graph->setReadWrite();
1950     }
1951 }
1952 
1953 void DotGraphView::slotEdgeSelected(CanvasEdge *edge, Qt::KeyboardModifiers modifiers)
1954 {
1955     Q_D(DotGraphView);
1956     qCDebug(KGRAPHVIEWERLIB_LOG) << edge->edge()->id();
1957     QList<QString> selection;
1958     selection.push_back(edge->edge()->id());
1959     if (!modifiers.testFlag(Qt::ControlModifier)) {
1960         for (GraphEdge *e : d->m_graph->edges()) {
1961             if (e->canvasEdge() != edge) {
1962                 e->setSelected(false);
1963                 e->canvasEdge()->update();
1964             }
1965         }
1966         for (GraphNode *n : d->m_graph->nodes()) {
1967             n->setSelected(false);
1968             n->canvasNode()->update();
1969         }
1970         for (GraphSubgraph *s : d->m_graph->subgraphs()) {
1971             s->setElementSelected(nullptr, false, true);
1972         }
1973     } else {
1974         for (GraphEdge *e : d->m_graph->edges()) {
1975             if (e->canvasEdge() != edge) {
1976                 if (e->isSelected()) {
1977                     selection.push_back(e->id());
1978                 }
1979             }
1980         }
1981         for (GraphNode *n : d->m_graph->nodes()) {
1982             if (n->isSelected()) {
1983                 selection.push_back(n->id());
1984             }
1985         }
1986         for (GraphSubgraph *s : d->m_graph->subgraphs()) {
1987             if (s->isSelected()) {
1988                 selection.push_back(s->id());
1989             }
1990         }
1991     }
1992     emit selectionIs(selection, QPoint());
1993 }
1994 
1995 void DotGraphView::slotElementSelected(CanvasElement *element, Qt::KeyboardModifiers modifiers)
1996 {
1997     Q_D(DotGraphView);
1998     QList<QString> selection;
1999     selection.push_back(element->element()->id());
2000     if (!modifiers.testFlag(Qt::ControlModifier)) {
2001         for (GraphEdge *e : d->m_graph->edges()) {
2002             if (e->isSelected()) {
2003                 e->setSelected(false);
2004                 e->canvasEdge()->update();
2005             }
2006         }
2007         for (GraphNode *e : d->m_graph->nodes()) {
2008             if (e->canvasElement() != element) {
2009                 if (e->isSelected()) {
2010                     e->setSelected(false);
2011                     e->canvasElement()->update();
2012                 }
2013             }
2014         }
2015         for (GraphSubgraph *s : d->m_graph->subgraphs()) {
2016             s->setElementSelected(element->element(), true, true);
2017         }
2018     } else {
2019         for (GraphEdge *e : d->m_graph->edges()) {
2020             if (e->isSelected()) {
2021                 selection.push_back(e->id());
2022             }
2023         }
2024         for (GraphNode *n : d->m_graph->nodes()) {
2025             if (n->isSelected()) {
2026                 selection.push_back(n->id());
2027             }
2028         }
2029         for (GraphSubgraph *s : d->m_graph->subgraphs()) {
2030             s->retrieveSelectedElementsIds(selection);
2031         }
2032     }
2033     emit selectionIs(selection, QPoint());
2034 }
2035 
2036 void DotGraphView::removeSelectedEdges()
2037 {
2038     Q_D(DotGraphView);
2039     for (GraphEdge *e : d->m_graph->edges()) {
2040         if (e->isSelected()) {
2041             qCDebug(KGRAPHVIEWERLIB_LOG) << "emiting removeEdge " << e->id();
2042             d->m_graph->removeEdge(e->id());
2043             emit removeEdge(e->id());
2044         }
2045     }
2046 }
2047 
2048 void DotGraphView::removeSelectedNodes()
2049 {
2050     Q_D(DotGraphView);
2051     qCDebug(KGRAPHVIEWERLIB_LOG);
2052     for (GraphNode *e : d->m_graph->nodes()) {
2053         if (e->isSelected()) {
2054             qCDebug(KGRAPHVIEWERLIB_LOG) << "emiting removeElement " << e->id();
2055             d->m_graph->removeElement(e->id());
2056             emit removeElement(e->id());
2057         }
2058     }
2059 }
2060 
2061 void DotGraphView::removeSelectedSubgraphs()
2062 {
2063     Q_D(DotGraphView);
2064     for (GraphSubgraph *e : d->m_graph->subgraphs()) {
2065         if (e->isSelected()) {
2066             qCDebug(KGRAPHVIEWERLIB_LOG) << "emiting removeElement " << e->id();
2067             d->m_graph->removeElement(e->id());
2068             emit removeElement(e->id());
2069         }
2070     }
2071 }
2072 
2073 void DotGraphView::removeSelectedElements()
2074 {
2075     removeSelectedNodes();
2076     removeSelectedEdges();
2077     removeSelectedSubgraphs();
2078 }
2079 
2080 void DotGraphView::timerEvent(QTimerEvent *event)
2081 {
2082     Q_D(DotGraphView);
2083     qCDebug(KGRAPHVIEWERLIB_LOG) << event->timerId();
2084     qreal vpercent = verticalScrollBar()->value() * 1.0 / 100;
2085     qreal hpercent = horizontalScrollBar()->value() * 1.0 / 100;
2086     if (d->m_scrollDirection == Left) {
2087         horizontalScrollBar()->setValue(horizontalScrollBar()->value() - (5 * hpercent));
2088     } else if (d->m_scrollDirection == Right) {
2089         horizontalScrollBar()->setValue(horizontalScrollBar()->value() + (5 * hpercent));
2090     } else if (d->m_scrollDirection == Top) {
2091         verticalScrollBar()->setValue(verticalScrollBar()->value() - (5 * vpercent));
2092     } else if (d->m_scrollDirection == Bottom) {
2093         verticalScrollBar()->setValue(verticalScrollBar()->value() + (5 * vpercent));
2094     }
2095 }
2096 
2097 void DotGraphView::leaveEvent(QEvent * /*event*/)
2098 {
2099     Q_D(DotGraphView);
2100     qCDebug(KGRAPHVIEWERLIB_LOG) << mapFromGlobal(QCursor::pos());
2101     if (d->m_editingMode == DrawNewEdge) {
2102         d->m_leavedTimer = startTimer(10);
2103         if (mapFromGlobal(QCursor::pos()).x() <= 0) {
2104             d->m_scrollDirection = Left;
2105         } else if (mapFromGlobal(QCursor::pos()).y() <= 0) {
2106             d->m_scrollDirection = Top;
2107         } else if (mapFromGlobal(QCursor::pos()).x() >= width()) {
2108             d->m_scrollDirection = Right;
2109         } else if (mapFromGlobal(QCursor::pos()).y() >= height()) {
2110             d->m_scrollDirection = Bottom;
2111         }
2112     }
2113 }
2114 
2115 #if QT_VERSION > QT_VERSION_CHECK(6, 0, 0)
2116 void DotGraphView::enterEvent(QEnterEvent * /*event*/)
2117 #else
2118 void DotGraphView::enterEvent(QEvent * /*event*/)
2119 #endif
2120 {
2121     Q_D(DotGraphView);
2122     qCDebug(KGRAPHVIEWERLIB_LOG);
2123     if (d->m_leavedTimer != std::numeric_limits<int>::max()) {
2124         killTimer(d->m_leavedTimer);
2125         d->m_leavedTimer = std::numeric_limits<int>::max();
2126     }
2127 }
2128 
2129 void DotGraphView::slotAGraphReadFinished()
2130 {
2131     Q_D(DotGraphView);
2132     QString layoutCommand = (d->m_graph ? d->m_graph->layoutCommand() : QString());
2133     if (layoutCommand.isEmpty()) {
2134         if (!d->m_loadThread.dotFileName().isEmpty())
2135             layoutCommand = d->m_graph->chooseLayoutProgramForFile(d->m_loadThread.dotFileName());
2136         else
2137             layoutCommand = "dot";
2138     }
2139     d->m_layoutThread.layoutGraph(d->m_loadThread.g(), layoutCommand);
2140     d->m_loadThread.processed_finished();
2141 }
2142 
2143 void DotGraphView::slotAGraphLayoutFinished()
2144 {
2145     Q_D(DotGraphView);
2146     graph_t *g = d->m_layoutThread.g();
2147     bool result = loadLibrary(g, d->m_layoutThread.layoutCommand());
2148     if (result)
2149         // file name can be taken from m_loadThread both in sync and async loading cases
2150         // see comment in loadLibrarySync()
2151         d->m_graph->dotFileName(d->m_loadThread.dotFileName());
2152     else {
2153         Q_ASSERT(!d->m_canvas);
2154         QGraphicsScene *newCanvas = new QGraphicsScene();
2155         QGraphicsSimpleTextItem *loadingLabel = newCanvas->addSimpleText(i18n("Failed to open %1", d->m_loadThread.dotFileName()));
2156         loadingLabel->setZValue(100);
2157         centerOn(loadingLabel);
2158         setScene(newCanvas);
2159         d->m_canvas = newCanvas;
2160     }
2161 
2162     if (g) {
2163         gvFreeLayout(d->m_layoutThread.gvc(), g);
2164         agclose(g);
2165     }
2166     d->m_layoutThread.processed_finished();
2167 }
2168 
2169 void DotGraphView::slotSelectNode(const QString &nodeName)
2170 {
2171     qCDebug(KGRAPHVIEWERLIB_LOG) << nodeName;
2172     GraphNode *node = dynamic_cast<GraphNode *>(graph()->elementNamed(nodeName));
2173     if (node == nullptr)
2174         return;
2175     node->setSelected(true);
2176     if (node->canvasNode()) {
2177         node->canvasNode()->modelChanged();
2178         slotElementSelected(node->canvasNode(), Qt::NoModifier);
2179     }
2180 }
2181 
2182 void DotGraphView::centerOnNode(const QString &nodeId)
2183 {
2184     GraphNode *node = dynamic_cast<GraphNode *>(graph()->elementNamed(nodeId));
2185     if (node == nullptr)
2186         return;
2187     if (node->canvasNode()) {
2188         centerOn(node->canvasNode());
2189     }
2190 }
2191 
2192 }
2193 
2194 #include "moc_dotgraphview.cpp"