/* TikZiT - a GUI diagram editor for TikZ Copyright (C) 2018 Aleks Kissinger This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "graph.h" #include "util.h" #include #include #include #include #include Graph::Graph(QObject *parent) : QObject(parent) { _data = new GraphElementData(this); _bbox = QRectF(0,0,0,0); } Graph::~Graph() { } // add a node. The graph claims ownership. void Graph::addNode(Node *n) { n->setParent(this); _nodes << n; } void Graph::addNode(Node *n, int index) { n->setParent(this); _nodes.insert(index, n); } void Graph::removeNode(Node *n) { // the node itself is not deleted, as it may still be referenced in an undo command. It will // be deleted when graph is, via QObject memory management. _nodes.removeOne(n); } void Graph::addEdge(Edge *e) { e->setParent(this); _edges << e; } void Graph::addEdge(Edge *e, int index) { e->setParent(this); _edges.insert(index, e); } void Graph::removeEdge(Edge *e) { // the edge itself is not deleted, as it may still be referenced in an undo command. It will // be deleted when graph is, via QObject memory management. _edges.removeOne(e); } int Graph::maxIntName() { int max = -1; int i; bool ok; foreach (Node *n, _nodes) { i = n->name().toInt(&ok); if (ok && i > max) max = i; } return max; } void Graph::reorderNodes(const QVector &newOrder) { _nodes = newOrder; } void Graph::reorderEdges(const QVector &newOrder) { _edges = newOrder; } QRectF Graph::realBbox() { //float maxX = 0.0f; QRectF rect = bbox(); foreach (Node *n, _nodes) { rect = rect.united(QRectF(n->point().x()-0.5f, n->point().y()-0.5f, 1.0f, 1.0f)); } return rect; } QRectF Graph::boundsForNodes(QSetnds) { QPointF p; QPointF tl; QPointF br; bool hasPoints = false; foreach (Node *n, nds) { p = n->point(); if (!hasPoints) { hasPoints = true; tl = p; br = p; } else { if (p.x() < tl.x()) tl.setX(p.x()); if (p.y() > tl.y()) tl.setY(p.y()); if (p.x() > br.x()) br.setX(p.x()); if (p.y() < br.y()) br.setY(p.y()); } } QRectF rect(tl, br); return rect; } QString Graph::freshNodeName() { return QString::number(maxIntName() + 1); } void Graph::renameApart(Graph *graph) { int i = graph->maxIntName() + 1; foreach (Node *n, _nodes) { n->setName(QString::number(i)); i++; } } GraphElementData *Graph::data() const { return _data; } void Graph::setData(GraphElementData *data) { delete _data; _data = data; } const QVector &Graph::nodes() { return _nodes; } const QVector &Graph::edges() { return _edges; } QRectF Graph::bbox() const { return _bbox; } bool Graph::hasBbox() { return !(_bbox == QRectF(0,0,0,0)); } void Graph::clearBbox() { _bbox = QRectF(0,0,0,0); } QString Graph::tikz() { QString str; QTextStream code(&str); int line = 0; code << "\\begin{tikzpicture}" << _data->tikz() << "\n"; line++; if (hasBbox()) { code << "\t\\path [use as bounding box] (" << _bbox.topLeft().x() << "," << _bbox.topLeft().y() << ") rectangle (" << _bbox.bottomRight().x() << "," << _bbox.bottomRight().y() << ");\n"; line++; } if (!_nodes.isEmpty()) { code << "\t\\begin{pgfonlayer}{nodelayer}\n"; line++; } Node *n; foreach (n, _nodes) { n->setTikzLine(line); code << "\t\t\\node "; if (!n->data()->isEmpty()) code << n->data()->tikz() << " "; code << "(" << n->name() << ") at (" << floatToString(n->point().x()) << ", " << floatToString(n->point().y()) << ") {" << n->label() << "};\n"; line++; } if (!_nodes.isEmpty()) { code << "\t\\end{pgfonlayer}\n"; line++; } if (!_edges.isEmpty()) { code << "\t\\begin{pgfonlayer}{edgelayer}\n"; line++; } Edge *e; foreach (e, _edges) { e->setTikzLine(line); e->updateData(); code << "\t\t\\draw "; if (!e->data()->isEmpty()) code << e->data()->tikz() << " "; code << "(" << e->source()->name(); if (e->sourceAnchor() != "") code << "." << e->sourceAnchor(); code << ") to "; if (e->hasEdgeNode()) { code << "node "; if (!e->edgeNode()->data()->isEmpty()) code << e->edgeNode()->data()->tikz() << " "; code << "{" << e->edgeNode()->label() << "} "; } if (e->source() == e->target()) { code << "()"; } else { code << "(" << e->target()->name(); if (e->targetAnchor() != "") code << "." << e->targetAnchor(); code << ")"; } code << ";\n"; line++; } if (!_edges.isEmpty()) { code << "\t\\end{pgfonlayer}\n"; line++; } code << "\\end{tikzpicture}\n"; line++; code.flush(); return str; } Graph *Graph::copyOfSubgraphWithNodes(QSet nds) { Graph *g = new Graph(); g->setData(_data->copy()); QMap nodeTable; foreach (Node *n, nodes()) { if (nds.contains(n)) { Node *n1 = n->copy(); nodeTable.insert(n, n1); g->addNode(n1); } } foreach (Edge *e, edges()) { if (nds.contains(e->source()) && nds.contains(e->target())) { g->addEdge(e->copy(&nodeTable)); } } return g; } void Graph::insertGraph(Graph *graph) { QMap nodeTable; foreach (Node *n, graph->nodes()) addNode(n); foreach (Edge *e, graph->edges()) addEdge(e); } void Graph::reflectNodes(QSet nds, bool horizontal) { QRectF bds = boundsForNodes(nds); float ctr; if (horizontal) ctr = bds.center().x(); else ctr = bds.center().y(); QPointF p; foreach(Node *n, nds) { p = n->point(); if (horizontal) p.setX(2 * ctr - p.x()); else p.setY(2 * ctr - p.y()); n->setPoint(p); } foreach (Edge *e, _edges) { if (nds.contains(e->source()) && nds.contains(e->target())) { if (!e->basicBendMode()) { if (horizontal) { if (e->inAngle() < 0) e->setInAngle(-180 - e->inAngle()); else e->setInAngle(180 - e->inAngle()); if (e->outAngle() < 0) e->setOutAngle(-180 - e->outAngle()); else e->setOutAngle(180 - e->outAngle()); } else { e->setInAngle(-e->inAngle()); e->setOutAngle(-e->outAngle()); } } else { e->setBend(-e->bend()); } } } } void Graph::rotateNodes(QSet nds, bool clockwise) { //QRectF bds = boundsForNodes(nds); // QPointF ctr = bds.center(); // ctr.setX((float)floor(ctr.x() * 4.0f) / 4.0f); // ctr.setY((float)floor(ctr.y() * 4.0f) / 4.0f); float sign = (clockwise) ? 1.0f : -1.0f; QPointF p; // float dx, dy; foreach(Node *n, nds) { p = n->point(); // dx = p.x() - ctr.x(); // dy = p.y() - ctr.y(); n->setPoint(QPointF(sign * p.y(), -sign * p.x())); } int newIn, newOut; foreach (Edge *e, _edges) { if (nds.contains(e->source()) && nds.contains(e->target())) { // update angles if necessary. Note that "basic" bends are computed based // on node position, so they don't need to be updated. if (!e->basicBendMode()) { newIn = e->inAngle() - sign * 90; newOut = e->outAngle() - sign * 90; // normalise the angle to be within (-180,180] if (newIn > 180) newIn -= 360; else if (newIn <= -180) newIn += 360; if (newOut > 180) newOut -= 360; else if (newOut <= -180) newOut += 360; e->setInAngle(newIn); e->setOutAngle(newOut); } } } } void Graph::setBbox(const QRectF &bbox) { _bbox = bbox; }