/**************************************************************************** ** ** (C) 1992-2007 Trolltech ASA. All rights reserved. ** ** This file is part of Qt Jambi. ** ** ** This file may be used under the terms of the GNU General Public ** License version 2.0 as published by the Free Software Foundation ** and appearing in the file LICENSE.GPL included in the packaging of ** this file. Please review the following information to ensure GNU ** General Public Licensing requirements will be met: ** http://www.trolltech.com/products/qt/opensource.html ** ** If you are unsure which license is appropriate for your use, please ** review the following information: ** http://www.trolltech.com/products/qt/licensing.html or contact the ** sales department at sales@trolltech.com. ** ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. ** ****************************************************************************/ package com.trolltech.examples; import java.util.Vector; import com.trolltech.qt.core.*; import com.trolltech.qt.gui.*; @QtJambiExample(name = "Elastic Nodes") public class ElasticNodes extends QGraphicsView { public static void main(String args[]) { QApplication.initialize(args); ElasticNodes elasticNodes = new ElasticNodes(); elasticNodes.show(); QApplication.exec(); } private int timerId; private Node centerNode; private Vector<Node> nodes = new Vector<Node>(); private static final QBrush BRUSH_DARK_GRAY = new QBrush(QColor.darkGray); private static final QPen QPEN_EDGE = new QPen(QColor.black, 1, Qt.PenStyle.SolidLine, Qt.PenCapStyle.RoundCap, Qt.PenJoinStyle.RoundJoin); private static final QPen QPEN_BLACK = new QPen(QColor.black, 0); private static QRadialGradient GRADIENT_SUNKEN; private static QRadialGradient GRADIENT_NORMAL; private static QPainterPath NODE_SHAPE; static{ NODE_SHAPE = new QPainterPath(); NODE_SHAPE.addEllipse(-10, -10, 20, 20); GRADIENT_SUNKEN = new QRadialGradient(-3, -3, 10); GRADIENT_SUNKEN.setCenter(3, 3); GRADIENT_SUNKEN.setFocalPoint(3, 3); GRADIENT_SUNKEN.setColorAt(1, new QColor(QColor.yellow).lighter(120)); GRADIENT_SUNKEN.setColorAt(0, new QColor(QColor.darkYellow).lighter(120)); GRADIENT_NORMAL = new QRadialGradient(-3, -3, 10); GRADIENT_NORMAL.setColorAt(0, QColor.yellow); GRADIENT_NORMAL.setColorAt(1, QColor.darkYellow); } public ElasticNodes() { QGraphicsScene scene = new QGraphicsScene(this); scene.setItemIndexMethod(QGraphicsScene.ItemIndexMethod.NoIndex); scene.setSceneRect(-200, -200, 400, 400); setScene(scene); setCacheMode(new QGraphicsView.CacheMode(QGraphicsView.CacheModeFlag.CacheBackground)); setRenderHint(QPainter.RenderHint.Antialiasing); setTransformationAnchor(QGraphicsView.ViewportAnchor.AnchorUnderMouse); setResizeAnchor(QGraphicsView.ViewportAnchor.AnchorViewCenter); Node node1 = new Node(this); Node node2 = new Node(this); Node node3 = new Node(this); Node node4 = new Node(this); centerNode = new Node(this); Node node6 = new Node(this); Node node7 = new Node(this); Node node8 = new Node(this); Node node9 = new Node(this); scene.addItem(node1); scene.addItem(node2); scene.addItem(node3); scene.addItem(node4); scene.addItem(centerNode); scene.addItem(node6); scene.addItem(node7); scene.addItem(node8); scene.addItem(node9); scene.addItem(new Edge(node1, node2)); scene.addItem(new Edge(node2, node3)); scene.addItem(new Edge(node2, centerNode)); scene.addItem(new Edge(node3, node6)); scene.addItem(new Edge(node4, node1)); scene.addItem(new Edge(node4, centerNode)); scene.addItem(new Edge(centerNode, node6)); scene.addItem(new Edge(centerNode, node8)); scene.addItem(new Edge(node6, node9)); scene.addItem(new Edge(node7, node4)); scene.addItem(new Edge(node8, node7)); scene.addItem(new Edge(node9, node8)); node1.setPos(-50, -50); node2.setPos(0, -50); node3.setPos(50, -50); node4.setPos(-50, 0); centerNode.setPos(0, 0); node6.setPos(50, 0); node7.setPos(-50, 50); node8.setPos(0, 50); node9.setPos(50, 50); scale(0.8, 0.8); for (QGraphicsItemInterface item : scene().items()) { if (item instanceof Node) nodes.add((Node) item); } setMinimumSize(400, 400); setWindowTitle(tr("Elastic Nodes")); setWindowIcon(new QIcon("classpath:com/trolltech/images/qt-logo.png")); } private void itemMoved() { if (timerId == 0) timerId = startTimer(1000 / 25); } protected void keyPressEvent(QKeyEvent event) { Qt.Key key = Qt.Key.resolve(event.key()); switch (key) { case Key_Up: centerNode.moveBy(0, -20); break; case Key_Down: centerNode.moveBy(0, 20); break; case Key_Left: centerNode.moveBy(-20, 0); break; case Key_Right: centerNode.moveBy(20, 0); break; case Key_Plus: scaleView(1.2); break; case Key_Minus: scaleView(1 / 1.2); break; case Key_Space: case Key_Enter: for (QGraphicsItemInterface item : scene().items()) { if (item instanceof Node) item.setPos(-150 + Math.random() * 300, -150 + Math.random() * 300); } break; default: super.keyPressEvent(event); } } protected void timerEvent(QTimerEvent event) { for (Node node : nodes) node.calculateForces(); boolean itemsMoved = false; for (Node node : nodes) { if (node.advance()) itemsMoved = true; } if (!itemsMoved) { killTimer(timerId); timerId = 0; } } protected void wheelEvent(QWheelEvent event) { scaleView(Math.pow(2, -event.delta() / 240.0)); } protected void drawBackground(QPainter painter, QRectF rect) { // Shadow QRectF sceneRect = this.sceneRect(); QRectF rightShadow = new QRectF(sceneRect.right(), sceneRect.top() + 5, 5, sceneRect.height()); QRectF bottomShadow = new QRectF(sceneRect.left() + 5, sceneRect.bottom(), sceneRect.width(), 5); if (rightShadow.intersects(rect) || rightShadow.contains(rect)) painter.fillRect(rightShadow, BRUSH_DARK_GRAY); if (bottomShadow.intersects(rect) || bottomShadow.contains(rect)) painter.fillRect(bottomShadow, BRUSH_DARK_GRAY); // Fill QLinearGradient gradient = new QLinearGradient(sceneRect.topLeft(), sceneRect.bottomRight()); gradient.setColorAt(0, QColor.white); gradient.setColorAt(1, QColor.lightGray); painter.fillRect(rect.intersected(sceneRect), new QBrush(gradient)); painter.setBrush(QBrush.NoBrush); painter.drawRect(sceneRect); // Text QRectF textRect = new QRectF(sceneRect.left() + 4, sceneRect.top() + 4, sceneRect.width() - 4, sceneRect.height() - 4); String message = tr("Click and drag the nodes around, and zoom with the mouse wheel or the '+' and '-' keys"); QFont font = painter.font(); font.setBold(true); font.setPointSize(14); painter.setFont(font); painter.setPen(QColor.lightGray); painter.drawText(textRect.translated(2, 2), message); painter.setPen(QColor.black); painter.drawText(textRect, message); } private void scaleView(double scaleFactor) { QMatrix m = matrix(); m.scale(scaleFactor, scaleFactor); double factor = m.mapRect(new QRectF(0, 0, 1, 1)).width(); if (factor < 0.07 || factor > 100) return; scale(scaleFactor, scaleFactor); } public class Node extends QGraphicsItem { private Vector<Edge> edgeList = new Vector<Edge>(); private QPointF newPos; private ElasticNodes graph; private double adjust = 2; private QRectF boundingRect = new QRectF(-10 - adjust, -10 - adjust, 23 + adjust, 23 + adjust); Node(ElasticNodes graphWidget) { graph = graphWidget; setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable); setZValue(1); newPos = pos(); } private void addEdge(Edge edge) { edgeList.add(edge); edge.adjust(); } private void calculateForces() { if (scene() == null || scene().mouseGrabberItem() == this) { newPos = pos(); return; } // Sum up all forces pushing this item away double xvel = 0; double yvel = 0; for (Node node : nodes) { QPointF deltaPoint = mapFromItem(node, 0, 0); double dx = -deltaPoint.x(); double dy = -deltaPoint.y(); double l = 2.0 * (dx * dx + dy * dy); if (l > 0) { xvel += (dx * 150.0) / l; yvel += (dy * 150.0) / l; } } // Now subtract all forces pulling items together double weight = (edgeList.size() + 1) * 10; for (Edge edge : edgeList) { QPointF pos; if (edge.sourceNode() == this) pos = mapFromItem(edge.destNode(), 0, 0); else pos = mapFromItem(edge.sourceNode(), 0, 0); xvel += pos.x() / weight; yvel += pos.y() / weight; } if (Math.abs(xvel) < 0.1 && Math.abs(yvel) < 0.1) xvel = yvel = 0; QRectF sceneRect = scene().sceneRect(); newPos.setX(Math.min(Math.max(newPos.x() + xvel, sceneRect.left() + 10), sceneRect.right() - 10)); newPos.setY(Math.min(Math.max(newPos.y() + yvel, sceneRect.top() + 10), sceneRect.bottom() - 10)); } private boolean advance() { if (newPos == pos()) return false; setPos(newPos); return true; } public QRectF boundingRect() { return boundingRect; } public QPainterPath shape() { return NODE_SHAPE; } @Override public void paint(QPainter painter, QStyleOptionGraphicsItem option, QWidget widget) { painter.setPen(Qt.PenStyle.NoPen); painter.setBrush(QColor.fromRgba(QColor.black.rgb() & 0x7fffffff)); painter.drawEllipse(-7, -7, 20, 20); if ((option.state().isSet(QStyle.StateFlag.State_Sunken))) { painter.setBrush(GRADIENT_SUNKEN); } else { painter.setBrush(GRADIENT_NORMAL); } painter.setPen(QPEN_BLACK); painter.drawEllipse(-10, -10, 20, 20); } public Object itemChange(GraphicsItemChange change, Object value) { switch (change) { case ItemPositionChange: for (Edge edge : edgeList) edge.adjust(); graph.itemMoved(); break; default: break; } return super.itemChange(change, value); } public void mousePressEvent(QGraphicsSceneMouseEvent event) { update(); super.mousePressEvent(event); } public void mouseReleaseEvent(QGraphicsSceneMouseEvent event) { update(); super.mouseReleaseEvent(event); } } public class Edge extends QGraphicsItem { private Node source; private Node dest; private QPointF sourcePoint = new QPointF(); private QPointF destPoint = new QPointF(); private double arrowSize = 10; private double penWidth = 1; private double extra = (penWidth + arrowSize) / 2.0; private QRectF boundingRect = new QRectF(); QPointF sourceArrowP1 = new QPointF(); QPointF sourceArrowP2 = new QPointF(); QPointF destArrowP1 = new QPointF(); QPointF destArrowP2 = new QPointF(); QPolygonF pol1 = new QPolygonF(); QPolygonF pol2 = new QPolygonF(); public Edge(Node sourceNode, Node destNode) { // setAcceptedMouseButtons(LeftButton); source = sourceNode; dest = destNode; source.addEdge(this); dest.addEdge(this); adjust(); } private Node sourceNode() { return source; } private Node destNode() { return dest; } private void adjust() { double dx = source.pos().x()-dest.pos().x(); double dy = source.pos().y()-dest.pos().y(); double length = Math.sqrt(dx*dx+dy*dy); if (length == 0.0) return; double paddingX = dx/length*10; double paddingY = dy/length*10; removeFromIndex(); sourcePoint.setX(source.pos().x() - paddingX); sourcePoint.setY(source.pos().y() - paddingY); destPoint.setX(dest.pos().x() + paddingX); destPoint.setY(dest.pos().y() + paddingY); boundingRect.setBottomLeft(source.pos()); boundingRect.setTopRight(dest.pos()); boundingRect = boundingRect.normalized(); boundingRect.adjust(-extra, -extra, extra, extra); addToIndex(); } public QRectF boundingRect() { return boundingRect; } public void paint(QPainter painter, QStyleOptionGraphicsItem option, QWidget widget) { if (source == null || dest == null) return; // Draw the line itself QLineF line = new QLineF(sourcePoint, destPoint); painter.setPen(QPEN_EDGE); painter.drawLine(line); // Draw the arrows if there's enough room double angle; if (line.length() > 0) angle = Math.acos(line.dx() / line.length()); else angle = 0; if (line.dy() >= 0) angle = (Math.PI * 2) - angle; sourceArrowP1.setX(sourcePoint.x() + Math.sin(angle + Math.PI / 3) * arrowSize); sourceArrowP1.setY(sourcePoint.y() + Math.cos(angle + Math.PI / 3) * arrowSize); sourceArrowP2.setX(sourcePoint.x() + Math.sin(angle + Math.PI - Math.PI / 3) * arrowSize); sourceArrowP2.setY(sourcePoint.y() + Math.cos(angle + Math.PI - Math.PI / 3) * arrowSize); destArrowP1.setX(destPoint.x() + Math.sin(angle - Math.PI / 3) * arrowSize); destArrowP1.setY(destPoint.y() + Math.cos(angle - Math.PI / 3) * arrowSize); destArrowP2.setX(destPoint.x() + Math.sin(angle - Math.PI + Math.PI / 3) * arrowSize); destArrowP2.setY(destPoint.y() + Math.cos(angle - Math.PI + Math.PI / 3) * arrowSize); pol1.clear(); pol2.clear(); pol1.append(line.p1()); pol1.append(sourceArrowP1); pol1.append(sourceArrowP2); pol2.append(line.p2()); pol2.append(destArrowP1); pol2.append(destArrowP2); painter.setBrush(QColor.black); painter.drawPolygon(pol1); painter.drawPolygon(pol2); } } }