Ver código fonte

Merge branch 'TestViewSystem' of GraphDrawer/NodeShuffler into master

Eren B. Yilmaz 6 anos atrás
pai
commit
3ef33bb6e5

+ 1 - 2
doc/chapter/2architecture.tex

@@ -11,8 +11,7 @@ The following assumptions are made for the implementation of the node placement
 
 Regarding the last assumption, we found an example where for a disconnected graph the algorithm behaved incorrectly.
 Maybe we will get rid of this assumption later, see chapter~\ref{ch:progress}.
-We are currently investigating another example, where the algorithm behaves incorrectly for a connected graph.
-These two examples are included in the appendix (figures~\ref{fig:error_disconnected_img},~\ref{fig:error_connected_img},~\ref{fig:error_disconnected}, and~\ref{fig:error_connected}).
+This example is included in the appendix (figures~\ref{fig:error_disconnected_img} and~\ref{fig:error_disconnected}).
 
 
 \section{Overview}\label{sec:components}

+ 5 - 6
doc/chapter/4progress.tex

@@ -34,17 +34,17 @@ The following features are either planned (\planned), under construction (\progr
         \item[\done] Showing pseudocode and the position where the algorithm currently is.
         \begin{itemize}
             \item[\done] Clicking on the pseudocode to set a breakpoint at (not \reserved{goto}) a specific location.
-            \item[\planned] Customizing the font size of the code.
+            \item[\done] Customizing the font size of the code.
         \end{itemize}
         \item[\planned] Showing a legend to explain the shapes and colors.
         \item[\done] Showing tooltips when hovering over code lines and nodes in the graph that show the state of the variables.
-        \item[\planned] Hovering the mose over a node to highlight it in all layouts.
+        \item[\done] Hovering the mose over a node to highlight it in all layouts.
     \end{itemize}
     \item[\done] Running the algorithm step by step manually.
     \item[\done] Running the algorithm step by step with configurable delay (\enquote{automatic execution}).
     \item[\done] Running the algorithm only on the expanded parts of the source code (\enquote{step overrun}).
     \begin{itemize}
-        \item[\planned] Right clicking the code to expand or collapse all lines up to a specified level.
+        \item[\done] Right clicking the code to expand or collapse all lines up to a specified level.
     \end{itemize}
     \item[\done] Using debugger-like commands such as \enquote{step into}, \enquote{step over}, \enquote{step out}.
     \item[\done] Adding buttons and other graphical elements to support the user interface (low priority, see figure~\ref{fig:sketch}).
@@ -61,9 +61,8 @@ The following features are either planned (\planned), under construction (\progr
 \end{itemize}
 
 \section{Known Issues}\label{sec:knownIssues}
-The most important unsolved issues are listed here.
+Only the most important unsolved issues are listed here.
 For a complete list, see \url{https://koljastrohm-games.com:3000/GraphDrawer/NodeShuffler/issues}.
 \begin{itemize}
-    \item Our implementation does not behave correctly for very few graphs, see section ~\ref{sec:assumptions} and figures~\ref{fig:error_disconnected_img},~\ref{fig:error_connected_img},~\ref{fig:error_disconnected}, and~\ref{fig:error_connected}.
-    \item Swing is not thread-safe, but we are using multiple threads, so every now and then there are race conditions causing mostly \code{ArrayIndexOutOfBoundsException}s and \code{NullPointerException}s.
+    \item The most important issues were solved.
 \end{itemize}

+ 0 - 17
doc/chapter/appendix.tex

@@ -57,14 +57,6 @@
     \label{fig:error_disconnected_img}
 \end{figure}
 
-\begin{figure}[htp]
-    \centering
-    \includegraphics[width=0.33\linewidth]{img/error_connected}
-    \caption[Error caused by connected graph]{An illustration of the error caused by the graph displayed in figure~\ref{fig:error_connected}.
-    In the fourth layer the last two nodes are drawn at the same position.}
-    \label{fig:error_connected_img}
-\end{figure}
-
 \begin{figure}
     \begin{lstinputlisting}[language=json,emph={},basicstyle=\scriptsize\ttfamily,numberstyle=\tiny]{src/error_disconnected.json}
     \end{lstinputlisting}
@@ -72,12 +64,3 @@
     The error is illustrated in figure~\ref{fig:error_disconnected_img}.}
     \label{fig:error_disconnected}
 \end{figure}
-
-\begin{figure}
-    \begin{lstinputlisting}[language=json,emph={},basicstyle=\tiny\ttfamily,numberstyle=\tiny]{src/error_connected.json}
-    \end{lstinputlisting}
-    \caption[Connected graph causing an error]{Example graph where the node placement algorithm does not behave correctly, even though it is connected.
-    We did not check yet if this is a problem with our implementation or with the node placement algorithm itself.
-    The error is illustrated in figure~\ref{fig:error_connected_img}.}
-    \label{fig:error_connected}
-\end{figure}

+ 2 - 0
doc/doc.tex

@@ -57,6 +57,8 @@
 \setlength{\abovecaptionskip}{0.2cm} % Abstand der zwischen Bild- und Bildunterschrift
 \usepackage{enumitem} % Referenzen auf Item in enumerate-Blocks
 
+\usepackage[normalem]{ulem} % durchgestichener text mit \sout
+
 % A checklist for the current progress
 \usepackage{pifont}
 \usepackage{tikz}

+ 18 - 9
src/animation/AnimatedAlgorithm.java

@@ -17,6 +17,7 @@ public abstract class AnimatedAlgorithm extends Thread {
     private JFrame view;
     protected PseudoCodeNode root;
     protected PseudoCodeProcessor processor;
+    private boolean renderImage = false;
 
     public AnimatedAlgorithm( AnimationController controller, LayeredGraphNode graph, JFrame view )
     {
@@ -24,19 +25,27 @@ public abstract class AnimatedAlgorithm extends Thread {
         this.graph = graph;
         this.view = view;
         root = null;
+        renderImage = true;
     }
 
-    private void update()
+    private synchronized void update()
     {
-        SwingUtilities.invokeLater(new Runnable() {
-            public void run() {
-                view.repaint();
-                for( ComponentListener l : view.getComponentListeners() )
-                {
-                    l.componentResized( new ComponentEvent(view, 0) );
+        if( renderImage )
+        {
+            renderImage = false;
+            SwingUtilities.invokeLater(new Runnable() {
+                public void run() {
+                    for( ComponentListener l : view.getComponentListeners() )
+                    {
+                        l.componentResized( new ComponentEvent(view, 0) );
+                        synchronized( AnimatedAlgorithm.this )
+                        {
+                            renderImage = true;
+                        }
+                    }
                 }
-            }
-        });
+            });
+        }
     }
 
     @Override

+ 3 - 3
src/bk/BKNodePlacement.java

@@ -148,8 +148,7 @@ public class BKNodePlacement extends AnimatedAlgorithm {
                 case BOTTOM_TOP_RIGHT:
                     state = State.LAYOUT4;
                     break;
-                case COMBINED:
-                    state = State.COMBINE;
+                case COMBINED: // this will never happen here
                     break;
                 }
                 for( LayeredGraphNode n : graph.getContainedNodes() )
@@ -175,6 +174,7 @@ public class BKNodePlacement extends AnimatedAlgorithm {
         PseudoCodeNode balancingStage = new PseudoCodeNode( "-- balancing --", vars, tree, new Comment() ) {
             @Override
             public String getDebugOutput( Memory m ) {
+                state = State.COMBINE;
                 if( !m.isSomewhereDefined( "graph", MemoryType.COMPLETE_STACK ) )
                     return "";
                 String info = "| Node | x BR | x BL | x UR | x UL | x CO |\n";
@@ -186,7 +186,7 @@ public class BKNodePlacement extends AnimatedAlgorithm {
                             "|" + TextLayoutHelper.strToLen( n.getX( LayoutType.TOP_BOTTOM_LEFT ) + "", 6 ) +
                             "|" + TextLayoutHelper.strToLen( n.getX( LayoutType.TOP_BOTTOM_RIGHT ) + "", 6 ) +
                             "|" + TextLayoutHelper.strToLen( n.getX( LayoutType.BOTTOM_TOP_LEFT ) + "", 6 ) +
-                            "|" + TextLayoutHelper.strToLen( n.getX( LayoutType.BOTTOM_TOP_LEFT ) + "", 6 ) +
+                            "|" + TextLayoutHelper.strToLen( n.getX( LayoutType.BOTTOM_TOP_RIGHT ) + "", 6 ) +
                             "|" + TextLayoutHelper.strToLen( n.getX( LayoutType.COMBINED ) + "", 6 ) + "|\n";
                 }
                 return info;

+ 7 - 2
src/bk/PlaceBlock.java

@@ -15,7 +15,9 @@ import codelines.IfLoop;
 import codelines.SetVariable;
 import codelines.WhileLoop;
 import graph.LayeredGraphNode;
+import graph.LayeredNode;
 // PAPER = OUR
+import view.NodeView;
 
 // DOWN = RIGHT
 // UP = LEFT
@@ -174,7 +176,10 @@ public class PlaceBlock{
     {
         double max = 0;
         for( LayeredGraphNode n : graph.getContainedNodes() )
-            max = Math.max( max, n.getWidth( layout ) );
-        return max + 5;
+        {
+            NodeView v = ((LayeredNode)n).getView( layout );
+            max = Math.max( max, v.getOriginalWidth() );
+        }
+        return max + 20;
     }
 }

+ 2 - 8
src/graph/LayeredEdge.java

@@ -189,14 +189,8 @@ public class LayeredEdge implements LayeredGraphEdge {
     {
         NodeView sourceView = ((LayeredNode)sources.get( 0 )).getView( layout );
         NodeView targetView = ((LayeredNode)targets.get( 0 )).getView( layout );
-        //if( bindPoints.get( 0 ) == null && sources.size() > 0 )
-        //{
-            setStartPoint( (int)sources.get( 0 ).getX( layout ) + sourceView.getScaledX( (int)sources.get( 0 ).getWidth( layout ) / 2 ), (int)sources.get( 0 ).getY( layout ) + sourceView.getPreferredSize().height - sourceView.getScaledY( 0 ), layout );
-        //}
-        //if( bindPoints.get( bindPoints.size() - 1 ) == null && targets.size() > 0 )
-        //{
-            setEndPoint( (int)targets.get( 0 ).getX( layout ) + targetView.getScaledX( (int)targets.get( 0 ).getWidth( layout ) / 2 ) , (int)targets.get( 0 ).getY( layout ) + targetView.getScaledY( 0 ), layout );
-        //}
+        setStartPoint( sourceView.getVirtualX() + sourceView.getVirtualWidth() / 2, sourceView.getVirtualY() + sourceView.getVirtualHeight(), layout );
+        setEndPoint( targetView.getVirtualX() + targetView.getVirtualWidth() / 2, targetView.getVirtualY(), layout );
         if( layout == LayoutType.TOP_BOTTOM_LEFT )
             return bindPoints[ 0 ];
         if( layout == LayoutType.TOP_BOTTOM_RIGHT )

+ 12 - 0
src/graph/LayeredGraphNode.java

@@ -24,6 +24,18 @@ public interface LayeredGraphNode {
    */
   public ElkNode getOriginalNode();
 
+  /**
+   * setter for the field mouseOver
+   * @param mouseOver the new value
+   */
+  public void setMouseOver( boolean mouseOver );
+  
+  /**
+   * getter for the field mouseOver
+   * @return current value of the field
+   */
+  public boolean isMouseOver();
+  
   /**
    * set the shift of this node to the given value in the given layout
    * or in all layouts if the argument is null.

+ 67 - 54
src/graph/LayeredNode.java

@@ -45,7 +45,6 @@ public class LayeredNode implements LayeredGraphNode {
         public double y;
         public double w;
         public double h;
-        public Color color;
         public boolean selected;
         private NodeView view;
     }
@@ -53,6 +52,7 @@ public class LayeredNode implements LayeredGraphNode {
     private LayoutInfo[] layouts;
     private CombinedLayoutInfo combined;
     private String name;
+    private boolean mouseOver;
 
     // for subgraph in this node
     private ArrayList<LayeredGraphEdge> edges;
@@ -123,8 +123,16 @@ public class LayeredNode implements LayeredGraphNode {
             combined.w = original.getWidth();
             combined.h = original.getHeight();
         }
-        combined.color = null;
         combined.selected = false;
+        setMouseOver(false);
+    }
+
+    public void setMouseOver( boolean over ) {
+        mouseOver = over;
+    }
+
+    public boolean isMouseOver() {
+        return mouseOver;
     }
 
     public void setView(NodeView view, LayoutType layout) {
@@ -355,24 +363,23 @@ public class LayeredNode implements LayeredGraphNode {
     }
 
     @Override
-    public void setColor(Color c, LayoutType layout) {
-        if (layout == null) {
-            this.layouts[0].color = c;
-            this.layouts[1].color = c;
-            this.layouts[2].color = c;
-            this.layouts[3].color = c;
-            combined.color = c;
+    public void setColor( Color c, LayoutType layout )
+    {
+        if( layout == null )
+        {
+            this.layouts[ 0 ].color = c;
+            this.layouts[ 1 ].color = c;
+            this.layouts[ 2 ].color = c;
+            this.layouts[ 3 ].color = c;
         }
-        if (layout == LayoutType.TOP_BOTTOM_LEFT)
-            this.layouts[0].color = c;
-        if (layout == LayoutType.TOP_BOTTOM_RIGHT)
-            this.layouts[1].color = c;
-        if (layout == LayoutType.BOTTOM_TOP_LEFT)
-            this.layouts[2].color = c;
-        if (layout == LayoutType.BOTTOM_TOP_RIGHT)
-            this.layouts[3].color = c;
-        if (layout == LayoutType.COMBINED)
-            combined.color = c;
+        if( layout == LayoutType.TOP_BOTTOM_LEFT )
+            this.layouts[ 0 ].color = c;
+        if( layout == LayoutType.TOP_BOTTOM_RIGHT )
+            this.layouts[ 1 ].color = c;
+        if( layout == LayoutType.BOTTOM_TOP_LEFT )
+            this.layouts[ 2 ].color = c;
+        if( layout == LayoutType.BOTTOM_TOP_RIGHT )
+            this.layouts[ 3 ].color = c;
     }
 
     private int calcClassSize(LayeredGraphNode sink, LayoutType layout) {
@@ -408,18 +415,21 @@ public class LayeredNode implements LayeredGraphNode {
             return this.layouts[2].sink.getClassColor(layout);
         if (layout == LayoutType.BOTTOM_TOP_RIGHT && this.layouts[3].sink == this && calcClassSize(this, layout) == 1)
             return Color.LIGHT_GRAY;
-        if (layout == LayoutType.BOTTOM_TOP_RIGHT && this.layouts[3].sink == this)
-            return this.layouts[3].color;
-        else if (layout == LayoutType.BOTTOM_TOP_RIGHT)
-            return this.layouts[3].sink.getClassColor(layout);
-        if (layout == LayoutType.COMBINED)
-            return combined.color;
+        if( layout == LayoutType.BOTTOM_TOP_RIGHT && this.layouts[ 3 ].sink == this )
+            return this.layouts[ 3 ].color;
+        else if( layout == LayoutType.BOTTOM_TOP_RIGHT )
+            return this.layouts[ 3 ].sink.getClassColor( layout );
+        if( layout == LayoutType.COMBINED )
+            return Color.LIGHT_GRAY;
         return null;
     }
 
     @Override
-    public Color getColor(LayoutType layout) {
-        if (layout == LayoutType.TOP_BOTTOM_LEFT && this.layouts[0].root == this && this.layouts[0].align == this)
+    public Color getColor( LayoutType layout )
+    {
+        if( layout == null )
+            return this.layouts[ 0 ].color;
+        if( layout == LayoutType.TOP_BOTTOM_LEFT && this.layouts[ 0 ].root == this && this.layouts[ 0 ].align == this )
             return Color.LIGHT_GRAY;
         if (layout == LayoutType.TOP_BOTTOM_LEFT && this.layouts[0].root != this)
             return this.layouts[0].root.getColor(layout);
@@ -439,12 +449,12 @@ public class LayeredNode implements LayeredGraphNode {
             return this.layouts[2].color;
         if (layout == LayoutType.BOTTOM_TOP_RIGHT && this.layouts[3].root == this && this.layouts[3].align == this)
             return Color.LIGHT_GRAY;
-        if (layout == LayoutType.BOTTOM_TOP_RIGHT && this.layouts[3].root != this)
-            return this.layouts[3].root.getColor(layout);
-        if (layout == LayoutType.BOTTOM_TOP_RIGHT)
-            return this.layouts[3].color;
-        if (layout == LayoutType.COMBINED)
-            return combined.color;
+        if( layout == LayoutType.BOTTOM_TOP_RIGHT && this.layouts[ 3 ].root != this )
+            return this.layouts[ 3 ].root.getColor( layout );
+        if( layout == LayoutType.BOTTOM_TOP_RIGHT )
+            return this.layouts[ 3 ].color;
+        if( layout == LayoutType.COMBINED )
+            return Color.LIGHT_GRAY;
         return null;
     }
 
@@ -568,10 +578,11 @@ public class LayeredNode implements LayeredGraphNode {
         if (nodes.size() > 0) {
             double max = 0;
             double min = Double.POSITIVE_INFINITY;
-            for (LayeredGraphNode n : nodes) {
-                if (max < n.getX(layout) + n.getWidth(layout) + 50)
-                    max = n.getX(layout) + n.getWidth(layout) + 50;
-                min = Math.min(n.getX(layout), min);
+            for( LayeredGraphNode n : nodes )
+            {
+                if( max < n.getX(layout) + n.getWidth(layout) )
+                    max = n.getX(layout) + n.getWidth(layout);
+                min = Math.min( n.getX(layout), min);
             }
             if (layout == LayoutType.TOP_BOTTOM_LEFT)
                 return Math.max(max - min, layouts[0].w);
@@ -598,23 +609,25 @@ public class LayeredNode implements LayeredGraphNode {
     }
 
     @Override
-    public double getHeight(LayoutType layout) {
-        if (nodes.size() > 0) {
-            double max = 0;
-            for (LayeredGraphNode n : nodes) {
-                if (max < n.getY(layout) + n.getHeight(layout) + 50)
-                    max = n.getY(layout) + n.getHeight(layout) + 50;
-            }
-            if (layout == LayoutType.TOP_BOTTOM_LEFT)
-                return Math.max(max, layouts[0].h);
-            if (layout == LayoutType.TOP_BOTTOM_RIGHT)
-                return Math.max(max, layouts[1].h);
-            if (layout == LayoutType.BOTTOM_TOP_LEFT)
-                return Math.max(max, layouts[2].h);
-            if (layout == LayoutType.BOTTOM_TOP_RIGHT)
-                return Math.max(max, layouts[3].h);
-            if (layout == LayoutType.COMBINED)
-                return Math.max(max, combined.h);
+    public double getHeight( LayoutType layout ) {
+        if( nodes.size() > 0 )
+        {
+        	double max = 0;
+        	for( LayeredGraphNode n : nodes )
+        	{
+        		if( max < n.getY(layout) + n.getHeight(layout) )
+        			max = n.getY(layout) + n.getHeight(layout);
+        	}
+            if( layout == LayoutType.TOP_BOTTOM_LEFT )
+                return Math.max( max, layouts[ 0 ].h );
+            if( layout == LayoutType.TOP_BOTTOM_RIGHT )
+                return Math.max( max, layouts[ 1 ].h );
+            if( layout == LayoutType.BOTTOM_TOP_LEFT )
+                return Math.max( max, layouts[ 2 ].h );
+            if( layout == LayoutType.BOTTOM_TOP_RIGHT )
+                return Math.max( max, layouts[ 3 ].h );
+            if( layout == LayoutType.COMBINED )
+                return Math.max( max, combined.h );
         }
         if (layout == LayoutType.TOP_BOTTOM_LEFT)
             return layouts[0].h;

+ 6 - 5
src/graph/InitializeNodePositions.java → src/lib/SimpleNodePlacement.java

@@ -1,9 +1,10 @@
-package graph;
+package lib;
 
 import java.awt.Color;
 import java.util.ArrayList;
 
 import bk.LayoutType;
+import graph.LayeredGraphNode;
 
 
 /**
@@ -12,7 +13,7 @@ import bk.LayoutType;
  * @author kolja
  *
  */
-public class InitializeNodePositions {
+public class SimpleNodePlacement {
     /**
      * the actual algorithm
      * @param graph the graph where the node positions are to be set.
@@ -24,7 +25,7 @@ public class InitializeNodePositions {
         for( LayeredGraphNode n : graph.getContainedNodes() )
         {
             n.setColor( Color.getHSBColor( hue, saturation, brightness ), null );
-            hue += 0.29f;
+            hue += 0.15f;
             placeNodes( n );
         }
         int curY = 0;
@@ -45,9 +46,9 @@ public class InitializeNodePositions {
             for (LayeredGraphNode node : layer) { // Gehe alle Knoten durch
                 node.setX( curX + maxWidth / 2 - node.getWidth( LayoutType.TOP_BOTTOM_LEFT ) / 2, false, null );
                 node.setY( curY + maxHeight / 2 - node.getHeight( LayoutType.TOP_BOTTOM_LEFT ) / 2, null ); // Position setzen
-                curX += maxWidth + 25;
+                curX += maxWidth + 20;
             }
-            curY += maxHeight + 25;
+            curY += maxHeight + 20;
         }
     }
 }

+ 2 - 2
src/main/Main.java

@@ -1,7 +1,7 @@
 package main;
-import graph.InitializeNodePositions;
 import graph.LayeredGraphNode;
 import graph.io.Reader;
+import lib.SimpleNodePlacement;
 import view.MainView;
 
 
@@ -19,7 +19,7 @@ public class Main {
     public static void main(String[] args) {
         Reader r = new Reader( "logo.json" );
         LayeredGraphNode graph = r.readInputGraph();
-        InitializeNodePositions.placeNodes( graph );
+        SimpleNodePlacement.placeNodes( graph );
         new MainView( graph );
     }
  

+ 9 - 0
src/view/AnnimatedView.java

@@ -0,0 +1,9 @@
+package view;
+
+public interface AnnimatedView {
+
+    public int getVirtualX();
+    public int getVirtualY();
+    public int getVirtualWidth();
+    public int getVirtualHeight();
+}

+ 51 - 50
src/view/EdgeView.java

@@ -2,7 +2,6 @@ package view;
 
 import java.awt.BasicStroke;
 import java.awt.Color;
-import java.awt.Dimension;
 import java.awt.Graphics;
 import java.awt.Graphics2D;
 import java.awt.Point;
@@ -18,7 +17,7 @@ import graph.LayeredGraphEdge;
  * @author kolja
  *
  */
-public class EdgeView extends JPanel {
+public class EdgeView extends JPanel implements AnnimatedView {
     private static final long serialVersionUID = 1L;
 
     private LayeredGraphEdge model;
@@ -30,73 +29,75 @@ public class EdgeView extends JPanel {
     }
     
     @Override
-    public Point getLocation()
-    {
-        ArrayList<Point> bps = model.getLinePoints( layout );
-        double minX = bps.get( 0 ).getX();
-        double minY = bps.get( 0 ).getY();
-        for( Point p : bps )
-        {
-            minX = Math.min( minX, p.getX() );
-            minY =  Math.min( minY, p.getY() );
-        }
-        minX -= 5;
-        minY -= 5;
-        return new Point( (int)minX, (int)minY );
-    }
-    
-    @Override
-    public int getWidth()
+    public void paint( Graphics g )
     {
-        ArrayList<Point> bps = model.getLinePoints( layout );
-        double max = bps.get( 0 ).getX();
-        for( Point p : bps )
-            max =  Math.max( max, p.getX() );
-        return (int)max + 10;
+        paintComponent( g );
     }
     
-    @Override
-    public int getHeight()
+    private int getScaledX( int x )
     {
-        ArrayList<Point> bps = model.getLinePoints( layout );
-        double max = bps.get( 0 ).getY();
-        for( Point p : bps )
-            max =  Math.max( max, p.getY() );
-        return (int)max + 10;
+        double scale = Math.min( getWidth() / (double)getVirtualWidth(), getHeight() / (double)getVirtualHeight());
+        x *= scale;
+        return x;
     }
     
-    @Override
-    public Dimension getPreferredSize()
+    private int getScaledY( int y )
     {
-        return new Dimension( getWidth(), getHeight() );
+        double scale = Math.min( getWidth() / (double)getVirtualWidth(), getHeight() / (double)getVirtualHeight());
+        y *= scale;
+        return y;
     }
-    
-    @Override
-    public void paint( Graphics g )
-    {
-        paintComponent( g );/*
-        for( Component c : getComponents() )
-            c.paint( g.create( 
-                    c.getX() + 10, 
-                    c.getY() + 10, 
-                    Math.min( getWidth() - 10, c.getWidth() + c.getX() + 10 ), 
-                    Math.min( getHeight() - 10, c.getHeight() + c.getY() + 10 ) ) );
-    */}
 
     @Override
     public void paintComponent( Graphics g )
     {
         ((Graphics2D)g).setStroke(new BasicStroke(1));
-        //System.out.println( "Clipping: x:" + g.getClip().getBounds().getX() + " y:" + g.getClip().getBounds().getY() + " w:" + g.getClip().getBounds().getWidth() + " h:" + g.getClip().getBounds().getHeight() );
         g.setColor( Color.LIGHT_GRAY );
         if( model.isConflicted( layout ) )
             g.setColor( Color.RED );
         ArrayList<Point> bps = model.getLinePoints( layout );
+        int x = getVirtualX();
+        int y = getVirtualY();
         for( int i = 1; i < bps.size(); i++ )
         {
-          //  System.out.println( "Draw a Line from (" + (int)bps.get( i - 1 ).getX() + "," + (int)bps.get( i - 1 ).getY() + ") to (" + (int)bps.get( i ).getX() + "," + (int)bps.get( i ).getY() + ")" );
-            g.drawLine( (int)bps.get( i - 1 ).getX() - getLocation().x, (int)bps.get( i - 1 ).getY() - getLocation().y, (int)bps.get( i ).getX() - getLocation().x, (int)bps.get( i ).getY() - getLocation().y );
+            g.drawLine( getScaledX((int)bps.get( i - 1 ).getX() - x), getScaledY((int)bps.get( i - 1 ).getY() - y), getScaledX((int)bps.get( i ).getX() - x), getScaledY((int)bps.get( i ).getY() - y ));
         }
-        ((Graphics2D)g).fill( RenderHelper.createArrowShape( new Point( bps.get( bps.size() - 2 ).x - getLocation().x, bps.get( bps.size() - 2 ).y - getLocation().y ), new Point( bps.get( bps.size() - 1 ).x - getLocation().x, bps.get( bps.size() - 1 ).y - getLocation().y ) ) );
+        ((Graphics2D)g).fill( RenderHelper.createArrowShape( new Point( getScaledX( bps.get( 0 ).x - x ), getScaledY( bps.get( 0 ).y - y ) ), new Point( getScaledX( bps.get( bps.size() - 1 ).x - x ), getScaledY( bps.get( bps.size() - 1 ).y - y ) ) ) );
+    }
+
+    @Override
+    public int getVirtualX() {
+        int min = Integer.MAX_VALUE;
+        ArrayList<Point> bps = model.getLinePoints( layout );
+        for( Point p : bps )
+            min = Math.min( min, (int)p.getX() - 5 );
+        return min;
+    }
+
+    @Override
+    public int getVirtualY() {
+        int min = Integer.MAX_VALUE;
+        ArrayList<Point> bps = model.getLinePoints( layout );
+        for( Point p : bps )
+            min = Math.min( min, (int)p.getY() - 5 );
+        return min;
+    }
+
+    @Override
+    public int getVirtualWidth() {
+        int max = Integer.MIN_VALUE;
+        ArrayList<Point> bps = model.getLinePoints( layout );
+        for( Point p : bps )
+            max = Math.max( max, (int)p.getX() + 5 );
+        return max - getVirtualX();
+    }
+
+    @Override
+    public int getVirtualHeight() {
+        int max = Integer.MIN_VALUE;
+        ArrayList<Point> bps = model.getLinePoints( layout );
+        for( Point p : bps )
+            max = Math.max( max, (int)p.getY() + 5 );
+        return max - getVirtualY();
     }
 }

+ 27 - 120
src/view/MainView.java

@@ -1,7 +1,6 @@
 package view;
 
 import java.awt.BorderLayout;
-import java.awt.Color;
 import java.awt.Dimension;
 import java.awt.Font;
 import java.awt.GridLayout;
@@ -9,6 +8,7 @@ import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.event.ComponentAdapter;
 import java.awt.event.ComponentEvent;
+import java.awt.event.ComponentListener;
 import java.awt.event.KeyEvent;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
@@ -29,8 +29,6 @@ import javax.swing.JSplitPane;
 import javax.swing.JTextArea;
 import javax.swing.JTextField;
 import javax.swing.JTree;
-import javax.swing.event.DocumentEvent;
-import javax.swing.event.DocumentListener;
 import javax.swing.filechooser.FileNameExtensionFilter;
 import javax.swing.tree.DefaultTreeModel;
 import javax.swing.tree.TreePath;
@@ -41,13 +39,14 @@ import animation.Action;
 import animation.AnimationController;
 import animation.PseudoCodeNode;
 import bk.BKNodePlacement;
+import bk.BKNodePlacement.State;
 import bk.LayoutType;
-import graph.InitializeNodePositions;
 import graph.LayeredGraphEdge;
 import graph.LayeredGraphNode;
 import graph.LayeredNode;
 import graph.io.Reader;
 import graph.io.Writer;
+import lib.SimpleNodePlacement;
 import lib.TextLayoutHelper;
 
 /**
@@ -167,6 +166,8 @@ public class MainView {
      */
     public MainView( LayeredGraphNode graph )
     {
+        RenderHelper.font = new Font("Monospaced", Font.PLAIN, 12);
+        graph.setColor( null, null );
         frameCounter++;
         this.graph = graph;
         controller = new AnimationController();
@@ -338,45 +339,12 @@ public class MainView {
         delayText.setBounds( 260, 10, 80, 20 );
         delay = new JTextField( String.valueOf(AnimationController.DEFAULT_DELAY) );
         delay.setBounds( 260, 30, 90, 20 );
-        delay.getDocument().addDocumentListener( new DocumentListener() {
-
+        delay.getDocument().addDocumentListener( new NumberDocumentListener( new NumberDocumentListener.Action() {
             @Override
-            public void insertUpdate(DocumentEvent e) {
-                try
-                {
-                    controller.setDelay( Integer.parseInt( delay.getText() ) );
-                    delay.setBackground( Color.WHITE );
-                } catch( Exception e1 )
-                {
-                    delay.setBackground( Color.RED );
-                }
+            public void action(int val) {
+                controller.setDelay( Integer.parseInt( delay.getText() ) );
             }
-
-            @Override
-            public void removeUpdate(DocumentEvent e) {
-                try
-                {
-                    controller.setDelay( Integer.parseInt( delay.getText() ) );
-                    delay.setBackground( Color.WHITE );
-                } catch( Exception e1 )
-                {
-                    delay.setBackground( Color.RED );
-                }
-            }
-
-            @Override
-            public void changedUpdate(DocumentEvent e) {
-                try
-                {
-                    controller.setDelay( Integer.parseInt( delay.getText() ) );
-                    delay.setBackground( Color.WHITE );
-                } catch( Exception e1 )
-                {
-                    delay.setBackground( Color.RED );
-                }
-            }
-            
-        });
+        }, delay ) );
         load = new NiceButton( "load" );
         load.setLocation( 260, 60 );
         load.setMnemonic( KeyEvent.VK_L );
@@ -393,7 +361,7 @@ public class MainView {
                 {
                     Reader r = new Reader( chooser.getSelectedFile().getAbsolutePath() );
                     LayeredGraphNode graph = r.readInputGraph();
-                    InitializeNodePositions.placeNodes( graph );
+                    SimpleNodePlacement.placeNodes( graph );
                     new MainView( graph );
                 }
             }
@@ -555,7 +523,7 @@ public class MainView {
         treeView.setBounds( 10,  110,  390, 380 );
         
         JTextArea debugText = new JTextArea();
-        debugText.setFont( new Font( "Monospaced", Font.PLAIN, 12 ) );
+        debugText.setFont( RenderHelper.font );
         debugText.setEditable( false );
         debugText.setBackground( RenderHelper.BACKGROUND_COLOR );
         debugText.setForeground( RenderHelper.FOREGROUND_COLOR );
@@ -575,47 +543,17 @@ public class MainView {
         pl.setLocation( 0, 0 );
         pl.setSize( frame.getSize() );
         NodeView topLeft = createNodeView( graph, LayoutType.TOP_BOTTOM_LEFT );
-        topLeft.addMouseMotionListener( new MouseAdapter() {
-            @Override
-            public void mouseMoved( MouseEvent e ) {
-                topLeft.setToolTipText( topLeft.updateTooltipText( e.getX(), e.getY() ) );
-            }
-        });
         NodeView topRight = createNodeView( graph, LayoutType.TOP_BOTTOM_RIGHT );
-        topRight.addMouseMotionListener( new MouseAdapter() {
-            @Override
-            public void mouseMoved( MouseEvent e ) {
-                topRight.setToolTipText( topRight.updateTooltipText( e.getX(), e.getY() ) );
-            }
-        });
         NodeView bottomLeft = createNodeView( graph, LayoutType.BOTTOM_TOP_LEFT );
-        bottomLeft.addMouseMotionListener( new MouseAdapter() {
-            @Override
-            public void mouseMoved( MouseEvent e ) {
-                bottomLeft.setToolTipText( bottomLeft.updateTooltipText( e.getX(), e.getY() ) );
-            }
-        });
         NodeView bottomRight = createNodeView( graph, LayoutType.BOTTOM_TOP_RIGHT );
-        bottomRight.addMouseMotionListener( new MouseAdapter() {
-            @Override
-            public void mouseMoved( MouseEvent e ) {
-                bottomRight.setToolTipText( bottomRight.updateTooltipText( e.getX(), e.getY() ) );
-            }
-        });
         pl.add( topLeft );
         pl.add( topRight );
         pl.add( bottomLeft );
         pl.add( bottomRight );
-        layne.add( pl, 1 );
         NodeView combined = createNodeView( graph, LayoutType.COMBINED );
-        combined.addMouseMotionListener( new MouseAdapter() {
-            @Override
-            public void mouseMoved( MouseEvent e ) {
-                combined.setToolTipText( combined.updateTooltipText( e.getX(), e.getY() ) );
-            }
-        });
         combined.setSize( 500, 500 );
         layne.add( combined, 0 );
+        layne.add( pl, 1 );
         
         JSplitPane spane = new JSplitPane( JSplitPane.HORIZONTAL_SPLIT );
         spane.setLeftComponent( layne );
@@ -624,6 +562,7 @@ public class MainView {
         JPanel menue = new JPanel();
         menue.setLayout( null );
         menue.setPreferredSize( new Dimension( 410, 500 ) );
+        menue.setMinimumSize( new Dimension( 410, 300 ) );
         menue.add( stepForward );
         menue.add( stepForwardInto );
         menue.add( stepForwardOut );
@@ -664,20 +603,25 @@ public class MainView {
         frame.revalidate();
         frame.repaint();
 
+        State old = algorithm.getAlgorithmState();
+        
         frame.addComponentListener(new ComponentAdapter()
         {
             public void componentResized(ComponentEvent evt) {
                 menue.setSize( menue.getWidth(), layne.getHeight() );
                 spane2.setSize( menue.getWidth() - 20, menue.getHeight() - 120 );
-                if( graph.getColor( LayoutType.COMBINED ) == null )
+                if( graph.getColor( null ) == null )
                 {
                     grout.setHgap( 10 );
                     grout.setVgap( 10 );
+                    combined.setVisible( false );
                 }
                 else
                 {
                     grout.setHgap( layne.getWidth() / 3 );
                     grout.setVgap( layne.getHeight() / 3 );
+                    combined.setVisible( true );
+                    combined.doLayout();
                 }
                 combined.setSize( layne.getWidth() / 3, layne.getHeight() / 3 );
                 combined.setLocation( layne.getWidth() / 3, layne.getHeight() / 3 );
@@ -685,7 +629,7 @@ public class MainView {
                 debugText.setText( algorithm.getDebugString().trim() );
                 layne.remove( pl );
                 layne.add( pl, 1 );
-                if( optionsDialog != null && optionsDialog.getLayerDisplayOption() == 1 )
+                if( optionsDialog != null && optionsDialog.getLayerDisplayOption() == 1 && old != algorithm.getAlgorithmState() )
                 {
                     pl.remove( topLeft );
                     pl.remove( topRight );
@@ -733,50 +677,13 @@ public class MainView {
 
             @Override
             public void actionPerformed(ActionEvent e) {
-                pl.remove( topLeft );
-                pl.remove( topRight );
-                pl.remove( bottomLeft );
-                pl.remove( bottomRight );
-                pl.remove( combined );
-                layne.remove( combined );
                 controller.setStepOption( optionsDialog.getRunStepsOption() );
-                if( optionsDialog.getLayerDisplayOption() == 0 )
-                {
-                    pl.setLayout( grout );
-                    pl.add( topLeft );
-                    pl.add( topRight );
-                    pl.add( bottomLeft );
-                    pl.add( bottomRight );
-                    layne.add( combined, 0 );
-                    pl.revalidate();
-                }
-                else
-                {
-                    pl.setLayout( new BorderLayout() );
-                    switch( algorithm.getAlgorithmState() )
-                    {
-                    case CONFLICTS:
-                        pl.add( topLeft );
-                        break;
-                    case LAYOUT1:
-                        pl.add( topLeft );
-                        break;
-                    case LAYOUT2:
-                        pl.add( topRight );
-                        break;
-                    case LAYOUT3:
-                        pl.add( bottomLeft );
-                        break;
-                    case LAYOUT4:
-                        pl.add( bottomRight );
-                        break;
-                    case COMBINE:
-                        pl.add( combined );
-                        break;
-                    }
-                    pl.revalidate();
-                }
-                frame.repaint();
+                RenderHelper.font = new Font( "Monospaced", Font.PLAIN, optionsDialog.getFontSize() );
+                debugText.setFont( RenderHelper.font );
+                pseudoTree.setFont( RenderHelper.font );
+                pseudoTree.setRowHeight( (int)(15.0/12 * optionsDialog.getFontSize() ) );
+                for( ComponentListener l : frame.getComponentListeners() )
+                    l.componentResized( new ComponentEvent( frame, 0 ) );
             }
             
         });
@@ -786,7 +693,7 @@ public class MainView {
 
     private NodeView createNodeView( LayeredGraphNode gNode, LayoutType lt )
     {
-        NodeView graphView = new NodeView( gNode, lt );
+        NodeView graphView = new NodeView( gNode, lt, frame );
         ((LayeredNode)gNode).setView( graphView, lt );
         graphView.setLayout( null );
         graphView.setOpaque( true );

+ 208 - 87
src/view/NodeView.java

@@ -3,13 +3,17 @@ package view;
 import java.awt.BasicStroke;
 import java.awt.Color;
 import java.awt.Component;
-import java.awt.Dimension;
 import java.awt.Graphics;
 import java.awt.Graphics2D;
-import java.awt.Point;
+import java.awt.event.ComponentEvent;
+import java.awt.event.ComponentListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
 
 import javax.swing.BorderFactory;
+import javax.swing.JFrame;
 import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
 import javax.swing.border.Border;
 
 import bk.LayoutType;
@@ -20,125 +24,195 @@ import graph.LayeredGraphNode;
  * @author kolja
  *
  */
-public class NodeView extends JPanel {
+public class NodeView extends JPanel implements AnnimatedView, MouseListener {
     private static final long serialVersionUID = 1L;
     private LayeredGraphNode model;
     private LayoutType layout;
+    private JFrame mainView;
+    private int originalWidth;
+    private int originalHeight;
 
-    public NodeView( LayeredGraphNode model, LayoutType lt ) {
+    public NodeView( LayeredGraphNode model, LayoutType lt, JFrame mv ) {
+        mainView = mv;
         this.model = model;
         layout = lt;
-        setSize( (int)model.getWidth( layout ), (int)model.getHeight( layout ) );
+        addMouseListener( this );
+        originalWidth = (int)model.getWidth( lt );
+        originalHeight = (int)model.getHeight( lt );
     }
 
-    @Override
-    public Point getLocation()
-    {
-        return new Point( (int)model.getX( layout ), (int)model.getY( layout ) );
-    }
-    
-    @Override
-    public Dimension getPreferredSize()
+    private synchronized void update()
     {
-        return new Dimension( (int)model.getWidth( layout ), (int)model.getHeight( layout ) );
+        SwingUtilities.invokeLater(new Runnable() {
+            public void run() {
+                for( ComponentListener l : mainView.getComponentListeners() )
+                {
+                    l.componentResized( new ComponentEvent(mainView, 0) );
+                }
+            }
+        });
     }
     
     public int getScaledX( int x )
     {
-        double width_scale = super.getWidth() / model.getWidth( layout );
-        double height_scale = super.getHeight() / model.getHeight( layout );
-        double scale = Math.min( width_scale, height_scale);
+        double scale1 = Math.min( (getWidth()) / (double)getWidthOfNeededArea(), (getHeight()) / (double)getHeightOfNeededArea());
+        double scale = Math.min( (getWidth()-50*scale1) / (double)getWidthOfNeededArea(), (getHeight()-50*scale1) / (double)getHeightOfNeededArea());
         x *= scale;
-        if( scale < width_scale )
-            x += (super.getWidth() - (model.getWidth( layout ) * scale )) / 2;
         return x;
     }
     
     public int getScaledY( int y )
     {
-        double width_scale = super.getWidth() / model.getWidth( layout );
-        double height_scale = super.getHeight() / model.getHeight( layout );
-        double scale = Math.min( width_scale, height_scale);
+        double scale1 = Math.min( (getWidth()) / (double)getWidthOfNeededArea(), (getHeight()) / (double)getHeightOfNeededArea());
+        double scale = Math.min( (getWidth()-50*scale1) / (double)getWidthOfNeededArea(), (getHeight()-50*scale1) / (double)getHeightOfNeededArea());
         y *= scale;
-        if( scale < height_scale )
-            y += (super.getHeight() - (model.getHeight( layout ) * scale )) / 2;
         return y;
     }
     
-    public String updateTooltipText( int mx, int my ) {
+    public void updateTooltipText() {
+        if( layout != LayoutType.COMBINED )
+        {
+            setToolTipText( "<html>Name: " + model.toString() +
+                            "<br>Root: " + model.getRoot( layout ).toString() +
+                            "<br>Shink: " + model.getSink( layout ).toString() +
+                            "<br>Shift: " + model.getShift( layout ) + "</html>" );
+        }
+        else
+        {
+            setToolTipText( "<html>Name: " + model.toString() +
+                            "<br>result X: " + model.getX( LayoutType.COMBINED ) +
+                            "<br>other layouts: [" + model.getX( LayoutType.TOP_BOTTOM_LEFT ) + "," + model.getX( LayoutType.TOP_BOTTOM_RIGHT ) + ","
+                                + model.getX( LayoutType.BOTTOM_TOP_LEFT ) + "," + model.getX( LayoutType.BOTTOM_TOP_RIGHT ) + "]</html>" );
+        }
+        for( Component c : getComponents() )
+        {
+            if( !(c instanceof NodeView) )
+                continue;
+            ((NodeView)c).updateTooltipText();
+        }
+    }
+
+    public int getXOffset()
+    {
         int x = 0;
+        double scale1 = Math.min( (getWidth()) / (double)getWidthOfNeededArea(), (getHeight()) / (double)getHeightOfNeededArea());
+        double scale = Math.min( (getWidth()-50*scale1) / (double)getWidthOfNeededArea(), (getHeight()-50*scale1) / (double)getHeightOfNeededArea());
+        x += (getWidth()-50) / 2 - (getWidthOfNeededArea() * scale ) / 2 + 25;
+        return x;
+    }
+
+    public int getYOffset()
+    {
         int y = 0;
-        double scaleW = Math.min( (double)super.getWidth() / (int)model.getWidth( layout ), (double)super.getHeight() / (int)model.getHeight( layout ));
-        double scaleH = scaleW;
-        int width = (int)(super.getWidth() / scaleW);
-        if( scaleW == (double)super.getWidth() / (int)model.getWidth( layout ) )
-            y += (super.getHeight() - (model.getHeight( layout ) * scaleH )) / scaleH / 2;
-        if( scaleH == (double)super.getHeight() / (int)model.getHeight( layout ) )
-            x += (super.getWidth() - (model.getWidth( layout ) * scaleW )) / scaleW / 2;
-        if( model.isDummyNode() )
+        double scale1 = Math.min( (getWidth()) / (double)getWidthOfNeededArea(), (getHeight()) / (double)getHeightOfNeededArea());
+        double scale = Math.min( (getWidth()-50*scale1) / (double)getWidthOfNeededArea(), (getHeight()-50*scale1) / (double)getHeightOfNeededArea());
+        y += (getHeight()-50) / 2 - (getHeightOfNeededArea() * scale ) / 2 + 25;
+        return y;
+    }
+
+    public int getOriginalWidth() {
+        return originalWidth;
+    }
+
+    public int getOriginalHeight() {
+        return originalHeight;
+    }
+
+    private int getWidthOfNeededArea() {
+        int max = 0;
+        double min = Double.POSITIVE_INFINITY;
+        for( Component c : getComponents() )
         {
-            scaleW *= 1 / 4.0;
-            x += width / (3/4.0);
+            if( c instanceof NodeView )
+            {
+                max = Math.max( max, ((NodeView)c).getVirtualX() + ((NodeView)c).getOriginalWidth() );
+                min = Math.min( ((AnnimatedView)c).getVirtualX(), min);
+            }
         }
-        double minX = Double.POSITIVE_INFINITY;
+        return max - (int)min;
+    }
+
+    private int getHeightOfNeededArea() {
+        int max = 0;
+        double min = Double.POSITIVE_INFINITY;
         for( Component c : getComponents() )
         {
-            minX = Math.min( c.getLocation().x, minX);
+            if( c instanceof NodeView )
+            {
+                max = Math.max( max, ((NodeView)c).getVirtualY() + ((NodeView)c).getOriginalHeight() );
+                min = Math.min( ((AnnimatedView)c).getVirtualY(), min);
+            }
         }
+        return max - (int)min;
+    }
+
+    @Override
+    public void doLayout() {
+        double minX = Double.POSITIVE_INFINITY;
         for( Component c : getComponents() )
         {
-            int nx = (int)(mx / scaleW) - (c.getLocation().x + 25 - (int)minX + x);
-            int ny = (int)(my / scaleH) - (c.getLocation().y + 25 + y);
-            int width1 = Math.min( (int)model.getWidth( layout ) - 25, c.getPreferredSize().width + 25 );
-            int height1 = Math.min( (int)model.getHeight( layout ) - 25, c.getPreferredSize().height + 25 );
-            if( nx < width1 && ny < height1 && nx > 0 && ny > 0 && c instanceof NodeView )
-                return ((NodeView)c).updateTooltipText( nx, ny );
+            if( !(c instanceof AnnimatedView) )
+                continue;
+            minX = Math.min( ((AnnimatedView)c).getVirtualX(), minX);
         }
-        if( layout != LayoutType.COMBINED )
+        int x = 0;
+        int y = 0;
+        double scale1 = Math.min( (getWidth()) / (double)getWidthOfNeededArea(), (getHeight()) / (double)getHeightOfNeededArea());
+        double scale = Math.min( (getWidth()-50*scale1) / (double)getWidthOfNeededArea(), (getHeight()-50*scale1) / (double)getHeightOfNeededArea());
+        x += (getWidth()) / 2 - (getWidthOfNeededArea() * scale ) / 2;
+        y += (getHeight()) / 2 - (getHeightOfNeededArea() * scale ) / 2;
+        for( Component c : getComponents() )
         {
-            return "<html>Name: " + model.toString() + 
-                    "<br>Root: " + model.getRoot( layout ).toString() +
-                    "<br>Shink: " + model.getSink( layout ).toString() + 
-                    "<br>Shift: " + model.getShift( layout ) + "</html>";
+            if( !(c instanceof AnnimatedView) )
+                continue;
+            AnnimatedView view = (AnnimatedView)c;
+            c.setLocation( getScaledX( view.getVirtualX() - (int)minX ) + x, getScaledY( view.getVirtualY() ) + y);
+            if( c instanceof NodeView )
+                c.setSize( getScaledX( ((NodeView)c).getOriginalWidth() ), getScaledY( ((NodeView)c).getOriginalHeight() ) );
+            else
+                c.setSize( getScaledX( view.getVirtualWidth() ), getScaledY( view.getVirtualHeight() ) );
+            c.doLayout();
         }
-        return "Name: " + model.toString(); 
     }
     
     @Override
     public void paint( Graphics g )
     {
-        if( layout == LayoutType.COMBINED && model.getColor( layout ) == null )
+        if( layout == LayoutType.COMBINED && model.getColor( null ) == null )
             return;
-        double width_scale = super.getWidth() / model.getWidth( layout );
-        double height_scale = super.getHeight() / model.getHeight( layout );
-        double scale = Math.min( width_scale, height_scale);
-        ((Graphics2D)g).scale( scale, scale );
-        int x = 0;
-        int y = 0;
-        int width = (int)(super.getWidth() / scale);
-        int height = (int)(super.getHeight() / scale);
-        if( scale < height_scale )
-        	y += (super.getHeight() - (model.getHeight( layout ) * scale )) / scale / 2;
-        if( scale < width_scale )
-        	x += (super.getWidth() - (model.getWidth( layout ) * scale )) / scale / 2;
-        if( model.isDummyNode() )
-        {
-            ((Graphics2D)g).scale( 1 / 4.0, 1 );
-            x += width / (3/4.0);
-        }
-        paintComponent( g.create( x, y, width, height ) );
+        updateTooltipText();
+        paintComponent( g );
         double minX = Double.POSITIVE_INFINITY;
         for( Component c : getComponents() )
         {
-            minX = Math.min( c.getLocation().x, minX);
+            if( !(c instanceof AnnimatedView) )
+                continue;
+            minX = Math.min( ((AnnimatedView)c).getVirtualX(), minX);
         }
+        int x = 0;
+        double scale1 = Math.min( (getWidth()) / (double)getWidthOfNeededArea(), (getHeight()) / (double)getHeightOfNeededArea());
+        double scale = Math.min( (getWidth()-50*scale1) / (double)getWidthOfNeededArea(), (getHeight()-50*scale1) / (double)getHeightOfNeededArea());
+        x += (getWidth()-50) / 2 - (getWidthOfNeededArea() * scale ) / 2 + 25;
         for( Component c : getComponents() )
         {
-            c.paint( g.create( 
-                    c.getLocation().x + 25 - (int)minX + x, 
-                    c.getLocation().y + 25 + y, 
-                    Math.min( (int)model.getWidth( layout ) - 25, c.getPreferredSize().width + Math.abs(c.getLocation().x) + 25 ), 
-                    Math.min( (int)model.getHeight( layout ) - 25, c.getPreferredSize().height + Math.abs(c.getLocation().y) + 25 ) ) );
+            if( c instanceof NodeView )
+            {
+                if( layout != LayoutType.COMBINED )
+                {
+                    NodeView v = (NodeView)c;
+                    v.renderClass( g.create( getScaledX( v.getPlainVirtualX() - (int)minX - 12 ) + x, c.getY() - getScaledY(12), getScaledX( v.getPlainVirtualWidth() + 22 ), c.getHeight() + getScaledY(22) ) );
+                }
+            }
+            c.paint( g.create( c.getX(), c.getY(), c.getWidth(), c.getHeight() ) );
+        }
+    }
+
+    public void renderClass( Graphics g ) {
+        g.setColor( model.getRoot( layout ).getClassColor( layout ) );
+        if( model.getContainedNodes().size() == 0 && model.getRoot( layout ).getClassColor( layout ) != Color.LIGHT_GRAY )
+        {
+            g.setColor( new Color( (g.getColor().getRed() + 500) / 3, (g.getColor().getGreen() + 500) / 3, (g.getColor().getBlue() + 500) / 3 ) );
+            g.fillRect( 0, 0, g.getClipBounds().width, g.getClipBounds().height );
         }
     }
 
@@ -151,9 +225,9 @@ public class NodeView extends JPanel {
         if( model.getContainedNodes().size() == 0 )
         {
             if( model.getRoot( layout ) == model )
-                g2.fillOval( 0, 0, (int)model.getWidth( layout )-1, (int)model.getHeight( layout )-1 );
+                g2.fillOval( 0, 0, getWidth(), getHeight() );
             else
-                g2.fillRect( 0, 0, (int)model.getWidth( layout )-1, (int)model.getHeight( layout )-1 );
+                g2.fillRect( 0, 0, getWidth(), getHeight() );
         }
         boolean selected = model.isSelected( layout );
         if( selected )
@@ -161,21 +235,68 @@ public class NodeView extends JPanel {
             g.setColor( Color.BLACK );
             if( model.getContainedNodes().size() > 0 )
                 g.setColor( Color.GRAY );
-            g.fillRect( 0, 0, (int)model.getWidth( layout )-1, (int)model.getHeight( layout )-1 );
+            g.fillRect( 0, 0, getWidth(), getHeight() );
         }
         Border linebor = BorderFactory.createLineBorder(model.getColor( layout ), 5);
         if( model.getRoot( layout ) != model || model.getContainedNodes().size() != 0  )
-            linebor.paintBorder( this, g2, 0, 0, (int)model.getWidth( layout )-1, (int)model.getHeight( layout )-1 );
-        if( layout != LayoutType.COMBINED )
+            linebor.paintBorder( this, g2, 0, 0, getWidth(), getHeight() );
+        if( model.isMouseOver() && model.getContainedNodes().size() == 0 )
         {
-            g.setColor( model.getRoot( layout ).getClassColor( layout ) );
-            if( model.getContainedNodes().size() == 0 )
-            {
-                if( selected )
-                    g.fillOval( (int)model.getWidth( layout ) / 2 - (int)model.getWidth( layout ) / 5, (int)model.getHeight( layout ) / 2 - (int)model.getHeight( layout ) / 5, (int)model.getWidth( layout ) / 5 * 2, (int)model.getHeight( layout ) / 5 * 2 );
-                else
-                    g.fillOval( (int)model.getWidth( layout ) / 2 - (int)model.getWidth( layout ) / 3, (int)model.getHeight( layout ) / 2 - (int)model.getHeight( layout ) / 3, (int)model.getWidth( layout ) / 3 * 2, (int)model.getHeight( layout ) / 3 * 2 );
-            }
+            g.setColor( Color.WHITE );
+            g.fillOval( getWidth() / 4, getHeight() / 4, getWidth() / 2, getHeight() / 2 );
         }
     }
+
+    public int getPlainVirtualX() {
+        return (int)model.getX( layout );
+    }
+
+    @Override
+    public int getVirtualX() {
+        if( model.isDummyNode() )
+            return (int)(model.getX( layout ) + getVirtualWidth() / (3/4.0) );
+        return (int)model.getX( layout );
+    }
+
+    @Override
+    public int getVirtualY() {
+        return (int)model.getY( layout );
+    }
+
+    @Override
+    public int getVirtualWidth() {
+        if( model.isDummyNode() )
+            return (int)(model.getWidth( layout ) / 3);
+        return (int)model.getWidth( layout );
+    }
+
+    public int getPlainVirtualWidth() {
+        return (int)model.getWidth( layout );
+    }
+
+    @Override
+    public int getVirtualHeight() {
+        return (int)model.getHeight( layout );
+    }
+
+    @Override
+    public void mouseClicked(MouseEvent e) {}
+
+    @Override
+    public void mousePressed(MouseEvent e) {}
+
+    @Override
+    public void mouseReleased(MouseEvent e) {}
+
+    @Override
+    public void mouseEntered(MouseEvent e) {
+        model.setMouseOver( true );
+        update();
+    }
+
+    @Override
+    public void mouseExited(MouseEvent e) {
+        model.setMouseOver( false );
+        update();
+    }
 }

+ 59 - 0
src/view/NumberDocumentListener.java

@@ -0,0 +1,59 @@
+package view;
+
+import java.awt.Color;
+
+import javax.swing.JTextField;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+
+public class NumberDocumentListener implements DocumentListener {
+    
+    public interface Action{
+        public void action( int val );
+    }
+    
+    private Action action;
+    private JTextField field;
+    
+    public NumberDocumentListener( Action callback, JTextField textField )
+    {
+        action = callback;
+        field = textField;
+    }
+    
+    @Override
+    public void insertUpdate(DocumentEvent e) {
+        try
+        {
+            action.action( Integer.parseInt( field.getText() ) );
+            field.setBackground( Color.WHITE );
+        } catch( Exception e1 )
+        {
+            field.setBackground( Color.RED );
+        }
+    }
+
+    @Override
+    public void removeUpdate(DocumentEvent e) {
+        try
+        {
+            action.action( Integer.parseInt( field.getText() ) );
+            field.setBackground( Color.WHITE );
+        } catch( Exception e1 )
+        {
+            field.setBackground( Color.RED );
+        }
+    }
+
+    @Override
+    public void changedUpdate(DocumentEvent e) {
+        try
+        {
+            action.action( Integer.parseInt( field.getText() ) );
+            field.setBackground( Color.WHITE );
+        } catch( Exception e1 )
+        {
+            field.setBackground( Color.RED );
+        }
+    }
+}

+ 22 - 2
src/view/OptionsDialog.java

@@ -10,6 +10,7 @@ import javax.swing.JButton;
 import javax.swing.JComboBox;
 import javax.swing.JDialog;
 import javax.swing.JLabel;
+import javax.swing.JTextField;
 
 public class OptionsDialog extends JDialog {
 
@@ -19,11 +20,14 @@ public class OptionsDialog extends JDialog {
     private JComboBox<String> display;
     private JButton speichern;
     private JComboBox<String> run;
+    private JTextField fSize;
+    private int tempFSize = 12;
     
     private static class Options
     {
         public int layoutDisplaySelection = 0;
         public int runStepsSelection = 0;
+        public int fontSize = 12;
     }
     
     private Options options;
@@ -31,7 +35,7 @@ public class OptionsDialog extends JDialog {
     OptionsDialog()
     {
         setTitle( "Preferences" );
-        setLayout( new GridLayout( 3, 2 ) );
+        setLayout( new GridLayout( 4, 2 ) );
         add( new JLabel( "Display layouts:" ) );
         String[] displayValues = { "All", "Only current" };
         display = new JComboBox<String>( displayValues );
@@ -40,6 +44,15 @@ public class OptionsDialog extends JDialog {
         String[] runValues = { "All", "Only expanded" };
         run = new JComboBox<String>( runValues );
         add( run );
+        add( new JLabel( "Font size:" ) );
+        fSize = new JTextField( "12" );
+        add( fSize );
+        fSize.getDocument().addDocumentListener( new NumberDocumentListener( new NumberDocumentListener.Action() {
+            @Override
+            public void action(int val) {
+                tempFSize = val;
+            }
+        }, fSize ) );
         add( new JLabel() );
         speichern = new JButton( "Ok" );
         speichern.addActionListener( new ActionListener() {
@@ -48,9 +61,11 @@ public class OptionsDialog extends JDialog {
             public void actionPerformed(ActionEvent e) {
                 try {
                     boolean change = options.layoutDisplaySelection != display.getSelectedIndex() ||
-                            options.runStepsSelection != run.getSelectedIndex();
+                            options.runStepsSelection != run.getSelectedIndex() ||
+                            tempFSize != options.fontSize;
                     options.layoutDisplaySelection = display.getSelectedIndex();
                     options.runStepsSelection = run.getSelectedIndex();
+                    options.fontSize = tempFSize;
                     if( change )
                     {
                         for( ActionListener l : listeners )
@@ -83,6 +98,11 @@ public class OptionsDialog extends JDialog {
         return options.runStepsSelection;
     }
     
+    public int getFontSize()
+    {
+        return options.fontSize;
+    }
+    
     public void addActionListener( ActionListener al )
     {
         listeners.add( al );

+ 3 - 3
src/view/PseudoCodeLines.java

@@ -128,15 +128,15 @@ public class PseudoCodeLines extends JComponent implements MouseListener{
             int number = getLineNumber( node );
             Rectangle rect = tree.getRowBounds( i );
             String text = String.valueOf( number );
-            int yPosition = rect.y + rect.height - VERTICAL_PADDING;
+            int yPosition = rect.y + rect.height / 2 + 4;
             if( !node.hasSelectedSubnode() && node.isSelected() )
-                g.drawImage( currentLine.getImage(), HORIZONTAL_PADDING, rect.y + VERTICAL_PADDING - 5, 20, 20, null );
+                g.drawImage( currentLine.getImage(), HORIZONTAL_PADDING, rect.y + rect.height / 2 - 10, 20, 20, null );
             g2d.drawString( text, HORIZONTAL_PADDING * 2 + 20, yPosition );
             if( node.hasBreakPoint() )
             {
                 Color c = g.getColor();
                 g.setColor( new Color (0xe7887a) );
-                g.fillOval( getWidth() - HORIZONTAL_PADDING - 10, rect.y + VERTICAL_PADDING, 10, 10 );
+                g.fillOval( getWidth() - HORIZONTAL_PADDING - 10, rect.y + rect.height / 2 - 5, 10, 10 );
                 g.setColor( c );
             }
         }

+ 1 - 1
src/view/PseudoCodeRenderer.java

@@ -48,7 +48,7 @@ public class PseudoCodeRenderer extends DefaultTreeCellRenderer {
     
     @Override
     public Font getFont() {
-        return new Font("Monospaced", Font.PLAIN, 12);
+        return RenderHelper.font;
     }
     
     @Override

+ 2 - 2
src/view/RandomGraphDialog.java

@@ -16,9 +16,9 @@ import javax.swing.JLabel;
 import javax.swing.JOptionPane;
 import javax.swing.JTextField;
 
-import graph.InitializeNodePositions;
 import graph.LayeredGraphNode;
 import graph.RandomGraphGenerator;
+import lib.SimpleNodePlacement;
 import lib.SweepCrossingMinimizer;
 
 public class RandomGraphDialog extends JDialog {
@@ -308,7 +308,7 @@ public class RandomGraphDialog extends JDialog {
                         SweepCrossingMinimizer cminzer = new SweepCrossingMinimizer();
                         for( int i = 0; i < 10; i++ )
                           cminzer.minimizeCrossings( graph );
-                        InitializeNodePositions.placeNodes( graph );
+                        SimpleNodePlacement.placeNodes( graph );
                         new MainView( graph );
                         setVisible( false );
                     } catch( Exception e1 )

+ 2 - 0
src/view/RenderHelper.java

@@ -1,6 +1,7 @@
 package view;
 
 import java.awt.Color;
+import java.awt.Font;
 import java.awt.Point;
 import java.awt.Polygon;
 import java.awt.Shape;
@@ -16,6 +17,7 @@ public class RenderHelper {
     public static final Color FOREGROUND_COLOR = new Color(0xa9b7c6);
     public static final Color BREAKPOINT_COLOR = new Color(0x6c2020);
     public static final Color CURRENT_LINE_COLOR = new Color(0x606366);
+    public static Font font;
   
     /**
      * creates an arrow shape to draw it, for example as part of an edge.