1   /****************************************************************************
2    * This demo file is part of yFiles for Java 2.14.
3    * Copyright (c) 2000-2017 by yWorks GmbH, Vor dem Kreuzberg 28,
4    * 72070 Tuebingen, Germany. All rights reserved.
5    * 
6    * yFiles demo files exhibit yFiles for Java functionalities. Any redistribution
7    * of demo files in source code or binary form, with or without
8    * modification, is not permitted.
9    * 
10   * Owners of a valid software license for a yFiles for Java version that this
11   * demo is shipped with are allowed to use the demo source code as basis
12   * for their own yFiles for Java powered applications. Use of such programs is
13   * governed by the rights and conditions as set out in the yFiles for Java
14   * license agreement.
15   * 
16   * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED
17   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18   * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
19   * NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
21   * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22   * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23   * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24   * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26   *
27   ***************************************************************************/
28  package demo.view.isometry;
29  
30  import y.view.GenericNodeRealizer;
31  import y.view.NodeRealizer;
32  import y.view.hierarchy.GroupFeature;
33  
34  import java.awt.BasicStroke;
35  import java.awt.Color;
36  import java.awt.Font;
37  import java.awt.Graphics2D;
38  import java.awt.geom.AffineTransform;
39  import java.awt.geom.Line2D;
40  
41  /**
42   * A painter that paints group nodes in an isometric fashion.
43   */
44  public class IsometryGroupPainter implements GenericNodeRealizer.Painter, GenericNodeRealizer.ContainsTest {
45    static final int ICON_GAP = 2;
46    static final int ICON_HEIGHT = 16;
47    static final int ICON_WIDTH = 16;
48  
49    private final IsometryNodePainter painterDelegate;
50  
51    public IsometryGroupPainter(IsometryNodePainter painter) {
52      painterDelegate = painter;
53    }
54  
55    public void paint(NodeRealizer context, Graphics2D graphics) {
56      // delegate node painting
57      painterDelegate.paint(context, graphics);
58  
59      // paint group state icon
60      // save the original graphics context
61      final AffineTransform oldTransform = graphics.getTransform();
62      final Font oldFont = graphics.getFont();
63      final Color oldColor = graphics.getColor();
64  
65      // calculate the corners of the node in the view space.
66      final double[] corners = new double[16];
67      final IsometryData isometryData = IsometryRealizerFactory.getIsometryData(context);
68      isometryData.calculateCorners(corners);
69      IsometryData.moveTo(context.getX(), context.getY(), corners);
70  
71      // the lower corner is the anchor of the label.
72      final double anchorX = corners[IsometryData.C3_X];
73      final double anchorY = corners[IsometryData.C3_Y];
74  
75      // set the transformation from the layout space into the view space on the graphics context.
76      graphics.translate(anchorX, anchorY);
77      graphics.transform(new AffineTransform(
78          new double[]{
79              IsometryData.M_TO_VIEW_11,
80              IsometryData.M_TO_VIEW_21,
81              IsometryData.M_TO_VIEW_12,
82              IsometryData.M_TO_VIEW_22}));
83      graphics.translate(-anchorX, -anchorY);
84  
85      // determine position of the icon
86      final int x = (int) anchorX + ICON_GAP;
87      final int y = (int) (anchorY - ICON_HEIGHT - ICON_GAP);
88  
89      // paint icon border
90      graphics.setStroke(new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER));
91      graphics.setColor(Color.WHITE);
92      graphics.fillRect(x, y, ICON_WIDTH, ICON_HEIGHT);
93      graphics.setColor(Color.BLACK);
94      graphics.drawRect(x, y, ICON_WIDTH, ICON_HEIGHT);
95  
96      // paint "+" (folder) or "-" (group)
97      final Line2D line = new Line2D.Double(x + ICON_WIDTH * 0.25, y + ICON_HEIGHT * 0.5,
98          x + ICON_WIDTH * 0.75, y + ICON_HEIGHT * 0.5);
99      graphics.draw(line);
100     if (context instanceof GroupFeature && ((GroupFeature) context).isGroupClosed()) {
101       line.setLine(x + ICON_WIDTH * 0.5, y + ICON_HEIGHT * 0.25,
102           x + ICON_WIDTH * 0.5, y + ICON_HEIGHT * 0.75);
103       graphics.draw(line);
104     }
105 
106     // Restore the original graphics context.
107     graphics.setTransform(oldTransform);
108     graphics.setFont(oldFont);
109     graphics.setColor(oldColor);
110   }
111 
112   public void paintSloppy(NodeRealizer context, Graphics2D graphics) {
113     painterDelegate.paintSloppy(context, graphics);
114   }
115 
116   public boolean contains(NodeRealizer context, double x, double y) {
117     return painterDelegate.contains(context, x, y);
118   }
119 
120   /**
121    * Checks whether or not the given coordinates lie within the group state icon.
122    */
123   public static boolean hitsGroupStateIcon(NodeRealizer context, double x, double y) {
124     // calculate the corners of the node in the view space.
125     final double[] corners = new double[16];
126     final IsometryData isometryData = IsometryRealizerFactory.getIsometryData(context);
127     isometryData.calculateCorners(corners);
128     IsometryData.moveTo(context.getX(), context.getY(), corners);
129 
130     // the lower corner is the anchor of the label.
131     final double anchorX = corners[IsometryData.C3_X];
132     final double anchorY = corners[IsometryData.C3_Y];
133 
134     // move the given mouse coordinates by anchor and transform them into layout space
135     // that way, the hit test can use a non-transformed rectangle
136     final double mouseX = IsometryData.toLayoutX(x - anchorX, y - anchorY);
137     final double mouseY = IsometryData.toLayoutY(x - anchorX, y - anchorY);
138 
139     // return whether or not the mouse is located in the icons rectangle
140     return mouseX > ICON_GAP && mouseX < ICON_WIDTH + ICON_GAP
141         && mouseY < -ICON_GAP && mouseY > -ICON_HEIGHT - ICON_GAP;
142   }
143 }
144