chiark / gitweb /
HTML: move 'New game' back out of the drop-down menu.
[sgt-puzzles.git] / PuzzleApplet.java
1 /*
2  * PuzzleApplet.java: NestedVM applet for the puzzle collection
3  */
4 import java.awt.*;
5 import java.awt.event.*;
6 import java.awt.image.BufferedImage;
7 import java.util.*;
8 import javax.swing.*;
9 import javax.swing.border.BevelBorder;
10 import javax.swing.Timer;
11 import java.util.List;
12
13 import org.ibex.nestedvm.Runtime;
14
15 public class PuzzleApplet extends JApplet implements Runtime.CallJavaCB {
16
17     private static final long serialVersionUID = 1L;
18
19     private static final int CFG_SETTINGS = 0, CFG_SEED = 1, CFG_DESC = 2,
20             LEFT_BUTTON = 0x0200, MIDDLE_BUTTON = 0x201, RIGHT_BUTTON = 0x202,
21             LEFT_DRAG = 0x203, MIDDLE_DRAG = 0x204, RIGHT_DRAG = 0x205,
22             LEFT_RELEASE = 0x206, CURSOR_UP = 0x209, CURSOR_DOWN = 0x20a,
23             CURSOR_LEFT = 0x20b, CURSOR_RIGHT = 0x20c, MOD_CTRL = 0x1000,
24             MOD_SHFT = 0x2000, MOD_NUM_KEYPAD = 0x4000, ALIGN_VCENTRE = 0x100,
25             ALIGN_HCENTRE = 0x001, ALIGN_HRIGHT = 0x002, C_STRING = 0,
26             C_CHOICES = 1, C_BOOLEAN = 2;
27
28     private JFrame mainWindow;
29
30     private JMenu typeMenu;
31     private JMenuItem[] typeMenuItems;
32     private int customMenuItemIndex;
33
34     private JMenuItem solveCommand;
35     private Color[] colors;
36     private JLabel statusBar;
37     private PuzzlePanel pp;
38     private Runtime runtime;
39     private String[] puzzle_args;
40     private Graphics2D  gg;
41     private Timer timer;
42     private int xarg1, xarg2, xarg3;
43     private int[] xPoints, yPoints;
44     private BufferedImage[] blitters = new BufferedImage[512];
45     private ConfigDialog dlg;
46
47     static {
48         try {
49             UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
50         } catch (Exception ex) {
51             ex.printStackTrace();
52         }
53     }
54
55     public void init() {
56         try {
57             Container cp = getContentPane();
58             cp.setLayout(new BorderLayout());
59             runtime = (Runtime) Class.forName("PuzzleEngine").newInstance();
60             runtime.setCallJavaCB(this);
61             JMenuBar menubar = new JMenuBar();
62             JMenu jm;
63             menubar.add(jm = new JMenu("Game"));
64             addMenuItemWithKey(jm, "New", 'n');
65             addMenuItemCallback(jm, "Restart", "jcallback_restart_event");
66             addMenuItemCallback(jm, "Specific...", "jcallback_config_event", CFG_DESC);
67             addMenuItemCallback(jm, "Random Seed...", "jcallback_config_event", CFG_SEED);
68             jm.addSeparator();
69             addMenuItemWithKey(jm, "Undo", 'u');
70             addMenuItemWithKey(jm, "Redo", 'r');
71             jm.addSeparator();
72             solveCommand = addMenuItemCallback(jm, "Solve", "jcallback_solve_event");
73             solveCommand.setEnabled(false);
74             if (mainWindow != null) {
75                 jm.addSeparator();
76                 addMenuItemWithKey(jm, "Exit", 'q');
77             }
78             menubar.add(typeMenu = new JMenu("Type"));
79             typeMenu.setVisible(false);
80             menubar.add(jm = new JMenu("Help"));
81             addMenuItemCallback(jm, "About", "jcallback_about_event");
82             setJMenuBar(menubar);
83             cp.add(pp = new PuzzlePanel(), BorderLayout.CENTER);
84             pp.addKeyListener(new KeyAdapter() {
85                 public void keyPressed(KeyEvent e) {
86                     int key = -1;
87                     int shift = e.isShiftDown() ? MOD_SHFT : 0;
88                     int ctrl = e.isControlDown() ? MOD_CTRL : 0;
89                     switch (e.getKeyCode()) {
90                     case KeyEvent.VK_LEFT:
91                     case KeyEvent.VK_KP_LEFT:
92                         key = shift | ctrl | CURSOR_LEFT;
93                         break;
94                     case KeyEvent.VK_RIGHT:
95                     case KeyEvent.VK_KP_RIGHT:
96                         key = shift | ctrl | CURSOR_RIGHT;
97                         break;
98                     case KeyEvent.VK_UP:
99                     case KeyEvent.VK_KP_UP:
100                         key = shift | ctrl | CURSOR_UP;
101                         break;
102                     case KeyEvent.VK_DOWN:
103                     case KeyEvent.VK_KP_DOWN:
104                         key = shift | ctrl | CURSOR_DOWN;
105                         break;
106                     case KeyEvent.VK_PAGE_UP:
107                         key = shift | ctrl | MOD_NUM_KEYPAD | '9';
108                         break;
109                     case KeyEvent.VK_PAGE_DOWN:
110                         key = shift | ctrl | MOD_NUM_KEYPAD | '3';
111                         break;
112                     case KeyEvent.VK_HOME:
113                         key = shift | ctrl | MOD_NUM_KEYPAD | '7';
114                         break;
115                     case KeyEvent.VK_END:
116                         key = shift | ctrl | MOD_NUM_KEYPAD | '1';
117                         break;
118                     default:
119                         if (e.getKeyCode() >= KeyEvent.VK_NUMPAD0 && e.getKeyCode() <=KeyEvent.VK_NUMPAD9) {
120                             key = MOD_NUM_KEYPAD | (e.getKeyCode() - KeyEvent.VK_NUMPAD0+'0');
121                         }
122                     break;
123                     }
124                     if (key != -1) {
125                         runtimeCall("jcallback_key_event", new int[] {0, 0, key});
126                     }
127                 }
128                 public void keyTyped(KeyEvent e) {
129                     runtimeCall("jcallback_key_event", new int[] {0, 0, e.getKeyChar()});
130                 }
131             });
132             pp.addMouseListener(new MouseAdapter() {
133                 public void mouseReleased(MouseEvent e) {
134                     mousePressedReleased(e, true);
135                 }
136                 public void mousePressed(MouseEvent e) {
137                     pp.requestFocus();
138                     mousePressedReleased(e, false);
139                 }
140                 private void mousePressedReleased(MouseEvent e, boolean released) {
141                     int button;
142                     if ((e.getModifiers() & (InputEvent.BUTTON2_MASK | InputEvent.SHIFT_MASK)) != 0)
143                         button = MIDDLE_BUTTON;
144                     else if ((e.getModifiers() & (InputEvent.BUTTON3_MASK | InputEvent.ALT_MASK)) != 0)
145                         button = RIGHT_BUTTON;
146                     else if ((e.getModifiers() & (InputEvent.BUTTON1_MASK)) != 0)
147                         button = LEFT_BUTTON;
148                     else
149                         return;
150                     if (released)
151                         button += LEFT_RELEASE - LEFT_BUTTON;
152                     runtimeCall("jcallback_key_event", new int[] {e.getX(), e.getY(), button});
153                 }
154             });
155             pp.addMouseMotionListener(new MouseMotionAdapter() {
156                 public void mouseDragged(MouseEvent e) {
157                     int button;
158                     if ((e.getModifiers() & (InputEvent.BUTTON2_MASK | InputEvent.SHIFT_MASK)) != 0)
159                         button = MIDDLE_DRAG;
160                     else if ((e.getModifiers() & (InputEvent.BUTTON3_MASK | InputEvent.ALT_MASK)) != 0)
161                         button = RIGHT_DRAG;
162                     else
163                         button = LEFT_DRAG;
164                     runtimeCall("jcallback_key_event", new int[] {e.getX(), e.getY(), button});
165                 }
166             });
167             pp.addComponentListener(new ComponentAdapter() {
168                 public void componentResized(ComponentEvent e) {
169                     handleResized();
170                 }
171             });
172             pp.setFocusable(true);
173             pp.requestFocus();
174             timer = new Timer(20, new ActionListener() {
175                 public void actionPerformed(ActionEvent e) {
176                     runtimeCall("jcallback_timer_func", new int[0]);
177                 }
178             });
179             String gameid;
180             try {
181                 gameid = getParameter("game_id");
182             } catch (java.lang.NullPointerException ex) {
183                 gameid = null;
184             }
185             if (gameid == null) {
186                 puzzle_args = null;
187             } else {
188                 puzzle_args = new String[2];
189                 puzzle_args[0] = "puzzle";
190                 puzzle_args[1] = gameid;
191             }
192             SwingUtilities.invokeLater(new Runnable() {
193                 public void run() {
194                     runtime.start(puzzle_args);
195                     runtime.execute();
196                 }
197             });
198         } catch (Exception ex) {
199             ex.printStackTrace();
200         }
201     }
202
203     public void destroy() {
204         SwingUtilities.invokeLater(new Runnable() {
205             public void run() {
206                 runtime.execute();
207                 if (mainWindow != null) {
208                     mainWindow.dispose();
209                     System.exit(0);
210                 }
211             }
212         });
213     }
214
215     protected void handleResized() {
216         pp.createBackBuffer(pp.getWidth(), pp.getHeight(), colors[0]);
217         runtimeCall("jcallback_resize", new int[] {pp.getWidth(), pp.getHeight()});
218     }
219
220     private void addMenuItemWithKey(JMenu jm, String name, int key) {
221         addMenuItemCallback(jm, name, "jcallback_menu_key_event", key);
222     }
223
224     private JMenuItem addMenuItemCallback(JMenu jm, String name, final String callback, final int arg) {
225         return addMenuItemCallback(jm, name, callback, new int[] {arg}, false);
226     }
227
228     private JMenuItem addMenuItemCallback(JMenu jm, String name, final String callback) {
229         return addMenuItemCallback(jm, name, callback, new int[0], false);
230     }
231
232     private JMenuItem addMenuItemCallback(JMenu jm, String name, final String callback, final int[] args, boolean checkbox) {
233         JMenuItem jmi;
234         if (checkbox)
235             jm.add(jmi = new JCheckBoxMenuItem(name));
236         else
237         jm.add(jmi = new JMenuItem(name));
238         jmi.addActionListener(new ActionListener() {
239             public void actionPerformed(ActionEvent e) {
240                 runtimeCall(callback, args);
241             }
242         });
243         return jmi;
244     }
245
246     protected void runtimeCall(String func, int[] args) {
247         if (runtimeCallWithResult(func, args) == 42 && mainWindow != null) {
248             destroy();
249         }
250     }
251
252     protected int runtimeCallWithResult(String func, int[] args) {
253         try {
254             return runtime.call(func, args);
255         } catch (Runtime.CallException ex) {
256             ex.printStackTrace();
257             return 42;
258         }
259     }
260
261     private void buildConfigureMenuItem() {
262         if (typeMenu.isVisible()) {
263             typeMenu.addSeparator();
264         } else {
265             typeMenu.setVisible(true);
266         }
267         typeMenuItems[customMenuItemIndex] =
268             addMenuItemCallback(typeMenu, "Custom...",
269                                 "jcallback_config_event",
270                                 new int[] {CFG_SETTINGS}, true);
271     }
272
273     private void addTypeItem
274         (JMenu targetMenu, String name, int newId, final int ptrGameParams) {
275
276         typeMenu.setVisible(true);
277         typeMenuItems[newId] =
278             addMenuItemCallback(targetMenu, name,
279                                 "jcallback_preset_event",
280                                 new int[] {ptrGameParams}, true);
281     }
282
283     private void addTypeSubmenu
284         (JMenu targetMenu, String name, int newId) {
285
286         JMenu newMenu = new JMenu(name);
287         newMenu.setVisible(true);
288         typeMenuItems[newId] = newMenu;
289         targetMenu.add(newMenu);
290     }
291
292     public int call(int cmd, int arg1, int arg2, int arg3) {
293         try {
294             switch(cmd) {
295             case 0: // initialize
296                 if (mainWindow != null) mainWindow.setTitle(runtime.cstring(arg1));
297                 if ((arg2 & 1) != 0) buildConfigureMenuItem();
298                 if ((arg2 & 2) != 0) addStatusBar();
299                 if ((arg2 & 4) != 0) solveCommand.setEnabled(true);
300                 colors = new Color[arg3];
301                 return 0;
302             case 1: // configure Type menu
303                 if (arg1 == 0) {
304                     // preliminary setup
305                     typeMenuItems = new JMenuItem[arg2 + 2];
306                     typeMenuItems[arg2] = typeMenu;
307                     customMenuItemIndex = arg2 + 1;
308                     return arg2;
309                 } else if (xarg1 != 0) {
310                     addTypeItem((JMenu)typeMenuItems[arg2],
311                                 runtime.cstring(arg1), arg3, xarg1);
312                 } else {
313                     addTypeSubmenu((JMenu)typeMenuItems[arg2],
314                                    runtime.cstring(arg1), arg3);
315                 }
316                 return 0;
317             case 2: // MessageBox
318                 JOptionPane.showMessageDialog(this, runtime.cstring(arg2), runtime.cstring(arg1), arg3 == 0 ? JOptionPane.INFORMATION_MESSAGE : JOptionPane.ERROR_MESSAGE);
319                 return 0;
320             case 3: // Resize
321                 pp.setPreferredSize(new Dimension(arg1, arg2));
322                 if (mainWindow != null) mainWindow.pack();
323                 handleResized();
324                 if (mainWindow != null) mainWindow.setVisible(true);
325                 return 0;
326             case 4: // drawing tasks
327                 switch(arg1) {
328                 case 0:
329                     String text = runtime.cstring(arg2);
330                     if (text.equals("")) text = " ";
331                     statusBar.setText(text);
332                     break;
333                 case 1:
334                     gg = pp.backBuffer.createGraphics();
335                     if (arg2 != 0 || arg3 != 0 ||
336                         arg2 + xarg2 != getWidth() ||
337                         arg3 + xarg3 != getHeight()) {
338                         int left = arg2, right = arg2 + xarg2;
339                         int top = arg3, bottom = arg3 + xarg3;
340                         int width = getWidth(), height = getHeight();
341                         gg.setColor(colors != null ? colors[0] : Color.black);
342                         gg.fillRect(0, 0, left, height);
343                         gg.fillRect(right, 0, width-right, height);
344                         gg.fillRect(0, 0, width, top);
345                         gg.fillRect(0, bottom, width, height-bottom);
346                         gg.setClip(left, top, right-left, bottom-top);
347                     }
348                     break;
349                 case 2: gg.dispose(); pp.repaint(); break;
350                 case 3: gg.setClip(arg2, arg3, xarg1, xarg2); break;
351                 case 4:
352                     if (arg2 == 0 && arg3 == 0) {
353                         gg.setClip(0, 0, getWidth(), getHeight());
354                     } else {
355                         gg.setClip(arg2, arg3, getWidth()-2*arg2, getHeight()-2*arg3);
356                     }
357                     break;
358                 case 5:
359                     gg.setColor(colors[xarg3]);
360                     gg.fillRect(arg2, arg3, xarg1, xarg2);
361                     break;
362                 case 6:
363                     gg.setColor(colors[xarg3]);
364                     gg.drawLine(arg2, arg3, xarg1, xarg2);
365                     break;
366                 case 7:
367                     xPoints = new int[arg2];
368                     yPoints = new int[arg2];
369                     break;
370                 case 8:
371                     if (arg3 != -1) {
372                         gg.setColor(colors[arg3]);
373                         gg.fillPolygon(xPoints, yPoints, xPoints.length);
374                     }
375                     gg.setColor(colors[arg2]);
376                     gg.drawPolygon(xPoints, yPoints, xPoints.length);
377                     break;
378                 case 9:
379                     if (arg3 != -1) {
380                         gg.setColor(colors[arg3]);
381                         gg.fillOval(xarg1-xarg3, xarg2-xarg3, xarg3*2, xarg3*2);
382                     }
383                     gg.setColor(colors[arg2]);
384                     gg.drawOval(xarg1-xarg3, xarg2-xarg3, xarg3*2, xarg3*2);
385                     break;
386                 case 10:
387                     for(int i=0; i<blitters.length; i++) {
388                         if (blitters[i] == null) {
389                             blitters[i] = new BufferedImage(arg2, arg3, BufferedImage.TYPE_3BYTE_BGR);
390                             return i;
391                         }
392                     }
393                     throw new RuntimeException("No free blitter found!");
394                 case 11: blitters[arg2] = null; break;
395                 case 12:
396                     timer.start(); break;
397                 case 13:
398                     timer.stop(); break;
399                 }
400                 return 0;
401             case 5: // more arguments
402                 xarg1 = arg1;
403                 xarg2 = arg2;
404                 xarg3 = arg3;
405                 return 0;
406             case 6: // polygon vertex
407                 xPoints[arg1]=arg2;
408                 yPoints[arg1]=arg3;
409                 return 0;
410             case 7: // string
411                 gg.setColor(colors[arg2]);
412                 {
413                     String text = runtime.utfstring(arg3);
414                     Font ft = new Font((xarg3 & 0x10) != 0 ? "Monospaced" : "Dialog",
415                             Font.PLAIN, 100);
416                     int height100 = this.getFontMetrics(ft).getHeight();
417                     ft = ft.deriveFont(arg1 * 100 / (float)height100);
418                     FontMetrics fm = this.getFontMetrics(ft);
419                     int asc = fm.getAscent(), desc = fm.getDescent();
420                     if ((xarg3 & ALIGN_VCENTRE) != 0)
421                         xarg2 += asc - (asc+desc)/2;
422                     int wid = fm.stringWidth(text);
423                     if ((xarg3 & ALIGN_HCENTRE) != 0)
424                         xarg1 -= wid / 2;
425                     else if ((xarg3 & ALIGN_HRIGHT) != 0)
426                         xarg1 -= wid;
427                     gg.setFont(ft);
428                     gg.drawString(text, xarg1, xarg2);
429                 }
430                 return 0;
431             case 8: // blitter_save
432                 Graphics g2 = blitters[arg1].createGraphics();
433                 g2.drawImage(pp.backBuffer, 0, 0, blitters[arg1].getWidth(), blitters[arg1].getHeight(),
434                         arg2, arg3, arg2 + blitters[arg1].getWidth(), arg3 + blitters[arg1].getHeight(), this);
435                 g2.dispose();
436                 return 0;
437             case 9: // blitter_load
438                 gg.drawImage(blitters[arg1], arg2, arg3, this);
439                 return 0;
440             case 10: // dialog_init
441                 dlg= new ConfigDialog(this, runtime.cstring(arg1));
442                 return 0;
443             case 11: // dialog_add_control
444                 {
445                     int sval_ptr = arg1;
446                     int ival = arg2;
447                     int ptr = xarg1;
448                     int type=xarg2;
449                     String name = runtime.cstring(xarg3);
450                     switch(type) {
451                     case C_STRING:
452                         dlg.addTextBox(ptr, name, runtime.cstring(sval_ptr));
453                         break;
454                     case C_BOOLEAN:
455                         dlg.addCheckBox(ptr, name, ival != 0);
456                         break;
457                     case C_CHOICES:
458                         dlg.addComboBox(ptr, name, runtime.cstring(sval_ptr), ival);
459                     }
460                 }
461                 return 0;
462             case 12:
463                 dlg.finish();
464                 dlg = null;
465                 return 0;
466             case 13: // tick a menu item
467                 if (arg1 < 0) arg1 = customMenuItemIndex;
468                 for (int i = 0; i < typeMenuItems.length; i++) {
469                     if (typeMenuItems[i] instanceof JCheckBoxMenuItem) {
470                         ((JCheckBoxMenuItem)typeMenuItems[i]).setSelected
471                             (arg1 == i);
472                     }
473                 }
474                 return 0;
475             default:
476                 if (cmd >= 1024 && cmd < 2048) { // palette
477                     colors[cmd-1024] = new Color(arg1, arg2, arg3);
478                 }
479             if (cmd == 1024) {
480                 pp.setBackground(colors[0]);
481                 if (statusBar != null) statusBar.setBackground(colors[0]);
482                 this.setBackground(colors[0]);
483             }
484             return 0;
485             }
486         } catch (Throwable ex) {
487             ex.printStackTrace();
488             System.exit(-1);
489             return 0;
490         }
491     }
492
493     private void addStatusBar() {
494         statusBar = new JLabel("test");
495         statusBar.setBorder(new BevelBorder(BevelBorder.LOWERED));
496         getContentPane().add(BorderLayout.SOUTH,statusBar);
497     }
498
499     // Standalone runner
500     public static void main(String[] args) {
501         final PuzzleApplet a = new PuzzleApplet();
502         JFrame jf = new JFrame("Loading...");
503         jf.getContentPane().setLayout(new BorderLayout());
504         jf.getContentPane().add(a, BorderLayout.CENTER);
505         a.mainWindow=jf;
506         a.init();
507         a.start();
508         jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
509         jf.addWindowListener(new WindowAdapter() {
510             public void windowClosing(WindowEvent e) {
511                 a.stop();
512                 a.destroy();
513             }
514         });
515         jf.setVisible(true);
516     }
517
518     public static class PuzzlePanel extends JPanel {
519
520         private static final long serialVersionUID = 1L;
521         protected BufferedImage backBuffer;
522
523         public PuzzlePanel() {
524             setPreferredSize(new Dimension(100,100));
525             createBackBuffer(100,100, Color.black);
526         }
527
528         public void createBackBuffer(int w, int h, Color bg) {
529             if (w > 0 && h > 0) {
530                 backBuffer = new BufferedImage(w,h, BufferedImage.TYPE_3BYTE_BGR);
531                 Graphics g = backBuffer.createGraphics();
532                 g.setColor(bg);
533                 g.fillRect(0, 0, w, h);
534                 g.dispose();
535             }
536         }
537
538         protected void paintComponent(Graphics g) {
539             g.drawImage(backBuffer, 0, 0, this);
540         }
541     }
542
543     public static class ConfigComponent {
544         public int type;
545         public int configItemPointer;
546         public JComponent component;
547
548         public ConfigComponent(int type, int configItemPointer, JComponent component) {
549             this.type = type;
550             this.configItemPointer = configItemPointer;
551             this.component = component;
552         }
553     }
554
555     public class ConfigDialog extends JDialog {
556
557         private GridBagConstraints gbcLeft = new GridBagConstraints(
558                 GridBagConstraints.RELATIVE, GridBagConstraints.RELATIVE, 1, 1,
559                 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE,
560                 new Insets(0, 0, 0, 0), 0, 0);
561         private GridBagConstraints gbcRight = new GridBagConstraints(
562                 GridBagConstraints.RELATIVE, GridBagConstraints.RELATIVE,
563                 GridBagConstraints.REMAINDER, 1, 1.0, 0,
564                 GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL,
565                 new Insets(5, 5, 5, 5), 0, 0);
566         private GridBagConstraints gbcBottom = new GridBagConstraints(
567                 GridBagConstraints.RELATIVE, GridBagConstraints.RELATIVE,
568                 GridBagConstraints.REMAINDER, GridBagConstraints.REMAINDER,
569                 1.0, 1.0, GridBagConstraints.CENTER,
570                 GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0);
571
572         private static final long serialVersionUID = 1L;
573         private List components = new ArrayList();
574
575         public ConfigDialog(JApplet parent, String title) {
576             super(JOptionPane.getFrameForComponent(parent), title, true);
577             getContentPane().setLayout(new GridBagLayout());
578         }
579
580         public void addTextBox(int ptr, String name, String value) {
581             getContentPane().add(new JLabel(name), gbcLeft);
582             JComponent c = new JTextField(value, 25);
583             getContentPane().add(c, gbcRight);
584             components.add(new ConfigComponent(C_STRING, ptr, c));
585         }
586
587
588         public void addCheckBox(int ptr, String name, boolean selected) {
589             JComponent c = new JCheckBox(name, selected);
590             getContentPane().add(c, gbcRight);
591             components.add(new ConfigComponent(C_BOOLEAN, ptr, c));
592         }
593
594         public void addComboBox(int ptr, String name, String values, int selected) {
595             getContentPane().add(new JLabel(name), gbcLeft);
596             StringTokenizer st = new StringTokenizer(values.substring(1), values.substring(0,1));
597             JComboBox c = new JComboBox();
598             c.setEditable(false);
599             while(st.hasMoreTokens())
600                 c.addItem(st.nextToken());
601             c.setSelectedIndex(selected);
602             getContentPane().add(c, gbcRight);
603             components.add(new ConfigComponent(C_CHOICES, ptr, c));
604         }
605
606         public void finish() {
607             JPanel buttons = new JPanel(new GridLayout(1, 2, 5, 5));
608             getContentPane().add(buttons, gbcBottom);
609             JButton b;
610             buttons.add(b=new JButton("OK"));
611             b.addActionListener(new ActionListener() {
612                 public void actionPerformed(ActionEvent e) {
613                     save();
614                     dispose();
615                 }
616             });
617             getRootPane().setDefaultButton(b);
618             buttons.add(b=new JButton("Cancel"));
619             b.addActionListener(new ActionListener() {
620                 public void actionPerformed(ActionEvent e) {
621                     dispose();
622                 }
623             });
624             setDefaultCloseOperation(DISPOSE_ON_CLOSE);
625             pack();
626             setLocationRelativeTo(null);
627             setVisible(true);
628         }
629         private void save() {
630             for (int i = 0; i < components.size(); i++) {
631                 ConfigComponent cc = (ConfigComponent) components.get(i);
632                 switch(cc.type) {
633                 case C_STRING:
634                     JTextField jtf = (JTextField)cc.component;
635                     runtimeCall("jcallback_config_set_string", new int[] {cc.configItemPointer, runtime.strdup(jtf.getText())});
636                     break;
637                 case C_BOOLEAN:
638                     JCheckBox jcb = (JCheckBox)cc.component;
639                     runtimeCall("jcallback_config_set_boolean", new int[] {cc.configItemPointer, jcb.isSelected()?1:0});
640                     break;
641                 case C_CHOICES:
642                     JComboBox jcm = (JComboBox)cc.component;
643                     runtimeCall("jcallback_config_set_choice", new int[] {cc.configItemPointer, jcm.getSelectedIndex()});
644                     break;
645                 }
646             }
647             runtimeCall("jcallback_config_ok", new int[0]);
648         }
649     }
650 }