/* * Moonlight|3D Copyright (C) 2006 The Moonlight|3D team * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Created on May 15, 2006 */ package eu.moonlight3d.graph; import java.util.ArrayList; import java.util.List; import com.trolltech.qt.core.QRectF; import com.trolltech.qt.gui.QBrush; import com.trolltech.qt.gui.QColor; import com.trolltech.qt.gui.QGraphicsItem; import com.trolltech.qt.gui.QGraphicsItemInterface; import com.trolltech.qt.gui.QGraphicsLineItem; import com.trolltech.qt.gui.QGraphicsRectItem; import com.trolltech.qt.gui.QGraphicsScene; import com.trolltech.qt.gui.QGraphicsTextItem; import eu.moonlight3d.graph.layout.Layouter; /** * This class acts as a container class for a directed acyclic graph that should be * displayed. * * @author gregor */ public class GraphModel extends QGraphicsScene { /** * The list of nodes in this graph model. */ private ArrayList nodes; /** * The list of links in this graph model. */ private ArrayList links; /** * The list of views that is registered to this graph model. */ private ArrayList views; /** * The list of listeners that is registered to this graph model. */ private ArrayList listeners; /** * An updated list of listeners that is created whenever registerListener() * or unregisterListener() is called while the list of listeners is being * iterated over. */ private ArrayList updatedListeners; /** * Number of functions that are iterating over the list of listeners at this moment. */ private int iteratingListeners; /** * Reference to the layouter that is currently active */ private Layouter layouter; /** * Default constructor for creating an empty graph model. */ public GraphModel() { nodes=new ArrayList(); links=new ArrayList(); views=new ArrayList(); listeners=new ArrayList(); iteratingListeners=0; updatedListeners=null; } /** * Set the graph layouter. The layouter will be used to determine the position * of nodes and links upon the next change to the model. * * @param layouter new graph layouter */ public void setLayouter(Layouter layouter) { this.layouter=layouter; } /** * Return the currently assigned layouter. * * @return current graph layouter */ public Layouter getLayouter() { return layouter; } /** * Clear the graph model. This resets the graph model to its initial empty state. */ public void clearGraph() { nodes.clear(); links.clear(); for(QGraphicsItemInterface item : items()) { removeItem(item); } } /** * Add a node to this graph model. * * @param node the node to insert */ public void addNode(Node node) { nodes.add(node); QGraphicsTextItem nodeTextItem=addText(node.getName()); NodeItem nodeItem=new NodeItem(0, 0, nodeTextItem.boundingRect().width(), nodeTextItem.boundingRect().height(), node); addItem(nodeItem); nodeTextItem.setDefaultTextColor(QColor.white); nodeTextItem.setParentItem(nodeItem); node.setItem(nodeItem); double height=nodeTextItem.boundingRect().height(); double width=nodeTextItem.boundingRect().width(); for(Slot slot : node.getSlots()) { QGraphicsTextItem slotTextItem=addText(slot.getName()); slotTextItem.moveBy(0, height); QGraphicsRectItem slotItem=addRect(0,height,nodeTextItem.boundingRect().width(),slotTextItem.boundingRect().height()); slotTextItem.setParentItem(slotItem); slotItem.setParentItem(nodeItem); if(slot.getType()==Slot.Type.Input) { slotItem.setBrush(new QBrush(QColor.darkRed)); } else { slotItem.setBrush(new QBrush(QColor.darkGreen)); } slotItem.setZValue(10); height+=nodeTextItem.boundingRect().height(); slot.setItem(slotItem); if(slotItem.rect().width()>width) { width=slotItem.rect().width(); } } for(Slot slot : node.getSlots()) { QRectF rect=slot.getItem().rect(); rect.setWidth(width); slot.getItem().setRect(rect); } nodeItem.setRect(nodeItem.x()-3, nodeItem.y(), width+6, height+3); if(layouter!=null) { layouter.place(this, node); } } /** * Return the node that has the given name. * * @param nodeName the node with the given name * @return the node with that name or null if no node was found */ public Node getNode(String nodeName) { for(Node node : nodes) { if(node.getName().equals(nodeName)) { return node; } } return null; } public void removeNode(Node node) { for(Slot slot : node.getSlots()) { if(slot.getLink()!=null) { Link link=slot.getLink(); removeLink(link); } removeItem(slot.getItem()); } removeItem(node.getItem()); nodes.remove(node); } /** * Return a list of all nodes in this graph model. * * @return all nodes in this graph */ public List getNodes() { return (ArrayList) nodes.clone(); } /** * Add a link between two slots. The link is assumed to be directed from the * first to the second slot in the argument list. Link direction is important * because it influences the way the graph is laid out. * * @param input the slot which acts as link input * @param output the slot which acts as link output */ public void addLink(Slot input, Slot output) { Link link=new Link(input, output); QGraphicsLineItem linkItem=addLine(0,0,0,0); link.setItem(linkItem); input.setLink(link); output.setLink(link); links.add(link); if(layouter!=null) { layouter.insertLink(this, link); } } /** * Return a list of all links in this graph model. * * @return a list of all links */ public List getLinks() { return (ArrayList) links.clone(); } public void removeLink(Link link) { removeItem(link.getItem()); links.remove(link); link.getStartSlot().setLink(null); link.getEndSlot().setLink(null); } public void removeLink(Slot slot) { if(slot.getLink()!=null) { removeLink(slot.getLink()); } } /** * Recalculate the layout of the whole graph. The current layout will be * destroyed and recreated from the current operator graph structure. */ public void recalculateLayout() { if(layouter!=null) { layouter.layout(this); } } /** * Register a GraphView that should display this graph. * * @param view the view that should be registered */ void registerGraphView(GraphView view) { views.add(view); } /** * Unregister a GraphView. This detaches the GraphView from this model. * * @param view the view that should be unregistered */ void unregisterGraphView(GraphView view) { views.remove(view); } /** * Register a graph model listener. The listener will receive all events * emitted from the graph model instance it is registered to. * * @param listener the listener to add */ public void registerListener(GraphModelListener listener) { if(iteratingListeners==0) { listeners.add(listener); } else { updatedListeners=(ArrayList) listeners.clone(); updatedListeners.add(listener); } } /** * Unregister a graph model listener. The listener will no longer receive * events from the graph model. * * @param listener the listener to remove */ public void unregisterListener(GraphModelListener listener) { if(iteratingListeners==0) { listeners.remove(listener); } else { updatedListeners=(ArrayList) listeners.clone(); updatedListeners.remove(listener); } } void handleSelection(Node node, boolean selected) { iteratingListeners++; try { for(GraphModelListener listener : listeners) { listener.handleSelection(this,node,selected); } } finally { iteratingListeners--; if(updatedListeners!=null) { listeners=updatedListeners; updatedListeners=null; } } } }