DrawingPanel()   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
dl 0
loc 2
rs 10
c 0
b 0
f 0
1
package org.gannacademy.cdf.graphics.ui;
2
3
import org.gannacademy.cdf.graphics.Drawable;
4
5
import javax.imageio.ImageIO;
6
import javax.swing.*;
7
import java.awt.*;
8
import java.awt.image.BufferedImage;
9
import java.io.File;
10
import java.io.IOException;
11
import java.util.Stack;
12
13
/**
14
 * <p>A drawing panel receives and displays all drawing instructions</p>
15
 *
16
 * <p>In the default configuration (in which {@link AppWindow} is extended to construct a drawing), the drawing panel is
17
 * contained by an {@code AppWindow} object &mdash; an {@code AppWindow} has-a {@code DrawingPanel}, in the parlance of
18
 * the Advanced Placement exam, and a {@code DrawingPanel} has-many {@code Drawable} components.</p>
19
 *
20
 * @author <a href="https://github.com/gann-cdf/graphics/issues" target="_blank">Seth Battis</a>
21
 */
22
public class DrawingPanel extends JPanel {
23
24
  /**
25
   * {@value #DEFAULT_WIDTH} pixels
26
   */
27
  public static final int DEFAULT_WIDTH = 600;
28
29
  /**
30
   * {@value #DEFAULT_HEIGHT} pixels
31
   */
32
  public static final int DEFAULT_HEIGHT = 400;
33
34
  /**
35
   * {@link Color#WHITE}
36
   */
37
  public static final Color DEFAULT_BACKGROUND = Color.WHITE;
38
39
  /**
40
   * {@value #DEFAULT_IMAGE_FORMAT}
41
   */
42
  public static final String DEFAULT_IMAGE_FORMAT = "PNG";
43
44
  /*
45
   * TODO Not 100% confident that the stack is the right structure here -- it would be nice to let someone reorder the
46
   * components to effectively set their Z-depth (which could technically be done with a stack, but is not really in the
47
   * spirit of it all)
48
   */
49
  private Stack<Drawable> components;
50
51
  /**
52
   * Construct a drawing panel of default dimensions and background color
53
   */
54
  public DrawingPanel() {
55
    this(new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT), DEFAULT_BACKGROUND);
56
  }
57
58
  /**
59
   * <p>Construct a drawing panel with custom arguments</p>
60
   *
61
   * <p>Of potential interest: while setting a background color that is transparent reveals the light gray color of of
62
   * the enclosing {@link JFrame} underneath the drawing panel, it will save as a transparent PNG using
63
   * {@link #saveAs(String)}.</p>
64
   *
65
   * @param dimension  in pixels
66
   * @param background color
67
   */
68
  public DrawingPanel(Dimension dimension, Color background) {
69
    super();
70
    setPreferredSize(dimension);
71
    setBackground(background);
72
    components = new Stack<>();
73
  }
74
75
  /**
76
   * <p>Add a drawable component to the drawing panel</p>
77
   *
78
   * <p>While available for use, this method does not usually need to be called manually -- it is automatically called
79
   * by all {@link Drawable} component constructors. The {@link Drawable#setDrawingPanel(DrawingPanel)} method is
80
   * meant to be used to move a {@code Drawable} component from one drawing panel to another transparently.</p>
81
   *
82
   * @param component to be added
83
   */
84
  public synchronized void add(Drawable component) {
85
    if (!components.contains(component)) {
86
      components.push(component);
87
    }
88
  }
89
90
  /**
91
   * <p>Remove a drawable component from the drawing panel</p>
92
   *
93
   * <p>While available for use, this method dos not usually need to be called manually -- it is automatically called
94
   * by {@link Drawable#removeFromDrawingPanel()} and, if necessary, by
95
   * {@link Drawable#setDrawingPanel(DrawingPanel)}.</p>
96
   *
97
   * @param component to be removed
98
   * @return {@code true} if the component was present and removed, {@code false} otherwise
99
   */
100
  public synchronized boolean remove(Drawable component) {
101
    return components.remove(component);
102
  }
