View Javadoc

1   /*
2   @See License.txt@
3    */
4   
5   package spellcast.ui;
6   
7   import java.awt.BorderLayout;
8   import java.awt.GridBagConstraints;
9   import java.awt.GridBagLayout;
10  import java.awt.Insets;
11  import java.awt.event.ActionEvent;
12  import java.awt.event.ActionListener;
13  import java.beans.PropertyChangeListener;
14  import java.io.BufferedReader;
15  import java.io.BufferedWriter;
16  import java.io.File;
17  import java.io.FileReader;
18  import java.io.FileWriter;
19  import java.net.InetAddress;
20  import java.util.ArrayList;
21  import java.util.Collections;
22  import java.util.Comparator;
23  import java.util.Iterator;
24  
25  import javax.swing.BorderFactory;
26  import javax.swing.ComboBoxModel;
27  import javax.swing.DefaultComboBoxModel;
28  import javax.swing.JButton;
29  import javax.swing.JComboBox;
30  import javax.swing.JDialog;
31  import javax.swing.JFrame;
32  import javax.swing.JLabel;
33  import javax.swing.JPanel;
34  import javax.swing.JTextField;
35  import javax.swing.event.SwingPropertyChangeSupport;
36  import org.apache.log4j.Logger;
37  import spellcast.net.NetConstants;
38  import spellcast.util.TextUtil;
39  
40  /***
41   * The New Game Dialog allows the user to create a new spellcast server.
42   * <p>
43   * A JComboBox is displayed with the label "Game Name" into which a game name
44   * can be entered.  By default the values will be the read from the file 
45   * <b>user.home</b>/.spellcast/GameNames.txt.  The format of this file is described at the 
46   * end of this document.
47   * <p>
48   * A JText field is displayed with the label "Server Address" into which a manual address
49   * can be entered.  By default the value of <code>InetAddress.getLocalHost</code>, a colon, and 
50   * <code>NetConstants.SPELLCAST_NET_DEFAULT_PORT</code> will be displayed.
51   * When a new value is filled in the port is optional and will default to the
52   * <code>NetConstants.SPELLCAST_NET_DEFAULT_PORT</code>
53   * <p>
54   * In the South area are two buttons for "Create" and "Cancel".
55   * <p>
56   * Format of <b>user.home</b>/.spellcast/GameNames.txt:<br>
57   * The purpose of this file is to retain the 10 most popular 
58   * choice of game names for this user.<br>
59   * The file consists of at most 10 lines.<br>
60   * Each line is of the form: <number of uses> <game name>
61   * The number of uses indicates how many times this game name has been used.<br>
62   * The game name can contain any character and is limited to be 32 characters.<br>
63   * The file is written so that each line is sorted into most used to least used<br> 
64   * <p>
65   * This class will fire NEW_GAME_PROP property change events.
66   * @author Barrie Treloar
67   */
68  public class NewGameDialog extends JDialog implements UIProperties {
69      private static final int MAX_GAME_NAMES = 10;
70      private static final int MAX_GAME_NAME_STRING_LENGTH = 32;
71  
72      private JButton c_create;
73      private JButton c_cancel;
74      private JComboBox c_gameName;
75      private JTextField c_serverAddress;
76      private ArrayList mostRecentUsedGameNames = new ArrayList(MAX_GAME_NAMES);
77      private File mostRecentUsedGameNamesFile =
78          new File(System.getProperty("user.home") + "/.spellcast/GameNames.txt");
79  
80      private SwingPropertyChangeSupport propertySupport;
81  
82      private static final Logger logger = Logger.getLogger("client.connect");
83  
84      public NewGameDialog() {
85          super((JFrame) null, "Create Spellcast Game", false);
86  
87          propertySupport = new SwingPropertyChangeSupport(this);
88  
89          createSouthPanel();
90          createCenterPanel();
91          pack();
92      }
93  
94      public void addPropertyChangeListener(PropertyChangeListener listener) {
95          propertySupport.addPropertyChangeListener(listener);
96      }
97  
98      public void removePropertyChangeListener(PropertyChangeListener listener) {
99          propertySupport.removePropertyChangeListener(listener);
100     }
101 
102     private ComboBoxModel loadMostRecentUsedGameNames() {
103         DefaultComboBoxModel result = new DefaultComboBoxModel();
104         BufferedReader r = null;
105 
106         try {
107             if (mostRecentUsedGameNamesFile.exists()) {
108                 r = new BufferedReader(new FileReader(mostRecentUsedGameNamesFile));
109                 String line = null;
110                 // Only read at most 10 lines.
111                 for (int i = 0; i < 10 && (line = r.readLine()) != null; i++) {
112                     // Ensure line in correct format ( <number><space><string> )
113                     int numberOfUses = TextUtil.getNumberOfUses(line);
114                     String gameName = TextUtil.getGameName(line);
115                     if (gameName.length() > MAX_GAME_NAME_STRING_LENGTH) {
116                         gameName = gameName.substring(0, MAX_GAME_NAME_STRING_LENGTH);
117                         logger.warn(
118                             "Truncating game name that exceeds max length of "
119                                 + MAX_GAME_NAME_STRING_LENGTH);
120                     }
121                     if (numberOfUses == -1 || gameName == null) {
122                         logger.warn(
123                             "Malformed line (" + i + ") expecting format of " + "<number><space><string>");
124                     }
125                     else {
126                         mostRecentUsedGameNames.add(numberOfUses + " " + gameName);
127                     }
128                 }
129                 Collections.sort(mostRecentUsedGameNames, new GameNameComparator());
130 
131                 Iterator i = mostRecentUsedGameNames.iterator();
132                 while (i.hasNext()) {
133                     line = (String) i.next();
134                     result.addElement(TextUtil.getGameName(line));
135                 }
136             }
137         }
138         catch (Exception e) {
139             logger.error("Could not load Most Recent Used Game Names.", e);
140         }
141         finally {
142             try {
143                 if (r != null) {
144                     r.close();
145                 }
146             }
147             catch (Exception e) {
148                 logger.error("Unexpected error in Reader.close().", e);
149             }
150         }
151 
152         return result;
153     }
154 
155     private void saveMostRecentUsedGameNames(ComboBoxModel model) {
156         BufferedWriter w = null;
157 
158         try {
159             // Creates only if does not exist
160             mostRecentUsedGameNamesFile.createNewFile();
161 
162             if (mostRecentUsedGameNamesFile.exists()) {
163                 // Check to see if the game name has already been used before
164                 // If it has then re-add it to the list after incrementing the numberOfUses
165                 boolean gameNotInList = true;
166                 String currentGameName = (String) model.getSelectedItem();
167                 Iterator i = mostRecentUsedGameNames.iterator();
168                 while (i.hasNext()) {
169                     String line = (String) i.next();
170                     String gameName = TextUtil.getGameName(line);
171                     if (gameName.equals(currentGameName)) {
172                         int numberOfUses = TextUtil.getNumberOfUses(line) + 1;
173                         i.remove();
174                         mostRecentUsedGameNames.add(numberOfUses + " " + gameName);
175                         gameNotInList = false;
176                         break;
177                     }
178                 }
179                 if (gameNotInList) {
180                     mostRecentUsedGameNames.add("1 " + currentGameName);
181                 }
182                 Collections.sort(mostRecentUsedGameNames, new GameNameComparator());
183                 // trim the list to the max size
184                 while (mostRecentUsedGameNames.size() > MAX_GAME_NAMES) {
185                     mostRecentUsedGameNames.remove(mostRecentUsedGameNames.size() - 1);
186                 }
187 
188                 w = new BufferedWriter(new FileWriter(mostRecentUsedGameNamesFile));
189                 i = mostRecentUsedGameNames.iterator();
190                 while (i.hasNext()) {
191                     String line = (String) i.next();
192                     w.write(line);
193                     w.newLine();
194                 }
195             }
196         }
197         catch (Exception e) {
198             logger.error("Could not save Most Recent Used Game Names.", e);
199         }
200         finally {
201             try {
202                 if (w != null) {
203                     w.flush();
204                     w.close();
205                 }
206             }
207             catch (Exception e) {
208                 logger.error("Unexpected error in Writer.close().", e);
209             }
210         }
211 
212     }
213 
214     private void createSouthPanel() {
215         JPanel p = new JPanel();
216         GridBagLayout gridbag = new GridBagLayout();
217         p.setLayout(gridbag);
218         GridBagConstraints c = new GridBagConstraints();
219         // Common constraints
220         c.insets = new Insets(4, 4, 4, 4);
221         c.fill = GridBagConstraints.HORIZONTAL;
222 
223         c.weightx = 1.0;
224         JLabel empty = new JLabel(" ");
225         gridbag.setConstraints(empty, c);
226         p.add(empty);
227 
228         c.weightx = 0.0;
229         c_create = new JButton("Create");
230         c_create.setDefaultCapable(true);
231         getRootPane().setDefaultButton(c_create);
232         c_create.addActionListener(new ActionListener() {
233             public void actionPerformed(ActionEvent e) {
234                 String theGameName = (String) c_gameName.getSelectedItem();
235                 if (theGameName == null || theGameName.equals("")) {
236                     theGameName = "Spellcast";
237                     c_gameName.addItem(theGameName);
238                     c_gameName.setSelectedItem(theGameName);
239                 }
240                 String serverAddressString = c_serverAddress.getText().trim();
241                 setVisible(false);
242 
243                 NewGameEvent evt = new NewGameEvent(theGameName, serverAddressString);
244                 propertySupport.firePropertyChange(NEW_GAME_PROP, null, evt);
245             }
246         });
247         gridbag.setConstraints(c_create, c);
248         p.add(c_create);
249 
250         c_cancel = new JButton("Cancel");
251         c_cancel.addActionListener(new ActionListener() {
252             public void actionPerformed(ActionEvent e) {
253                 setVisible(false);
254             }
255         });
256         gridbag.setConstraints(c_cancel, c);
257         p.add(c_cancel);
258 
259         getContentPane().add(p, BorderLayout.SOUTH);
260     }
261 
262     private void createCenterPanel() {
263         JPanel p = new JPanel();
264         p.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
265 
266         GridBagLayout gridbag = new GridBagLayout();
267         p.setLayout(gridbag);
268         GridBagConstraints c = new GridBagConstraints();
269         // Common constraints
270         c.insets = new Insets(4, 4, 4, 4);
271 
272         JLabel gameNameLabel = new JLabel("Game Name");
273         gridbag.setConstraints(gameNameLabel, c);
274         p.add(gameNameLabel);
275 
276         c.gridwidth = GridBagConstraints.REMAINDER;
277         c.anchor = GridBagConstraints.WEST;
278         c_gameName = new JComboBox(loadMostRecentUsedGameNames());
279         c_gameName.setEditable(true);
280         gridbag.setConstraints(c_gameName, c);
281         p.add(c_gameName);
282         gameNameLabel.setLabelFor(c_gameName);
283 
284         c.gridwidth = GridBagConstraints.RELATIVE;
285         c.anchor = GridBagConstraints.CENTER;
286         JLabel serverAddressLabel = new JLabel("Server Address");
287         gridbag.setConstraints(serverAddressLabel, c);
288         p.add(serverAddressLabel);
289 
290         c.gridwidth = GridBagConstraints.REMAINDER;
291         c.anchor = GridBagConstraints.WEST;
292         c_serverAddress = new JTextField(12);
293         try {
294             c_serverAddress.setText(
295                 InetAddress.getLocalHost().getHostAddress()
296                     + ":"
297                     + NetConstants.SPELLCAST_NET_DEFAULT_PORT);
298         }
299         catch (Exception e) {
300             logger.error("Could not get Localhost for setting Server Address.", e);
301         }
302         gridbag.setConstraints(c_serverAddress, c);
303         p.add(c_serverAddress);
304         serverAddressLabel.setLabelFor(c_serverAddress);
305 
306         getContentPane().add(p, BorderLayout.CENTER);
307     }
308 
309     /***
310      * This comparator imposes the correct order of GameNames based upon
311      * how many uses the Game Name has.  The order is most uses first to least uses last.
312      * It Compares its two arguments for order. 
313      * Returns a negative integer, zero, or a positive integer
314      * as the first argument is less than, equal to, or greater than the second.
315      * Note: this comparator imposes orderings that are inconsistent with equals.
316      */
317     class GameNameComparator implements Comparator {
318         public int compare(Object o1, Object o2) {
319             int result = 0;
320 
321             String s1 = (String) o1;
322             String s2 = (String) o2;
323 
324             int uses1 = TextUtil.getNumberOfUses(s1);
325             int uses2 = TextUtil.getNumberOfUses(s2);
326 
327             // o1 is greater than o2 so it should go first, hence -1
328             if (uses1 > uses2) {
329                 result = -1;
330             }
331             else
332                 if (uses1 < uses2) {
333                     result = 1;
334                 }
335                 else {
336                     result = 0;
337                 }
338 
339             return result;
340         }
341     }
342 
343 }