// Copyright (C) 2008 Lukas Lalinsky // // 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 2 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, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. #include #include "hub.h" #include "diagramdocument.h" #include "diagramobject.h" #include "line.h" #include "items/database/databasetable.h" #include "items/database/databaserelationship.h" #include "items/database/column.h" #include "commands.h" #include #include #include #include #include #include #include #include #include #include "diagram/linelayouter.h" using namespace std; class DiagramDocument::DiagramDocumentPrivate { public: DiagramDocumentPrivate() : gridSize(10), gridVisible(true), gridPen(QColor(185, 185, 185), 0), printing(false), notation(Relational), updateTimerIsRunning(false) {} int gridSize; bool gridVisible; QPen gridPen; bool printing; Notation notation; bool updateTimerIsRunning; QTimer *updateTimer; QList itemsToShow; QList itemsToRemove; QSet linesToUpdate; QSet objectsToUpdate; QMap counters; Diagram::LineLayouter *lineLayouter; }; static const int UPDATE_DELAY = 2; DiagramDocument::DiagramDocument(QObject *parent) : QGraphicsScene(parent), d(new DiagramDocumentPrivate), m_mode(DiagramDocument::Select), m_line(NULL) { m_undoStack = new QUndoStack(this); d->updateTimer = new QTimer(); d->updateTimer->setSingleShot(true); d->updateTimer->setInterval(UPDATE_DELAY); connect(d->updateTimer, SIGNAL(timeout()), this, SLOT(_updateLines())); } DiagramDocument::Notation DiagramDocument::notation() const { return d->notation; } void DiagramDocument::setNotation(Notation notation) { if (d->notation != notation) { d->notation = notation; // FIXME foreach (Line *line, itemsByType()) { updateLineLayout(line); } update(); } } int DiagramDocument::gridSize() const { return d->gridSize; } void DiagramDocument::setGridSize(int size) { d->gridSize = size; update(); } bool DiagramDocument::isGridVisible() const { return d->gridVisible; } void DiagramDocument::setGridVisible(bool visible) { d->gridVisible = visible; update(); } QColor DiagramDocument::gridColor() const { return d->gridPen.color(); } void DiagramDocument::setGridColor(const QColor &color) { d->gridPen.setColor(color); update(); } bool DiagramDocument::isPrinting() const { return d->printing; } void DiagramDocument::setPrinting(bool printing) { d->printing = printing; } DiagramDocument::Mode DiagramDocument::mode() { return m_mode; } void DiagramDocument::setMode(Mode mode) { m_mode = mode; emit modeChanged(mode); } void DiagramDocument::updatePositions(DiagramObject *object, bool force) { d->objectsToUpdate.insert(object); if (force) { d->updateTimer->start(0); d->updateTimer->setInterval(UPDATE_DELAY); } else { d->updateTimer->start(); } } void DiagramDocument::updateLineLayout(Line *line) { d->linesToUpdate.insert(line); } void DiagramDocument::_updateLines() { d->updateTimerIsRunning = true; QSet objectsToUpdate(d->objectsToUpdate); d->objectsToUpdate.clear(); updateLines(objectsToUpdate); d->updateTimerIsRunning = false; } void DiagramDocument::updateLines(QSet objectsToUpdate) { QSet hubsToUpdate; foreach (DiagramObject *obj, objectsToUpdate) { hubsToUpdate.insert(obj->hub()); foreach (Connector *conn, obj->hub()->connectors()) { hubsToUpdate.insert(conn->otherEnd()->hub()); } } foreach (Hub *hub, hubsToUpdate) { hub->update(); } foreach (Line *line, d->linesToUpdate) { line->updateLayout(); line->update(); } d->linesToUpdate.clear(); foreach (DiagramItem *item, d->itemsToShow) { item->show(); } d->itemsToShow.clear(); foreach (DiagramItem *item, d->itemsToRemove) { removeItem(item); } d->itemsToRemove.clear(); } void DiagramDocument::addItemLater(DiagramItem *item) { Q_ASSERT(d->updateTimerIsRunning == false); item->hide(); addItem(item); d->itemsToShow.append(item); d->updateTimer->start(0); } void DiagramDocument::removeItemLater(DiagramItem *item) { Q_ASSERT(d->updateTimerIsRunning == false); item->hide(); d->itemsToRemove.append(item); d->updateTimer->start(0); } template QList DiagramDocument::itemsByType() { QList result; foreach(QGraphicsItem *item, items()) { T *typedItem = dynamic_cast(item); if (typedItem) { result.append(typedItem); } /* if (item->type() == T::Type) { result.append(static_cast(item)); }*/ } return result; } QList DiagramDocument::selectedItems() { QList result; foreach(QGraphicsItem *item, QGraphicsScene::selectedItems()) { DiagramItem *typedItem = dynamic_cast(item); if (typedItem) { result.append(typedItem); } } return result; } void DiagramDocument::itemMoved(DiagramItem *item) { if (m_trackingMoves) { if (!m_movedItems.contains(item)) { m_movedItems[item] = item->pos(); } } } void DiagramDocument::itemHasMoved(DiagramItem *item) { DiagramObject *obj = static_cast(item); if (obj) { updatePositions(obj); } } void DiagramDocument::mousePressEvent(QGraphicsSceneMouseEvent *event) { m_movedItems.clear(); m_trackingMoves = true; if (m_mode == AddTable && event->button() == Qt::LeftButton) { DatabaseTable *table = new DatabaseTable(); table->createId(); table->setInitialName(1 + d->counters[table->typeName()]++); table->setPos(event->scenePos()); undoStack()->push(new AddObjectCommand(this, table)); clearSelection(); table->setSelected(true); setMode(Select); event->accept(); return; } if (m_mode == AddRelation && event->button() == Qt::LeftButton) { m_line = new QGraphicsLineItem(); m_line->setLine(QLineF(event->scenePos(), event->scenePos())); QPen pen; pen.setStyle(Qt::CustomDashLine); pen.setDashOffset(7); pen.setDashPattern(QVector() << 3 << 3); m_line->setPen(pen); m_line->setZValue(1000.0); addItem(m_line); event->accept(); return; } QGraphicsScene::mousePressEvent(event); } void DiagramDocument::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { if (m_line) { m_line->setLine(QLineF(m_line->line().p1(), event->scenePos())); event->accept(); return; } QGraphicsScene::mouseMoveEvent(event); } void DiagramDocument::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { if (m_line) { removeItem(m_line); DatabaseTable *source = qgraphicsitem_cast(itemAt(m_line->line().p1())); DatabaseTable *target = qgraphicsitem_cast(itemAt(m_line->line().p2())); if (source && target && source != target) { Line *line = new DatabaseRelationship(); line->createId(); line->connector(0)->setHub(source->hub()); line->connector(1)->setHub(target->hub()); undoStack()->push(new AddLineCommand(this, line)); } delete m_line; m_line = NULL; setMode(Select); event->accept(); return; } QGraphicsScene::mouseReleaseEvent(event); m_trackingMoves = false; QMapIterator i(m_movedItems); while (i.hasNext()) { i.next(); undoStack()->push(new MoveItemCommand(i.key(), i.value(), i.key()->pos())); } } DatabaseTable * DiagramDocument::selectedTable() { QList items = selectedItems(); if (items.size() != 1) return NULL; return qgraphicsitem_cast(items[0]); } void DiagramDocument::deleteSelectedItems() { foreach (DiagramItem *item, selectedItems()) { DiagramObject *obj = qobject_cast(item); if (obj) { undoStack()->push(new RemoveObjectCommand(this, obj)); } else { Line *line = qobject_cast(item); if (line) { undoStack()->push(new RemoveLineCommand(this, line)); } } } } QList DiagramDocument::findConnections(DiagramObject *object) { QList result; foreach (Line *connection, itemsByType()) if (connection->connector(0)->connectedObject() == object || connection->connector(1)->connectedObject() == object) result.append(connection); return result; } #include "domutils.h" void DiagramDocument::save(const QString &fileName) { QDomDocument doc; QDomProcessingInstruction xml = doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""); doc.appendChild(xml); QDomElement root = doc.createElement("diagram"); root.setAttribute("xmlns", "http://oxygene.sk/ns/diagram/1/"); doc.appendChild(root); appendEnumElement(doc, root, "notation", d->notation, this, "Notation"); QDomElement itemList = doc.createElement("item-list"); root.appendChild(itemList); foreach (DiagramObject *item, itemsByType()) { QDomElement element = doc.createElement("item"); itemList.appendChild(element); item->saveToXml(doc, element); } foreach (Line *item, itemsByType()) { QDomElement element = doc.createElement("item"); itemList.appendChild(element); item->saveToXml(doc, element); } QFile file(fileName); if (file.open(QIODevice::WriteOnly)) { QTextStream stream(&file); doc.save(stream, 2); file.close(); } setFileName(fileName); } #include "diagramitemfactory.h" bool DiagramDocument::load(const QString &fileName) { QDomDocument doc; QFile file(fileName); if (file.open(QIODevice::ReadOnly)) { if (!doc.setContent(&file)) { return false; } file.close(); } else { return false; } setFileName(fileName); QDomElement root = doc.firstChildElement("diagram"); d->notation = readEnumElement(root, "notation", Relational, this, "Notation"); QDomElement itemListElement = root.firstChildElement("item-list"); QDomElement itemElement = itemListElement.firstChildElement("item"); while (!itemElement.isNull()) { QString itemTypeName = itemElement.attribute("type"); DiagramItem *item = DiagramItemFactory::create(itemTypeName); if (item == NULL) { qWarning() << "Unknown item type:" << itemTypeName; } else { item->loadFromXml(itemElement, this); addItem(item); } itemElement = itemElement.nextSiblingElement("item"); } return true; } DiagramItem * DiagramDocument::itemById(const QUuid &id) { foreach (DiagramItem *item, itemsByType()) if (item->id() == id) return item; return 0; } void DiagramDocument::print(QPrinter *printer, const QRectF &target, const QRectF &source) { QPainter painter(printer); print(&painter, target, source); } void DiagramDocument::print(QPainter *painter, const QRectF &target, const QRectF &source) { setPrinting(true); render(painter, target, source); setPrinting(false); }