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