001 // This class is based on the FishToolbar class, version 1 August 2002
002 // by Julie Zenekski
003
004 // Original copyright notice:
005
006 // AP(r) Computer Science Marine Biology Simulation:
007 // The FishToolbar class is copyright(c) 2002 College Entrance
008 // Examination Board (www.collegeboard.com).
009 //
010 // This class is free software; you can redistribute it and/or modify
011 // it under the terms of the GNU General Public License as published by
012 // the Free Software Foundation.
013 //
014 // This class is distributed in the hope that it will be useful,
015 // but WITHOUT ANY WARRANTY; without even the implied warranty of
016 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
017 // GNU General Public License for more details.
018
019 package view;
020
021 import controller.IDisplayAdapter;
022 import controller.IEditAdapter;
023 import controller.IEnvAdapter;
024 import model.RandNumGenerator;
025 import sysModel.ISecurityAdapter;
026 import sysModel.fish.AFish;
027
028 import javax.swing.*;
029 import java.awt.*;
030 import java.awt.event.ActionEvent;
031 import java.awt.event.ActionListener;
032 import java.awt.geom.AffineTransform;
033 import java.lang.reflect.Constructor;
034 import java.lang.reflect.InvocationTargetException;
035 import java.util.Random;
036
037 /**
038 * Code for the edit toolbar.
039 *
040 * @author Mathias Ricken
041 */
042 public class EditToolbar extends JToolBar implements IEditAdapter {
043 /**
044 * Combobox to choose color.
045 */
046 private JComboBox _colorComboBox;
047
048 /**
049 * Combobox to choose fish class.
050 */
051 private JComboBox _fishComboBox;
052
053 /**
054 * The frame that contains this edit toolbar.
055 */
056 private JFrame _parentFrame;
057
058 /**
059 * Create a new EditToolbar and all its tools.
060 *
061 * @param parentFrame parent frame
062 * @param ea environment adapter
063 * @param da display adapter
064 */
065 public EditToolbar(JFrame parentFrame, IEnvAdapter ea, IDisplayAdapter da) {
066 super(SwingConstants.VERTICAL);
067 setFloatable(false);
068 makeTools(ea, da);
069 _parentFrame = parentFrame;
070 }
071
072 /**
073 * Return the currently selected AFish class.
074 *
075 * @return name of a AFish class (or AFish subclass)
076 */
077 public String getCurrentFish() {
078 return ((FishChoice)_fishComboBox.getSelectedItem()).getFishClassName();
079 }
080
081 /**
082 * Return the currently selected color. If random color is selected, a new randomly generated color is returned.
083 *
084 * @return the color to use for a new fish object
085 */
086 public Color getCurrentColor() {
087 ColorChoice cc = (ColorChoice)_colorComboBox.getSelectedItem();
088 return cc.getColor();
089 }
090
091 /**
092 * Create the UI components for the toolbar.
093 *
094 * @param ea environment adapter
095 * @param da display adapter
096 */
097 private void makeTools(final IEnvAdapter ea, final IDisplayAdapter da) {
098 setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder("Configure new fish"),
099 BorderFactory.createEmptyBorder(4, 4, 4, 4)));
100
101 add(new JLabel(" Type: "));
102
103 _fishComboBox = new JComboBox();
104
105 // add initial fish choices
106 String[] fishClassNames = ea.getFishClassNames();
107 for(int i = 0; i < fishClassNames.length; i++) {
108 String className = fishClassNames[i];
109 try {
110 _fishComboBox.addItem(new FishChoice(className, da, ea.getSecurityAdapter()));
111 }
112 catch(Exception e) {
113 System.out.println(className + " throws: " + e);
114 e.printStackTrace();
115 }
116 }
117 ColorChoice addNew = new ColorChoice("Add ...", Color.white) {
118 public void select() {
119 String className = (new InputStringDialog(JOptionPane.getFrameForComponent(EditToolbar.this),
120 "Add fish class")).showDialog();
121 if (className==null) { return; }
122 try {
123 FishChoice choice = new FishChoice(className, da, ea.getSecurityAdapter());
124 _fishComboBox.insertItemAt(choice, _fishComboBox.getItemCount() - 1);
125 _fishComboBox.setSelectedItem(choice);
126 choice.select();
127 }
128 catch(Throwable t) {
129 _fishComboBox.setSelectedIndex(0);
130 Object[] options = { "OK" };
131 StringBuffer message = new StringBuffer(t.toString());
132 if (null != t.getMessage()) {
133 message.append(':');
134 message.append(t.getMessage());
135 }
136 if (null != t.getCause()) {
137 message.append('\n');
138 message.append(t.getCause().toString());
139 }
140 JOptionPane.showOptionDialog(JOptionPane.getFrameForComponent(EditToolbar.this),
141 message,
142 "Error Loading Fish",
143 JOptionPane.DEFAULT_OPTION,
144 JOptionPane.WARNING_MESSAGE,
145 null,
146 options,
147 options[0]);
148 }
149 }
150 };
151 _fishComboBox.addItem(addNew);
152
153 _fishComboBox.setRenderer(new ChoiceWithIconRenderer(_fishComboBox));
154 _fishComboBox.setAlignmentX(LEFT_ALIGNMENT);
155 _fishComboBox.addActionListener(new ActionListener() {
156 public void actionPerformed(ActionEvent e) {
157 ChoiceWithIcon c = (ChoiceWithIcon)_fishComboBox.getSelectedItem();
158 c.select();
159 }
160 });
161 add(_fishComboBox);
162
163 addSeparator();
164
165 add(new JLabel(" Color: "));
166
167 ColorChoice random = new ColorChoice("Random", ColorIcon.RANDOM_COLOR) {
168 public Color getColor() {
169 Random rng = RandNumGenerator.instance();
170 return new Color(rng.nextInt(256), rng.nextInt(256), rng.nextInt(256));
171 }
172 };
173 ColorChoice other = new ColorChoice("Other ...", Color.white) {
174 public void select() {
175 _colorComboBox.hidePopup();
176 Component parentFrame = JOptionPane.getFrameForComponent(EditToolbar.this);
177 Color chosen = JColorChooser.showDialog(parentFrame, "Choose Fish Color", getColor());
178 if (null != chosen) {
179 setColor(chosen);
180 }
181 }
182 };
183 ColorChoice[] standardChoices = {new ColorChoice("Red", Color.red),
184 new ColorChoice("Orange", new Color(255, 128, 0)),
185 new ColorChoice("Yellow", Color.yellow),
186 new ColorChoice("Green", Color.green),
187 new ColorChoice("Blue", new Color(0, 128, 255)),
188 new ColorChoice("Purple", new Color(128, 0, 128)),
189 random,
190 other};
191 _colorComboBox = new JComboBox(standardChoices);
192 _colorComboBox.setSelectedItem(random);
193 _colorComboBox.setRenderer(new ChoiceWithIconRenderer(_colorComboBox));
194 _colorComboBox.setAlignmentX(LEFT_ALIGNMENT);
195 _colorComboBox.addActionListener(new ActionListener() {
196 public void actionPerformed(ActionEvent e) {
197 ColorChoice cc = (ColorChoice)_colorComboBox.getSelectedItem();
198 cc.select();
199 }
200 });
201 add(_colorComboBox);
202
203 add(Box.createGlue());
204 }
205
206 /**
207 * Shows/hides the fish toolbar. In order to avoid disrupting the current layout, resize the parent frame by the
208 * amount need to grow/shrink to accommodate the change in window size without resizing any other components.
209 *
210 * @param visible true if to be made visible
211 */
212 public void setVisible(boolean visible) {
213 if (isVisible() != visible) {
214 super.setVisible(visible);
215 Dimension cur = _parentFrame.getSize();
216 Dimension change;
217 if (HORIZONTAL == getOrientation()) {
218 change = new Dimension(0, (visible ? 1 : -1) * getPreferredSize().height);
219 }
220 else {
221 change = new Dimension((visible ? 1 : -1) * getPreferredSize().width, 0);
222 }
223 _parentFrame.setSize(new Dimension(cur.width + change.width, cur.height + change.height));
224 _parentFrame.validate(); // force immediate validation
225 }
226 }
227
228 /**
229 * Enables or disables the edit toolbar.
230 *
231 * @param enabled new state
232 */
233 public void setEnabled(boolean enabled) {
234 if (isEnabled() != enabled) {
235 super.setEnabled(enabled);
236 setVisible(enabled);
237 }
238 }
239
240 /**
241 * Interface that unifies choices with icons (fish, color) so we can use one common renderer for both.
242 */
243 private interface ChoiceWithIcon {
244 Icon getIcon();
245
246 /**
247 * Callback if this choice was selected.
248 */
249 void select();
250 }
251
252 /**
253 * Nested class used to hold the per-item information for the entries in the combo box of color choices. Each item
254 * represents a color choice which is basically just a Color object and a name.
255 */
256 private class ColorChoice implements ChoiceWithIcon {
257 private Color color;
258 private String name;
259 private ColorIcon icon;
260
261 public ColorChoice(String n, Color c) {
262 color = c;
263 name = n;
264 icon = new ColorIcon(color, 16, 16);
265 }
266
267 public void setColor(Color c) {
268 color = c;
269 icon.setColor(c);
270 }
271
272 public Color getColor() {
273 return color;
274 }
275
276 public String toString() {
277 return name;
278 }
279
280 public Icon getIcon() {
281 return icon;
282 }
283
284 public void select() {
285 // do nothing by default
286 }
287 }
288
289 /**
290 * Nested class used to hold the per-item information for the entries in the combo box of fish choices. Each item
291 * represents a fish choice which is a AFish class.
292 */
293 private class FishChoice implements ChoiceWithIcon {
294 private String _fishClassName;
295 private FishIcon _icon;
296
297 public FishChoice(String fishClassName, IDisplayAdapter da, ISecurityAdapter sa)
298 throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException{
299 _fishClassName = fishClassName;
300 _icon = new FishIcon(_fishClassName, Color.gray, 16, 16, da, sa);
301 }
302
303 public String getFishClassName() {
304 return _fishClassName;
305 }
306
307 public String toString() {
308 return _fishClassName;
309 }
310
311 public Icon getIcon() {
312 return _icon;
313 }
314
315 public void select() {
316 // by default do nothing
317 }
318 }
319
320 // Custom cell renderer that displays text and icon.
321 // (Default renderer can only display one or the other.)
322 private static class ChoiceWithIconRenderer extends JLabel implements ListCellRenderer {
323 private JComboBox cb;
324
325 public ChoiceWithIconRenderer(JComboBox b) {
326 setOpaque(true);
327 setHorizontalAlignment(LEFT);
328 setVerticalAlignment(CENTER);
329 setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
330 cb = b;
331 }
332
333 public Component getListCellRendererComponent(JList list,
334 Object value,
335 int index,
336 boolean selected,
337 boolean cellHasFocus) {
338 setBackground(selected ? list.getSelectionBackground() : list.getBackground());
339 setForeground(selected ? list.getSelectionForeground() : list.getForeground());
340 if (!cb.isEnabled()) {
341 setText("No choice"); // drawFish differently when disabled
342 setIcon(null);
343 }
344 else {
345 setText(value.toString());
346 setIcon(((ChoiceWithIcon)value).getIcon());
347 }
348 return this;
349 }
350 }
351
352
353 /**
354 * Nested class used to drawFish the color swatch icon used for color choice entries in the color combo box. This
355 * simple class just draws a rectangle filled with the color and edged with a black border.
356 */
357 private static class ColorIcon implements Icon {
358 // so we have a unique object to compare == to
359 public static final Color RANDOM_COLOR = new Color(0, 0, 0);
360 private static final int MARGIN = 2;
361
362 private Color _color;
363 private int _width;
364 private int _height;
365
366 public ColorIcon(Color c, int w, int h) {
367 _color = c;
368 _width = w;
369 _height = h;
370 }
371
372 public void setColor(Color c) {
373 _color = c;
374 }
375
376 public int getIconWidth() {
377 return _width;
378 }
379
380 public int getIconHeight() {
381 return _height;
382 }
383
384 public void paintIcon(Component comp, Graphics g, int x, int y) {
385 Graphics2D g2 = (Graphics2D)g;
386 Rectangle r = new Rectangle(x + MARGIN, y + MARGIN, _width - 2 * MARGIN, _height - 2 * MARGIN);
387 if (_color != RANDOM_COLOR) {
388 g2.setColor(_color);
389 g2.fill(r);
390 }
391 else {
392 for(int k = 0; k < r.width; k++) // drawFish rainbow lines
393 {
394 g2.setColor(Color.getHSBColor((float)k / r.width, .95f, 1));
395 g2.drawLine(r.x + k, r.y, r.x + k, r.y + r.height);
396 }
397 }
398 g2.setColor(Color.black);
399 g2.draw(r);
400 }
401 }
402
403 /**
404 * Nested class used to drawFish the fish icon used for fish entries in the fish combo box. We construct a AFish and
405 * then hand it off to its display object to drawFish.
406 */
407 static class FishIcon implements Icon {
408 private AFish _fish = null;
409 private String _fishClassName;
410 private Color _color;
411 private int _width;
412 private int _height;
413 IDisplayAdapter _da;
414 ISecurityAdapter _sa;
415
416 public FishIcon(String fishClassName, Color c, int w, int h, IDisplayAdapter da, ISecurityAdapter sa)
417 throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
418 _fishClassName = fishClassName;
419 _color = c;
420 _width = w;
421 _height = h;
422 _da = da;
423 _sa = sa;
424
425 if (null == _fish) {
426 // NOTE: Introduced class loader here
427 Class fishClass = null;
428 // Note: DrJava incompatibility.
429 fishClass = _sa.getClassLoader().loadClass(_fishClassName);
430 Constructor envCtor = fishClass.getConstructor(new Class[]{Color.class});
431 _fish = (AFish)envCtor.newInstance(new Object[]{Color.BLUE});
432 }
433 }
434
435 public int getIconWidth() {
436 return _width;
437 }
438
439 public int getIconHeight() {
440 return _height;
441 }
442
443 public void paintIcon(Component comp, Graphics g, int x, int y) {
444 Graphics2D g2 = (Graphics2D)g;
445 AffineTransform savedTransform = g2.getTransform(); // save current
446 g2.translate(x + getIconWidth() / 2, y + getIconHeight() / 2);
447 g2.setStroke(new BasicStroke(1.0f / getIconHeight()));
448 g2.scale(_width, _height);
449 _fish.paint(g2, comp);
450 g2.setTransform(savedTransform); // restore coordinate system
451 }
452 }
453
454 }
455
456