103
104
  /**
105
   * <p>Clear all drawable components from the drawing panel</p>
106
   *
107
   * <p>This does not destroy the drawable components &mdash; any references to these components outside of the drawing
108
   * panel will still be valid, but the components will no longer refer to this drawing panel.</p>
109
   */
110
  public void clear() {
111
    while (!components.isEmpty()) {
112
      components.peek().removeFromDrawingPanel();
113
    }
114
  }
115
116
  /**
117
   * <p>Repaint the contents of the drawing panel (drawable components) as-needed</p>
118
   *
119
   * <p>This method is called automatically by the enclosing {@link JFrame} to repaint the drawing panel as needed. It
120
   * is not meant to be called manually. If the drawing panel needs to be updated, a {@link #repaint()} request will
121
   * schedule the update.</p>
122
   *
123
   * <p>This method calls the {@link #draw(Graphics2D)} method to perform the actual drawing instructions</p>
124
   *
125
   * @param graphics context for drawing instructions
126
   * @see #draw(Graphics2D)
127
   */
128
  @Override
129
  public void paintComponent(Graphics graphics) {
130
    super.paintComponent(graphics);
131
    draw((Graphics2D) graphics);
132
  }
133
134
  /**
135
   * <p>Request drawing instructions from all contained drawing components</p>
136
   *
137
   * <p>This method encapsulates all of the drawing instructions necessary to display the current drawable components.
138
   * It is used by both the {@link #paintComponent(Graphics)} method to update the display and the {@link #saveAs(String)}
139
   * method to write the current drawable components to a file.</p>
140
   *
141
   * <p>This method calls the {@link #preDraw(Graphics2D)} method prior to making drawing instructions to
142
   * set any rendering hints or other configuration for the drawing.</p>
143
   *
144
   * @param graphics context for drawing instructions
145
   * @see Drawable#draw(Graphics2D)
146
   * @see #preDraw(Graphics2D)
147
   * @see #paintComponent(Graphics)
148
   * @see #saveAs(String, String)
149
   */
150
  protected synchronized void draw(Graphics2D graphics) {
151
    Graphics2D graphics2D = graphics;
152
    preDraw(graphics2D);
153
    for (Drawable component : components) {
154
      component.draw(graphics2D);
155
    }
156
  }
157
158
  /**
159
   * <p>Override this method to set custom graphics configurations</p>
160
   * <p>This method is called by {@link #draw(Graphics2D)} to configure any desired rendering hints or other
161
   * preconfiguration before executing the actual drawing instructions.</p>
162
   *
163
   * @param graphics context for drawing commands
164
   */
165
  public void preDraw(Graphics2D graphics) {
166
    graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
167
  }
168
169
  /**
170
   * Save the current drawing panel as a file using the default format
171
   *
172
   * @param path relative to the current working directory
173
   * @return {@code true} if the file was successfully written, {@code false} otherwise
174
   */
175
  public boolean saveAs(String path) {
176
    return saveAs(path, DEFAULT_IMAGE_FORMAT);
177
  }
178
179
  /**
180
   * Save the current drawing panel as a file
181
   *
182
   * @param path   relative to the current working directory
183
   * @param format of the image file (e.g. {@value #DEFAULT_IMAGE_FORMAT}
184
   * @return {@code true} if the file was successfully written, {@code false} otherwise
185
   */
186
  public boolean saveAs(String path, String format) {
187
    try {
188
      BufferedImage image = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
189
      Graphics2D context = image.createGraphics();
190
      context.setPaint(getBackground());
191
      context.fillRect(0, 0, image.getWidth(), image.getHeight());
192
      draw(context);
193
      ImageIO.write(image, format, new File(path));
194
    } catch (IOException e) {
195
      System.err.println("There was an error trying to create the DrawingPanel image file");
196
      e.printStackTrace();
0 ignored issues
show
Best Practice introduced by
Throwable.printStackTrace writes to the console which might not be available at runtime. Using a logger is preferred.
Loading history...
197
      return false;
198
    }
199
    return true;
200
  }
201
}
202