View Javadoc
1   
2   package org.newdawn.slick.font.effects;
3   
4   import java.awt.AlphaComposite;
5   import java.awt.Color;
6   import java.awt.Dimension;
7   import java.awt.EventQueue;
8   import java.awt.Graphics2D;
9   import java.awt.GridBagConstraints;
10  import java.awt.GridBagLayout;
11  import java.awt.Insets;
12  import java.awt.image.BufferedImage;
13  
14  import javax.annotation.Nonnull;
15  import javax.annotation.Nullable;
16  import javax.swing.BorderFactory;
17  import javax.swing.DefaultComboBoxModel;
18  import javax.swing.JButton;
19  import javax.swing.JCheckBox;
20  import javax.swing.JColorChooser;
21  import javax.swing.JComboBox;
22  import javax.swing.JComponent;
23  import javax.swing.JDialog;
24  import javax.swing.JLabel;
25  import javax.swing.JPanel;
26  import javax.swing.JSpinner;
27  import javax.swing.JTextArea;
28  import javax.swing.SpinnerNumberModel;
29  
30  import org.newdawn.slick.font.GlyphPage;
31  import org.newdawn.slick.font.effects.ConfigurableEffect.Value;
32  
33  /**
34   * Provides utility methods for effects.
35   * 
36   * @author Nathan Sweet <misc@n4te.com>
37   */
38  public class EffectUtil {
39      /** A graphics 2D temporary surface to be used when generating effects */
40      @Nonnull
41      static private final BufferedImage scratchImage = new BufferedImage(GlyphPage.MAX_GLYPH_SIZE, GlyphPage.MAX_GLYPH_SIZE,
42          BufferedImage.TYPE_INT_ARGB);
43  
44      /**
45       * Returns an image that can be used by effects as a temp image.
46       *
47       * @return The scratch image used for temporary operations
48       */
49      @Nonnull
50      static public BufferedImage getScratchImage() {
51          Graphics2D g = (Graphics2D)scratchImage.getGraphics();
52          g.setComposite(AlphaComposite.Clear);
53          g.fillRect(0, 0, GlyphPage.MAX_GLYPH_SIZE, GlyphPage.MAX_GLYPH_SIZE);
54          g.setComposite(AlphaComposite.SrcOver);
55          g.setColor(java.awt.Color.white);
56          return scratchImage;
57      }
58  
59      /**
60       * Prompts the user for a colour value
61       *
62       * @param name Thename of the value being configured
63       * @param currentValue The default value that should be selected
64       * @return The value selected
65       */
66      @Nullable
67      static public Value colorValue(String name, @Nullable Color currentValue) {
68          return new DefaultValue(name, EffectUtil.toString(currentValue)) {
69              public void showDialog () {
70                  Color newColor = JColorChooser.showDialog(null, "Choose a color", EffectUtil.fromString(value));
71                  if (newColor != null) value = EffectUtil.toString(newColor);
72              }
73  
74              public Object getObject () {
75                  return EffectUtil.fromString(value);
76              }
77          };
78      }
79  
80      /**
81       * Prompts the user for int value
82       *
83       * @param name The name of the dialog to show
84       * @param currentValue The current value to be displayed
85       * @param description The help text to provide
86       * @return The value selected by the user
87       */
88      @Nonnull
89      static public Value intValue (String name, final int currentValue, final String description) {
90          return new DefaultValue(name, String.valueOf(currentValue)) {
91              public void showDialog () {
92                  JSpinner spinner = new JSpinner(new SpinnerNumberModel(currentValue, Short.MIN_VALUE, Short.MAX_VALUE, 1));
93                  if (showValueDialog(spinner, description)) value = String.valueOf(spinner.getValue());
94              }
95  
96              public Object getObject () {
97                  return Integer.valueOf(value);
98              }
99          };
100     }
101 
102     /**
103      * Prompts the user for float value
104      *
105      * @param name The name of the dialog to show
106      * @param currentValue The current value to be displayed
107      * @param description The help text to provide
108      * @param min The minimum value to allow
109      * @param max The maximum value to allow
110      * @return The value selected by the user
111      */
112     @Nonnull
113     static public Value floatValue (String name, final float currentValue, final float min, final float max,
114         final String description) {
115         return new DefaultValue(name, String.valueOf(currentValue)) {
116             public void showDialog () {
117                 JSpinner spinner = new JSpinner(new SpinnerNumberModel(currentValue, min, max, 0.1f));
118                 if (showValueDialog(spinner, description)) value = String.valueOf(((Double)spinner.getValue()).floatValue());
119             }
120 
121             public Object getObject () {
122                 return Float.valueOf(value);
123             }
124         };
125     }
126 
127     /**
128      * Prompts the user for boolean value
129      *
130      * @param name The name of the dialog to show
131      * @param currentValue The current value to be displayed
132      * @param description The help text to provide
133      * @return The value selected by the user
134      */
135     @Nonnull
136     static public Value booleanValue (String name, final boolean currentValue, final String description) {
137         return new DefaultValue(name, String.valueOf(currentValue)) {
138             public void showDialog () {
139                 JCheckBox checkBox = new JCheckBox();
140                 checkBox.setSelected(currentValue);
141                 if (showValueDialog(checkBox, description)) value = String.valueOf(checkBox.isSelected());
142             }
143 
144             public Object getObject () {
145                 return Boolean.valueOf(value);
146             }
147         };
148     }
149 
150 
151     /**
152      * Prompts the user for a value that represents a fixed number of options.
153      * All options are strings.
154      *
155      * @param options The first array has an entry for each option. Each entry is either a String[1] that is both the display value
156      *           and actual value, or a String[2] whose first element is the display value and second element is the actual value.
157      *
158      * @param name The name of the value being prompted for
159      * @param currentValue The current value to show as default
160      * @param description The description of the value
161      * @return The value selected by the user
162      */
163     @Nonnull
164     static public Value optionValue (String name, final String currentValue, @Nonnull final String[][] options, final String description) {
165         return new DefaultValue(name, currentValue) {
166             public void showDialog () {
167                 int selectedIndex = -1;
168                 DefaultComboBoxModel<String> model = new DefaultComboBoxModel<>();
169                 for (int i = 0; i < options.length; i++) {
170                     model.addElement(options[i][0]);
171                     if (getValue(i).equals(currentValue)) selectedIndex = i;
172                 }
173                 JComboBox<String> comboBox = new JComboBox<>(model);
174                 comboBox.setSelectedIndex(selectedIndex);
175                 if (showValueDialog(comboBox, description)) value = getValue(comboBox.getSelectedIndex());
176             }
177 
178             private String getValue (int i) {
179                 if (options[i].length == 1) return options[i][0];
180                 return options[i][1];
181             }
182 
183             public String toString () {
184                 for (int i = 0; i < options.length; i++)
185                     if (getValue(i).equals(value)) return options[i][0];
186                 return "";
187             }
188 
189             public Object getObject () {
190                 return value;
191             }
192         };
193     }
194 
195     /**
196      * Convers a color to a string.
197      *
198      * @param color The color to encode to a string
199      * @return The colour as a string
200      */
201     @Nonnull
202     private static String toString(@Nullable Color color) {
203         if (color == null) throw new IllegalArgumentException("color cannot be null.");
204         String r = Integer.toHexString(color.getRed());
205         if (r.length() == 1) r = "0" + r;
206         String g = Integer.toHexString(color.getGreen());
207         if (g.length() == 1) g = "0" + g;
208         String b = Integer.toHexString(color.getBlue());
209         if (b.length() == 1) b = "0" + b;
210         return r + g + b;
211     }
212 
213     /**
214      * Converts a string to a color.
215      *
216      * @param rgb The string encoding the colour
217      * @return The colour represented by the given encoded string
218      */
219     private static Color fromString(@Nullable String rgb) {
220         if (rgb == null || rgb.length() != 6) return Color.white;
221         return new Color(Integer.parseInt(rgb.substring(0, 2), 16), Integer.parseInt(rgb.substring(2, 4), 16), Integer.parseInt(rgb
222             .substring(4, 6), 16));
223     }
224 
225     /**
226      * Provides generic functionality for an effect's configurable value.
227      */
228     static private abstract class DefaultValue implements Value {
229         /** The value being held */
230         String value;
231         /** The key/name of the value */
232         final String name;
233 
234         /**
235          * Create a default value
236          *
237          * @param name The name of the value being configured
238          * @param value The value to use for the default
239          */
240         public DefaultValue(String name, String value) {
241             this.value = value;
242             this.name = name;
243         }
244 
245         /**
246          * @see org.newdawn.slick.font.effects.ConfigurableEffect.Value#setString(java.lang.String)
247          */
248         public void setString(String value) {
249             this.value = value;
250         }
251 
252         /**
253          * @see org.newdawn.slick.font.effects.ConfigurableEffect.Value#getString()
254          */
255         public String getString() {
256             return value;
257         }
258 
259         /**
260          * @see org.newdawn.slick.font.effects.ConfigurableEffect.Value#getName()
261          */
262         public String getName() {
263             return name;
264         }
265 
266         /**
267          * @see java.lang.Object#toString()
268          */
269         public String toString() {
270             if (value == null) {
271                 return "";
272             }
273             return value;
274         }
275 
276         /**
277          * Prompt the user for a value
278          *
279          * @param component The component to use as parent for the prompting dialog
280          * @param description The description of the value being prompted for
281          * @return True if the value was configured
282          */
283         public boolean showValueDialog(@Nonnull final JComponent component, String description) {
284             ValueDialog dialog = new ValueDialog(component, name, description);
285             dialog.setTitle(name);
286             dialog.setLocationRelativeTo(null);
287             EventQueue.invokeLater(() -> {
288                 JComponent focusComponent = component;
289                 if (focusComponent instanceof JSpinner)
290                     focusComponent = ((JSpinner.DefaultEditor)((JSpinner)component).getEditor()).getTextField();
291                 focusComponent.requestFocusInWindow();
292             });
293             dialog.setVisible(true);
294             return dialog.okPressed;
295         }
296     }
297 
298     /**
299      * Provides generic functionality for a dialog to configure a value.
300      */
301     static private class ValueDialog extends JDialog {
302         /**
303          * 
304          */
305         private static final long serialVersionUID = 1L;
306         /** True if OK was pressed */
307         public boolean okPressed = false;
308 
309         /**
310          * Create a new dialog to configure a specific value
311          *
312          * @param component The component to use as the parent of the dialog prompting the user
313          * @param name The name of the value being configured
314          * @param description The description of the value being configured
315          */
316         public ValueDialog(JComponent component, String name, String description) {
317             setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
318             setLayout(new GridBagLayout());
319             setModal(true);
320 
321             if (component instanceof JSpinner)
322                 ((JSpinner.DefaultEditor)((JSpinner)component).getEditor()).getTextField().setColumns(4);
323 
324             JPanel descriptionPanel = new JPanel();
325             descriptionPanel.setLayout(new GridBagLayout());
326             getContentPane().add(
327                 descriptionPanel,
328                 new GridBagConstraints(0, 0, 2, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0,
329                     0), 0, 0));
330             descriptionPanel.setBackground(Color.white);
331             descriptionPanel.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, Color.black));
332             {
333                 JTextArea descriptionText = new JTextArea(description);
334                 descriptionPanel.add(descriptionText, new GridBagConstraints(0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER,
335                     GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 0, 0));
336                 descriptionText.setWrapStyleWord(true);
337                 descriptionText.setLineWrap(true);
338                 descriptionText.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
339                 descriptionText.setEditable(false);
340             }
341 
342             JPanel panel = new JPanel();
343             getContentPane().add(
344                 panel,
345                 new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(5, 5, 0,
346                     5), 0, 0));
347             panel.add(new JLabel(name + ":"));
348             panel.add(component);
349 
350             JPanel buttonPanel = new JPanel();
351             getContentPane().add(
352                 buttonPanel,
353                 new GridBagConstraints(0, 2, 2, 1, 0.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.NONE,
354                     new Insets(0, 0, 0, 0), 0, 0));
355             {
356                 JButton okButton = new JButton("OK");
357                 buttonPanel.add(okButton);
358                 okButton.addActionListener(evt -> {
359                     okPressed = true;
360                     setVisible(false);
361                 });
362             }
363             {
364                 JButton cancelButton = new JButton("Cancel");
365                 buttonPanel.add(cancelButton);
366                 cancelButton.addActionListener(evt -> setVisible(false));
367             }
368 
369             setSize(new Dimension(320, 175));
370         }
371     }
372 }