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