1// BEGIN LICENSE BLOCK
2// Version: CMPL 1.1
3//
4// The contents of this file are subject to the Cisco-style Mozilla Public
5// License Version 1.1 (the "License"); you may not use this file except
6// in compliance with the License.  You may obtain a copy of the License
7// at www.eclipse-clp.org/license.
8//
9// Software distributed under the License is distributed on an "AS IS"
10// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See
11// the License for the specific language governing rights and limitations
12// under the License.
13//
14// The Original Code is  The ECLiPSe Constraint Logic Programming System.
15// The Initial Developer of the Original Code is  Cisco Systems, Inc.
16// Portions created by the Initial Developer are
17// Copyright (C) 2002 - 2006 Cisco Systems, Inc.  All Rights Reserved.
18//
19// Contributor(s): Andrew Cheadle, IC-Parc
20//
21// END LICENSE BLOCK
22
23/*
24 * EclipseMapColourer.java
25 *
26 * Program:   ECLiPSe Java Map Colouring Demo
27 * System:    ECLiPSe Constraint Logic Programming System
28 * Author/s    Andrew Cheadle, IC-Parc
29 */
30
31
32// Class imports
33import java.awt.*;
34import java.awt.event.*;
35import javax.swing.event.*;
36import javax.swing.JMenu;
37import javax.swing.JMenuItem;
38import javax.swing.JMenuBar;
39import javax.swing.JRadioButtonMenuItem;
40import javax.swing.ButtonGroup;
41import javax.swing.JButton;
42import javax.swing.JFrame;
43import javax.swing.JDialog;
44import javax.swing.JPanel;
45import javax.swing.JTextArea;
46import javax.swing.JTextField;
47import javax.swing.JLabel;
48import javax.swing.JSlider;
49import javax.swing.border.BevelBorder;
50import javax.swing.border.EtchedBorder;
51import javax.swing.JFileChooser;
52import javax.swing.JOptionPane;
53import javax.swing.ImageIcon;
54import java.io.*;
55import java.awt.image.*;
56import java.util.Vector;
57import java.awt.geom.Rectangle2D;
58import javax.swing.filechooser.FileFilter;
59import com.parctechnologies.eclipse.*;
60
61// The application is implemented by extending the
62// swing JFrame class.
63public class EclipseMapColourer extends JFrame
64{
65  // Class constructor
66  public EclipseMapColourer()
67  {
68    // Construct a JFrame with the appropriate title
69    super("Java ECLiPSe Map Colouring Demo");
70
71    // Build and layout the main map colouring frame
72    buildApplicationFrame();
73
74    // Build and layout the map resizing dialog
75    buildmapSizeDialog();
76
77    // Create a Map file chooser
78    mapFileChooser = new JFileChooser();
79
80    // Add a file filter to the MAP file selector
81    mapFileChooser.addChoosableFileFilter(new MapFileFilter());
82
83    // Colouring thread is not running
84    colouringThread = null;
85  }
86
87  // Build and layout the main map colouring frame
88  private void buildApplicationFrame()
89  {
90    // Control Panel
91    controlButtonPanel = new JPanel();
92
93    runButton = new JButton();
94    // When the 'Run' button is clicked start map colouring
95    runButton.addActionListener(new ActionListener() {
96        public void actionPerformed(ActionEvent evt) {
97          startSearch();
98        }
99      });
100
101    moreButton = new JButton();
102    // When the 'More' button is clicked continue map colouring
103    moreButton.addActionListener(new ActionListener() {
104        public void actionPerformed(ActionEvent evt) {
105          continueSearch();
106        }
107      });
108
109    finishButton = new JButton();
110    // When the 'Finish' button is clicked terminate map colouring
111    finishButton.addActionListener(new ActionListener() {
112        public void actionPerformed(ActionEvent evt) {
113          endSearch();
114        }
115      });
116
117    // Default ImageBuffer - blank
118    imageBuffer = new BufferedImage(100, 100,
119                                    BufferedImage.TYPE_INT_ARGB);
120
121    // Panel for displaying (coloured) map
122    mapPanel = new MapPanel();
123
124    // Panel and text are for status bar
125    statusPanel = new JPanel();
126    statusPanel.setLayout(new GridLayout(1, 1));
127    statusPanel.setBorder(new BevelBorder(BevelBorder.LOWERED));
128    statusTextArea = new JTextArea();
129    statusTextArea.setBackground(new Color(204, 204, 204));
130    statusTextArea.setEditable(false);
131    statusTextArea.setText(" Status: ");
132    statusTextArea.setToolTipText("Status of Map colouring");
133    statusPanel.add(statusTextArea);
134
135    // Menus
136    menuBar = new JMenuBar();
137
138    // File, New Map, Map Size..., Exit
139    fileMenu = new JMenu();
140    newMapMenuItem = new JMenuItem();
141    mapSizeMenuItem = new JMenuItem();
142    exitMenuItem = new JMenuItem();
143
144    // Method
145    methodMenu = new JMenu();
146
147    // Solver
148    solverMenu = new JMenu();
149    solverButtonGroup = new ButtonGroup();
150    fdMenuItem = new JRadioButtonMenuItem();
151    icMenuItem = new JRadioButtonMenuItem();
152    delayTilGrndMenuItem = new JRadioButtonMenuItem();
153    prologMenuItem = new JRadioButtonMenuItem();
154
155    // Value Choice
156    valueChoiceMenu = new JMenu();
157    valueChoiceButtonGroup = new ButtonGroup();
158    indomainMenuItem = new JRadioButtonMenuItem();
159    indomainRandomMenuItem = new JRadioButtonMenuItem();
160    rotateColoursMenuItem = new JRadioButtonMenuItem();
161
162    // Variable Selection
163    variableSelectionMenu = new JMenu();
164    variableSelectionButtonGroup = new ButtonGroup();
165    inputOrderMenuItem = new JRadioButtonMenuItem();
166    firstFailMenuItem = new JRadioButtonMenuItem();
167    occurrenceMenuItem = new JRadioButtonMenuItem();
168    mostConstrainedMenuItem = new JRadioButtonMenuItem();
169    antiFirstFailMenuItem = new JRadioButtonMenuItem();
170
171    // Help
172    helpMenu = new JMenu();
173    aboutMenuItem = new JMenuItem();
174    aboutMenuItem.addActionListener(new ActionListener() {
175        public void actionPerformed(ActionEvent evt) {
176          JOptionPane.showMessageDialog(null, "JAVA ECLiPSe Map Colouring " +
177                                        "Demo\nCopyright: Cisco Systems Inc, 2002\n",
178                                        "About ECLiPSe Map Colourer...",
179                                        JOptionPane.PLAIN_MESSAGE,
180                                        new ImageIcon("ic-parc.gif"));
181        }
182      });
183
184    // Configure menus
185    setJMenuBar(menuBar);
186
187    // File, New Map, Map Size..., Exit
188    fileMenu.setText("File");
189    newMapMenuItem.setText("New Map...");
190    newMapMenuItem.addActionListener(new ActionListener() {
191        public void actionPerformed(ActionEvent evt) {
192          chooseMapFile();
193        }
194      });
195    fileMenu.add(newMapMenuItem);
196    mapSizeMenuItem.setText("Map Size...");
197    mapSizeMenuItem.addActionListener(new ActionListener() {
198        public void actionPerformed(ActionEvent evt) {
199          mapSizeDialog.show();
200        }
201      });
202    fileMenu.add(mapSizeMenuItem);
203    fileMenu.addSeparator();
204    exitMenuItem.setText("Exit");
205    exitMenuItem.addActionListener(new ActionListener() {
206        public void actionPerformed(ActionEvent evt) {
207          exitForm();
208        }
209      });
210    fileMenu.add(exitMenuItem);
211    menuBar.add(fileMenu);
212
213
214    // Method
215    methodMenu.setText("Method");
216    menuBar.add(methodMenu);
217
218    // Solver
219    solverMenu.setText("Solver");
220    methodMenu.add(solverMenu);
221
222    // FD
223    fdMenuItem.setSelected(true);
224    fdMenuItem.setText("FD");
225    fdMenuItem.setActionCommand("fd");
226    solverMenu.add(fdMenuItem);
227    solverButtonGroup.add(fdMenuItem);
228
229    // IC
230    icMenuItem.setText("IC");
231    icMenuItem.setActionCommand("ic");
232    solverMenu.add(icMenuItem);
233    solverButtonGroup.add(icMenuItem);
234
235    // Delay Until Ground
236    delayTilGrndMenuItem.setText("Delay Until Ground");
237    delayTilGrndMenuItem.setActionCommand("delay");
238    solverMenu.add(delayTilGrndMenuItem);
239    solverButtonGroup.add(delayTilGrndMenuItem);
240
241    // Prolog
242    prologMenuItem.setText("Prolog (Generate & Test)");
243    prologMenuItem.setActionCommand("prolog");
244    solverMenu.add(prologMenuItem);
245    solverButtonGroup.add(prologMenuItem);
246
247    // Value Choice
248    valueChoiceMenu.setText("Value Choice");
249    methodMenu.add(valueChoiceMenu);
250
251    // Indomain
252    indomainMenuItem.setText("In-Domain");
253    indomainMenuItem.setSelected(true);
254    indomainMenuItem.setActionCommand("indomain");
255    valueChoiceMenu.add(indomainMenuItem);
256    valueChoiceButtonGroup.add(indomainMenuItem);
257
258    // Indomain_random
259    indomainRandomMenuItem.setText("Random-In-Domain");
260    indomainRandomMenuItem.setActionCommand("indomain_random");
261    valueChoiceMenu.add(indomainRandomMenuItem);
262    valueChoiceButtonGroup.add(indomainRandomMenuItem);
263
264    // Rotate Colours
265    rotateColoursMenuItem.setText("Rotate Colours");
266    rotateColoursMenuItem.setActionCommand("rotate");
267    valueChoiceMenu.add(rotateColoursMenuItem);
268    valueChoiceButtonGroup.add(rotateColoursMenuItem);
269
270    // Variable Selection
271    variableSelectionMenu.setText("Variable Selection");
272    methodMenu.add(variableSelectionMenu);
273
274    // Input Order
275    inputOrderMenuItem.setText("Input Order");
276    inputOrderMenuItem.setSelected(true);
277    inputOrderMenuItem.setActionCommand("input_order");
278    variableSelectionMenu.add(inputOrderMenuItem);
279    variableSelectionButtonGroup.add(inputOrderMenuItem);
280
281    // First-Fail
282    firstFailMenuItem.setText("First-Fail");
283    firstFailMenuItem.setActionCommand("first_fail");
284    variableSelectionMenu.add(firstFailMenuItem);
285    variableSelectionButtonGroup.add(firstFailMenuItem);
286
287    // Occurrence
288    occurrenceMenuItem.setText("Occurrence");
289    occurrenceMenuItem.setActionCommand("occurrence");
290    variableSelectionMenu.add(occurrenceMenuItem);
291    variableSelectionButtonGroup.add(occurrenceMenuItem);
292
293    // Most Contrained
294    mostConstrainedMenuItem.setText("Most Constrained");
295    mostConstrainedMenuItem.setActionCommand("most_constrained");
296    variableSelectionMenu.add(mostConstrainedMenuItem);
297    variableSelectionButtonGroup.add(mostConstrainedMenuItem);
298
299    // Anti-First-Fail
300    antiFirstFailMenuItem.setText("Anti-First-Fail");
301    antiFirstFailMenuItem.setActionCommand("anti_first_fail");
302    variableSelectionMenu.add(antiFirstFailMenuItem);
303    variableSelectionButtonGroup.add(antiFirstFailMenuItem);
304
305    // Help
306    helpMenu.setText("Help");
307    menuBar.add(helpMenu);
308
309    // About
310    aboutMenuItem.setText("About");
311    helpMenu.add(aboutMenuItem);
312
313    // Configure window
314
315    // Listen for the window close event
316    addWindowListener(new WindowAdapter() {
317        public void windowClosing(WindowEvent evt) {
318          exitForm();
319        }
320      });
321
322    // Set a grid layout for the control panel
323    controlButtonPanel.setLayout(new GridLayout(1, 3));
324
325    // Add the Run button
326    runButton.setText("Run");
327    runButton.setToolTipText("Start map colouring search");
328    runButton.setBorder(new BevelBorder(BevelBorder.RAISED));
329    controlButtonPanel.add(runButton);
330
331    // Add the More button
332    moreButton.setText("More");
333    moreButton.setToolTipText("Find next map colouring solution");
334    moreButton.setBorder(new BevelBorder(BevelBorder.RAISED));
335    moreButton.setEnabled(false);
336    controlButtonPanel.add(moreButton);
337
338    // Add the Finish button
339    finishButton.setText("Finish");
340    finishButton.setToolTipText("Terminate map colouring search");
341    finishButton.setBorder(new BevelBorder(BevelBorder.RAISED));
342    finishButton.setEnabled(false);
343    controlButtonPanel.add(finishButton);
344
345    // Place the control panel at the top of the window
346    getContentPane().add(controlButtonPanel, BorderLayout.NORTH);
347
348    // Place the map panel in the center of the window
349    getContentPane().add(mapPanel, BorderLayout.CENTER);
350
351    // Place the status panel at the bottom of the window
352    getContentPane().add(statusPanel, BorderLayout.SOUTH);
353
354    // Lay everything out neatly
355    pack();
356  }
357
358  // Build and layout the map resizing dialog
359  private void buildmapSizeDialog()
360  {
361    // Map size and scaling modal dialog box
362    mapSizeDialog = new JDialog(this, "Map Size", true);
363    mapSizeDialog.setResizable(false);
364    mapSizeDialog.getContentPane().setLayout(new GridLayout(1,1));
365    mapSizeDialog.setSize(332, 94);
366
367    // An etched frame to enclose the widgets
368    mapSizePanel = new JPanel();
369    mapSizePanel.setLayout(null);
370    mapSizePanel.setBorder(new EtchedBorder());
371
372    // Map sizing widgets
373    mapSizeLabel = new JLabel();
374    mapSizeLabel.setText("Map size:");
375    mapSizeLabel.setBounds(10, 10, 58, 16);
376    mapSizePanel.add(mapSizeLabel);
377
378    mapSizeSlider = new JSlider();
379    mapSizeSlider.setMinorTickSpacing(1);
380    mapSizeSlider.setBounds(73, 10, 192, 16);
381    mapSizeSlider.addChangeListener(new ChangeListener() {
382        public void stateChanged(ChangeEvent e) {
383          JSlider source = (JSlider)e.getSource();
384          if (!source.getValueIsAdjusting()) {
385            mapSizeTextField.setText("" + source.getValue());
386          }
387        }
388      });
389    mapSizePanel.add(mapSizeSlider);
390
391    mapSizeTextField = new JTextField();
392    mapSizeTextField.setBorder(new BevelBorder(BevelBorder.LOWERED));
393    mapSizeTextField.setHorizontalAlignment(JTextField.CENTER);
394    mapSizeTextField.setBounds(270, 8, 40, 20);
395    mapSizePanel.add(mapSizeTextField);
396
397    // Map scaling widgets
398    mapScalingLabel = new JLabel();
399    mapScalingLabel.setText("Map scale:");
400    mapScalingLabel.setBounds(10, 40, 64, 16);
401    mapSizePanel.add(mapScalingLabel);
402
403    mapScalingTextField = new JTextField();
404    mapScalingTextField.setBorder(new BevelBorder(BevelBorder.LOWERED));
405    mapScalingTextField.setHorizontalAlignment(JTextField.CENTER);
406    mapScalingTextField.setText("" + scalingFactor);
407    mapScalingTextField.setBounds(80, 38, 110, 20);
408    mapSizePanel.add(mapScalingTextField);
409
410    // 'Ok' button closes window and re-sizes/re-scales the map
411    mapSizeOkButton = new JButton();
412    mapSizeOkButton.setText("Ok");
413    mapSizeOkButton.setBorder(new BevelBorder(BevelBorder.RAISED));
414    mapSizeOkButton.setBounds(206, 40, 50, 20);
415    mapSizeOkButton.addActionListener(new ActionListener() {
416        public void actionPerformed(ActionEvent evt) {
417          int newScalingFactor, newMapSize;
418
419          newScalingFactor = (new Integer(mapScalingTextField.getText())).intValue();
420          newMapSize = (new Integer(mapSizeTextField.getText())).intValue();
421          resizeMap(newMapSize, newScalingFactor);
422          mapSizeDialog.hide();
423        }
424      });
425    mapSizePanel.add(mapSizeOkButton);
426
427    // 'Close' button just closes the window
428    mapSizeCancelButton = new JButton();
429    mapSizeCancelButton.setText("Cancel");
430    mapSizeCancelButton.setBorder(new BevelBorder(BevelBorder.RAISED));
431    mapSizeCancelButton.setBounds(262, 40, 50, 20);
432    mapSizeCancelButton.addActionListener(new ActionListener() {
433        public void actionPerformed(ActionEvent evt) {
434          mapSizeDialog.hide();
435        }
436      });
437    mapSizePanel.add(mapSizeCancelButton);
438
439    mapSizeDialog.getContentPane().add(mapSizePanel);
440  }
441
442  // Exit the application - the window is closed or 'Exit' is selected
443  private void exitForm()
444  {
445    // Stop the thread - otherwise System.exit() will wait for thread
446    // completion, i.e. map colouring to complete
447    if (colouringThread != null) {
448      // Thread.stop() is deprecated, however, the thread is
449      // only ever stopped at termination - the reasons cited
450      // for not using Thread.stop() do not apply. Because of
451      // the Java - ECLiPSe control model, it is better to do this
452      // than litter the code with checks for an 'exit variable'
453      // becoming 'true' and the exit not being actioned until
454      // control returns from ECLiPSe to one of these cancellation
455      // points.
456      colouringThread.stop();
457    }
458
459    try {
460      // Destroy the Eclipse
461      ((EmbeddedEclipse) eclipse).destroy();
462    }
463    catch(Exception expn) {
464      System.out.println("Exception caught whilst " +
465                         "destroying ECLiPSe engine: " +
466                         expn.getMessage());
467    }
468
469    // Exit!!
470    System.exit(0);
471  }
472
473  // Open the map file selector dialog and on selection
474  // of a file compile the map data
475  private void chooseMapFile()
476  {
477    int returnVal = mapFileChooser.showOpenDialog(this);
478    if(returnVal == JFileChooser.APPROVE_OPTION) {
479      compileMapData(mapFileChooser.getSelectedFile().getAbsolutePath());
480    }
481  }
482
483  // The 'Run' button has been clicked, initiate map
484  // colouring solution search
485  private void startSearch()
486  {
487    // Set the status
488    statusTextArea.setText(" Status: Colouring map - searching for a solution");
489
490    // Kick off a new thread that passes control to the ECLiPSe engine
491    // and waits for completion of the 'colouring' predicate. This must
492    // be done in a new thread otherwise the Java AWT event handling thread
493    // will be used and whilst ECLiPSe has control. As a result the user won't
494    // be able to perform any interaction and the map will not be updated with
495    // colours in real-time.
496    (colouringThread = new Thread() {
497        public void run() {
498
499          CompoundTerm result;
500          solutionCount = 0;
501
502          // Disable operations that are not allowed whilst colouring is
503          // in progress.
504          runButton.setEnabled(false);
505          methodMenu.setEnabled(false);
506          newMapMenuItem.setEnabled(false);
507          mapSizeMenuItem.setEnabled(false);
508
509          // Pass control to the embedded ECLiPSe engine and execute
510          // the 'colouring' predicate
511          try {
512	    Object[] colouringArgs = {new Atom(solverButtonGroup.getSelection().getActionCommand()),
513
514                                      new Atom(variableSelectionButtonGroup.getSelection().getActionCommand()),
515				      new Atom(valueChoiceButtonGroup.getSelection().getActionCommand()),
516                                      new Integer(mapSize), null, null};
517
518            result = eclipse.rpc(new CompoundTermImpl("colouring", colouringArgs));
519
520            // The top-level functor of the goal term is 'colouring'.
521            // The fifth and sixth arguments are the return arguments
522            // with the number of backtracks performed and the execution time. The
523            // remaining arguments are populated from the radio button menu items.
524            Integer backtracks = (Integer)result.arg(5);
525            Double time = (Double)result.arg(6);
526
527            if (backtracks != null) {
528              statusTextArea.setText(" Status: " + solutionCount + " solution(s) found " +
529                                     "in " + time.doubleValue() + " seconds with " +
530                                     backtracks.intValue() + " backtracks");
531            }
532            else {
533              statusTextArea.setText(" Status: " + solutionCount + " solution(s) found " +
534                                     "in " + time.doubleValue() + " seconds");
535            }
536          }
537          catch(Exception msqExpn) {
538            System.out.println( "Exception caught whilst invoking " +
539                                "remote predicate 'colouring': " +
540                                msqExpn.getMessage());
541          }
542
543          // Colouring completed - enable all operations
544          runButton.setEnabled(true);
545          methodMenu.setEnabled(true);
546          newMapMenuItem.setEnabled(true);
547          mapSizeMenuItem.setEnabled(true);
548
549          // Colouring thread is not running
550          colouringThread = null;
551        }
552      }).start();
553  }
554
555  // The 'Continue' button has been clicked, continue map
556  // colouring solution search
557  private void continueSearch()
558  {
559    // Set the status
560    statusTextArea.setText(" Status: Colouring map - searching for a solution");
561
562    // Disable operations that are not allowed whilst colouring is
563    // in progress.
564    moreButton.setEnabled(false);
565    finishButton.setEnabled(false);
566
567    // Indicate (another) solution has been found
568    solutionCount ++;
569
570    // Pass control back to ECLiPSe indicating it should
571    // continue the search
572    controlDataProducer.writeContinueAction("yes");
573  }
574
575  // The 'Finish' button has been clicked, terminate map
576  // colouring solution search
577  private void endSearch()
578  {
579    // Enable/disable the appropriate operations
580    runButton.setEnabled(true);
581    moreButton.setEnabled(false);
582    finishButton.setEnabled(false);
583    methodMenu.setEnabled(true);
584    newMapMenuItem.setEnabled(true);
585    mapSizeMenuItem.setEnabled(true);
586
587    // Indicate (another) solution has been found
588    solutionCount ++;
589
590    // Pass control back to ECLiPSe indicating it should
591    // terminate the search
592    controlDataProducer.writeContinueAction("no");
593  }
594
595  // Draw the map on the image buffer and set the size of the
596  // application frame appropriately
597  private void setupMap()
598  {
599    int cnt, rectCnt;
600    Rectangle rect;
601    Graphics2D gbi = imageBuffer.createGraphics();
602
603    gbi.setColor(Color.gray);
604
605    // Draw and fill every rectangle for every country
606    for(cnt = 0; cnt < mapSize; cnt++) {
607      for(rectCnt = 0; rectCnt < countries[cnt].size(); rectCnt++) {
608        rect = (Rectangle)countries[cnt].elementAt(rectCnt);
609        gbi.draw(rect);
610        gbi.fill(rect);
611      }
612    }
613
614    // Set the size of the application frame appropriately
615    this.setSize(maxWidth + 20, maxHeight + 35 +
616                 controlButtonPanel.getHeight() +
617                 menuBar.getHeight() + statusPanel.getHeight());
618  }
619
620  // The map size and scale has been updated, reflect this on the
621  // map
622  private void resizeMap(int newMapSize, int newScalingFactor)
623  {
624    int cnt, rectCnt, x, y, width, height;
625    Rectangle rect, newRect;
626    Vector[] newCountries;
627
628    // A new vector for the new country dimensions
629    newCountries = new Vector[maxMapSize];
630
631    // No longer no the maximum width and height of the image buffer
632    maxWidth = 0;
633    maxHeight = 0;
634
635    // Apply scaling to every rectangle of every country
636    for(cnt = 0; cnt < maxMapSize; cnt++) {
637      newCountries[cnt] = new Vector();
638      for(rectCnt = 0; rectCnt < countries[cnt].size(); rectCnt++) {
639        rect = (Rectangle)countries[cnt].elementAt(rectCnt);
640        x = (((rect.x - 1) / scalingFactor) * newScalingFactor) + 1;
641        y = (((rect.y - 1) / scalingFactor) * newScalingFactor) + 1;
642        width = (((rect.width + 2) / scalingFactor) * newScalingFactor) - 2;
643        height = (((rect.height + 2) / scalingFactor) * newScalingFactor) - 2;
644
645        newRect = new Rectangle(x, y, width, height);
646
647          newCountries[cnt].addElement(newRect);
648
649          // Update the maximum width and height of the image buffer
650          if ( width > maxWidth ) maxWidth = width;
651          if ( height > maxHeight ) maxHeight = height;
652      }
653    }
654
655    // Update the scaling and sizing factors
656    mapSize = newMapSize;
657    scalingFactor = newScalingFactor;
658
659    // Update the countries data structure
660    countries = newCountries;
661
662    // Create a new image buffer
663    imageBuffer = new BufferedImage(maxWidth, maxHeight,
664                                    BufferedImage.TYPE_INT_ARGB);
665
666    // Draw the map on the image buffer and set the size of the
667    // application frame appropriately
668    setupMap();
669
670    // Make a request for components to be laid out by
671    // the layout manmager
672    validate();
673
674    // Redraw the window
675    repaint();
676  }
677
678  // A colour translation function translating the prolog map colours
679  // to appropriate Java colours
680  private Color convertColour(String colour)
681  {
682    if (colour.equals("green")) return Color.green;
683    if (colour.equals("purple")) return Color.magenta;
684    if (colour.equals("red")) return Color.red;
685    if (colour.equals("yellow")) return Color.yellow;
686    if (colour.equals("darkgray")) return Color.gray;
687
688    System.out.println("Colour '" + colour + "' is unmapped.");
689    return Color.white;
690  }
691
692  // A country has been update with a new colour - reflect this
693  // on the map
694  private void updateMap(int countryIndex, String colour)
695  {
696    int rectCnt;
697    Rectangle rect;
698    Graphics2D gbi = (Graphics2D)imageBuffer.getGraphics();
699
700    gbi.setColor(convertColour(colour));
701
702    // Draw and fill every rectangle of the country in the appropriate
703    // colour
704    for(rectCnt = 0; rectCnt < countries[countryIndex].size(); rectCnt++) {
705      rect = (Rectangle)countries[countryIndex].elementAt(rectCnt);
706      gbi.draw(rect);
707      gbi.fill(rect);
708
709      // Redraw the window
710      repaint();
711    }
712  }
713
714  // When initialising, compile a default map file
715  private void compileDefaultMapData()
716  {
717    // Get the pathname separator, e.g. '/' (UNIX) or a '\'
718    // (Windows)
719    String seperator = System.getProperty("file.separator");
720
721    // Locate the default map file
722    String defaultMapFile =
723      System.getProperty("eclipse.directory") +
724      seperator + "lib_tcl" + seperator + "map_data.map";
725
726    // Compile the default map data
727    compileMapData(defaultMapFile);
728  }
729
730  // Instruct the embedded ECLiPSe engine to compile a
731  // new map file and pass it control to do so the
732  // country data will be received on the 'setup_map' queue
733  private void compileMapData(String filename)
734  {
735    CompoundTerm result;
736
737    // Make an embedded ECLiPSe call to 'init_map' predicate
738    try {
739
740      result = eclipse.rpc( new CompoundTermImpl("init_map", filename, null));
741
742      // The top-level functor of the goal term is 'init_map'.
743      // The first and second arguments of the goal term are the
744      // filename and the maximum size of the map. It is the second
745      // argument that we're interested in.
746      Integer retMaxMapSize = (Integer)result.arg(2);
747
748      // Reflect the map size in the appropriate variables and
749      // window widgets
750      mapSize = maxMapSize = retMaxMapSize.intValue();
751      mapSizeSlider.setMaximum(maxMapSize);
752      mapSizeSlider.setValue(maxMapSize);
753      mapSizeTextField.setText("" + mapSize);
754    }
755    catch(Exception msqExpn) {
756      System.out.println( "Exception caught whilst invoking " +
757                          "remote predicate 'init_map': " +
758                          msqExpn.getMessage());
759      return;
760    }
761
762    // Create a fixed size array containing dynamic arrays of
763    // rectangles to store the country data. Each country is represented
764    // as a dynamic array of rectangles
765    countries = new Vector[maxMapSize];
766
767    // Make an embedded ECLiPSe call to 'get_map_data' predicate
768    try {
769      result = eclipse.rpc( new CompoundTermImpl("get_map_data",
770                                new Integer(mapSize)));
771    }
772    catch(Exception msqExpn) {
773      System.out.println( "Exception caught whilst invoking " +
774                          "remote predicate 'get_map_data': " +
775                          msqExpn.getMessage());
776    }
777
778    repaint();
779    statusTextArea.setText(" Status: Map file '" + filename
780                           + "' loaded");
781  }
782
783  // Embed an ECLiPSe engine, initialise it, compile in the
784  // map colouring program and setup the 'setup_map',
785  // 'update_map' and 'continue' Java - ECLiPSe EXDR
786  // interface queues
787  private void loadECLiPSeMapColouringEngine()
788  {
789    String seperator;
790
791    eclipseEngineOptions = new EclipseEngineOptions();
792
793    // Connect the Eclipse's standard streams to the JVM's
794    eclipseEngineOptions.setUseQueues(false);
795
796    try {
797      // Initialise Eclipse
798      eclipse = EmbeddedEclipse.getInstance(eclipseEngineOptions);
799    }
800    catch(Exception expn) {
801      System.out.println("Exception caught whilst " +
802                         "initialising ECLiPSe engine: " +
803                         expn.getMessage());
804      System.exit(0);
805    }
806
807    seperator = System.getProperty("file.separator");
808
809    // Locate the map colouring program
810    mapColouringProgram = new File(System.getProperty("eclipse.directory") +
811                                   seperator + "lib_tcl" + seperator +
812                                   "mapcolour.ecl");
813
814    // Compile the map colouring solver
815    try {
816      eclipse.compile(mapColouringProgram);
817    }
818    catch(Exception miscExpn) {
819      System.out.println( "Exception caught whilst compiling " +
820                          "'mapcolour.ecl': " +
821                          miscExpn.getMessage());
822      System.exit(0);
823    }
824
825    // Set up the java representation of the queue streams
826    try {
827      continueQueue = eclipse.getToEclipseQueue("continue");
828    }
829    catch(Exception cqExpn) {
830      System.out.println( "Exception caught whilst creating " +
831                          "continue queue: " +
832                          cqExpn.getMessage());
833      System.exit(0);
834    }
835
836    // Add a producer to the continueQueue ToEclipseQueue
837    try {
838      controlDataProducer = new ControlDataProducer();
839      continueQueue.setListener(controlDataProducer);
840    }
841    catch(IOException cqIOExpn) {
842      System.out.println( "I/O Exception caught whilst creating " +
843                          "control queue listener: " +
844                          cqIOExpn.getMessage());
845      System.exit(0);
846    }
847
848    try {
849      setupMapQueue = eclipse.getFromEclipseQueue("setup_map");
850    }
851    catch(Exception msqExpn) {
852      System.out.println( "Exception caught whilst creating " +
853                          "map setup queue: " +
854                          msqExpn.getMessage());
855      System.exit(0);
856    }
857
858    // Add a listener to the setupMapQueue FromEclipseQueue
859    try {
860      setupMapQueue.setListener(new SetupMapDataListener());
861    }
862    catch(IOException msqIOExpn) {
863      System.out.println( "I/O Exception caught whilst creating " +
864                          "map setup queue listener: " +
865                          msqIOExpn.getMessage());
866      System.exit(0);
867    }
868
869    try {
870      updateMapQueue = eclipse.getFromEclipseQueue("update_map");
871    }
872    catch(Exception muqExpn) {
873      System.out.println( "Exception caught whilst creating " +
874                          "map update queue: " +
875                          muqExpn.getMessage());
876      System.exit(0);
877    }
878
879    // Add a listener to the updateMapQueue FromEclipseQueue
880    try {
881      updateMapQueue.setListener(new UpdateMapDataListener());
882    }
883    catch(IOException muqIOExpn) {
884      System.out.println( "I/O Exception caught whilst creating " +
885                          "map update queue listener: " +
886                          muqIOExpn.getMessage());
887      System.exit(0);
888    }
889  }
890
891  // Static 'main' function defining the application -
892  // load the default map data and show the application frame
893  public static void main(String args[])
894  {
895    EclipseMapColourer mapColourerApp;
896
897    // Construct the application frame
898    mapColourerApp = new EclipseMapColourer();
899
900    // Initialise and configure ECLiPSe engine
901    mapColourerApp.loadECLiPSeMapColouringEngine();
902
903    // Create the EclipseMapColourer
904
905    // Load the default map data
906    mapColourerApp.compileDefaultMapData();
907
908    // Open the EclipseMapColourer frame
909    mapColourerApp.setResizable(false);
910    mapColourerApp.show();
911  }
912
913  // Private class variable declarations
914
915  // GUI objects
916  private JMenuItem aboutMenuItem;
917  private JRadioButtonMenuItem rotateColoursMenuItem;
918  private JRadioButtonMenuItem indomainRandomMenuItem;
919  private JMenu fileMenu;
920  private JPanel controlButtonPanel;
921  private MapPanel mapPanel;
922  private JPanel statusPanel;
923  private JTextArea statusTextArea;
924  private JMenuBar menuBar;
925  private JMenu valueChoiceMenu;
926  private JRadioButtonMenuItem fdMenuItem;
927  private JMenu variableSelectionMenu;
928  private JMenuItem mapSizeMenuItem;
929  private JButton moreButton;
930  private JRadioButtonMenuItem delayTilGrndMenuItem;
931  private JButton finishButton;
932  private JRadioButtonMenuItem firstFailMenuItem;
933  private JMenu methodMenu;
934  private JRadioButtonMenuItem antiFirstFailMenuItem;
935  private JButton runButton;
936  private JRadioButtonMenuItem indomainMenuItem;
937  private JRadioButtonMenuItem inputOrderMenuItem;
938  private JMenu solverMenu;
939  private JMenuItem exitMenuItem;
940  private JMenuItem newMapMenuItem;
941  private JRadioButtonMenuItem icMenuItem;
942  private JRadioButtonMenuItem prologMenuItem;
943  private JRadioButtonMenuItem mostConstrainedMenuItem;
944  private JRadioButtonMenuItem occurrenceMenuItem;
945  private JMenu helpMenu;
946  private ButtonGroup solverButtonGroup;
947  private ButtonGroup valueChoiceButtonGroup;
948  private ButtonGroup variableSelectionButtonGroup;
949  private JFileChooser mapFileChooser;
950  private JDialog mapSizeDialog;
951  private JPanel mapSizePanel;
952  private JLabel mapSizeLabel;
953  private JSlider mapSizeSlider;
954  private JTextField mapSizeTextField;
955  private JLabel mapScalingLabel;
956  private JTextField mapScalingTextField;
957  private JButton mapSizeOkButton;
958  private JButton mapSizeCancelButton;
959
960  // Map related objects
961  private Vector[] countries;
962  private int scalingFactor = 20;
963  private BufferedImage imageBuffer;
964  private int maxWidth;
965  private int maxHeight;
966  private Thread colouringThread;
967  private int maxMapSize;
968  private int mapSize;
969  private int solutionCount;
970
971  // ECLiPSe Objects
972
973  // Create some default Eclipse options
974  private EclipseEngineOptions eclipseEngineOptions;
975
976  // Object representing the Eclipse process
977  private EclipseEngine eclipse;
978
979  // Path of the Eclipse program
980  private File mapColouringProgram;
981
982  // Data going out from java
983  private ToEclipseQueue continueQueue;
984  private ControlDataProducer controlDataProducer;
985
986  // Data coming in from eclipse
987  private FromEclipseQueue setupMapQueue;
988  private FromEclipseQueue updateMapQueue;
989
990  // Define an inner class that extends a JPanel
991  // allowing us to display an image buffer on it
992  class MapPanel extends JPanel
993  {
994    // Override the update() and paint()
995    // methods to draw our image buffer
996    // on the appropriate graphics context
997
998    public void update(Graphics g)
999    {
1000      super.update(g);
1001      paint(g);
1002    }
1003
1004    public void paint(Graphics g)
1005    {
1006      super.paint(g);
1007      g.drawImage(imageBuffer,0,0, this);
1008    }
1009  }
1010
1011  // Define inner class that allows the JFileChooser
1012  // to filter possible file selections to those with
1013  // '.map' extensions.
1014  class MapFileFilter extends FileFilter
1015  {
1016    // Whether a file should be filtered or not
1017    public boolean accept(File f) {
1018      if (f.isDirectory()) {
1019        return true;
1020      }
1021
1022      String extension = getExtension(f);
1023      if (extension != null) {
1024        if (extension.equals("map") ||
1025            extension.equals("MAP")) {
1026          return true;
1027        } else {
1028          return false;
1029        }
1030      }
1031
1032      return false;
1033    }
1034
1035    // The description of this filter
1036    public String getDescription() {
1037      return "Map files";
1038    }
1039
1040    // Locate the file extension
1041    private String getExtension(File f)
1042    {
1043      String s = f.getName();
1044      int i = s.lastIndexOf('.');
1045      if (i > 0 &&  i < s.length() - 1)
1046        return s.substring(i+1).toLowerCase();
1047      return "";
1048    }
1049  }
1050
1051  // Define an inner class that reads map country data
1052  // from the 'setup_map' queue populated by ECLiPSe
1053  // engine during 'get_map_data' predicate call. The
1054  // uncoloured map is then drawn.
1055  class SetupMapDataListener implements QueueListener
1056  {
1057    FromEclipseQueue input_queue_stream = null;
1058    EXDRInputStream input_queue_stream_formatted = null;
1059
1060    // Called when Eclipse flushes source
1061    public void dataAvailable(Object source)
1062    {
1063      CompoundTerm data;
1064      int x1, y1, x2, y2, countryIndex;
1065      Rectangle rect;
1066
1067      // Reset the maximum size of the map
1068      maxWidth = 0;
1069      maxHeight = 0;
1070
1071      if(input_queue_stream == null)
1072      {
1073	input_queue_stream = (FromEclipseQueue) source;
1074	input_queue_stream_formatted =
1075	  new EXDRInputStream(input_queue_stream);
1076      }
1077
1078      try
1079      {
1080        // Repeatedly read terms from stream until 'end' atom is read
1081        for ( ;; ) {
1082          data = (CompoundTerm)input_queue_stream_formatted.readTerm();
1083          if (data.functor().equals("end")) break;
1084
1085          // Add a rectangle for the appropriate country - note the
1086          // index in the data structure is the country name - 1
1087          countryIndex = ((Integer)(data.arg(1))).intValue() - 1;
1088          x1 = (((Integer)(data.arg(2))).intValue() * scalingFactor) + 1;
1089          y1 = (((Integer)(data.arg(3))).intValue() * scalingFactor) + 1;
1090          x2 = (((Integer)(data.arg(4))).intValue() * scalingFactor) - 1;
1091          y2 = (((Integer)(data.arg(5))).intValue() * scalingFactor) - 1;
1092
1093          rect = new Rectangle(x1, y1, x2-x1, y2-y1);
1094          if (countries[countryIndex] == null) {
1095            countries[countryIndex] = new Vector();
1096          }
1097
1098          countries[countryIndex].addElement(rect);
1099
1100          // Update the maximum width and height of the image buffer
1101          if ( x2 > maxWidth ) maxWidth = x2;
1102          if ( y2 > maxHeight ) maxHeight = y2;
1103        }
1104      } catch(IOException ioe){
1105        System.out.println( "I/O Exception caught whilst reading data on" +
1106                            "'setup_map' queue: " +
1107                            ioe.getMessage());
1108        return;
1109      }
1110
1111      // Calculate the size of the image buffer
1112      imageBuffer = new BufferedImage(maxWidth, maxHeight,
1113                                      BufferedImage.TYPE_INT_ARGB);
1114
1115      // Draw the uncoloured map
1116      setupMap();
1117    }
1118
1119    // Required to implement QueueListener
1120    public void dataRequest(Object source)
1121    {
1122    }
1123  }
1124
1125  // Define an inner class that reads map country data
1126  // from the 'update_map' queue populated by ECLiPSe
1127  // engine during 'colouring' predicate call. The
1128  // map is then drawn with the appropriate country (re)coloured.
1129  class UpdateMapDataListener implements QueueListener
1130  {
1131    FromEclipseQueue input_queue_stream = null;
1132    EXDRInputStream input_queue_stream_formatted = null;
1133
1134    // Called when Eclipse flushes source
1135    public void dataAvailable(Object source)
1136    {
1137      CompoundTerm data;
1138
1139      if(input_queue_stream == null)
1140      {
1141	input_queue_stream = (FromEclipseQueue) source;
1142	input_queue_stream_formatted =
1143	  new EXDRInputStream(input_queue_stream);
1144      }
1145
1146      try
1147      {
1148        // Repeatedly read terms from stream while data is available
1149        // The first argument is the map name/number, the second is an
1150        // atom indicating the new colour of the country
1151        while(((InputStream) source).available() > 0)
1152        {
1153          data = (CompoundTerm)input_queue_stream_formatted.readTerm();
1154          updateMap(((Integer)(data.arg(1))).intValue() - 1,
1155            ((Atom)(data.arg(2))).functor());
1156        }
1157      } catch(IOException ioe){
1158        System.out.println( "I/O Exception caught whilst reading data on" +
1159                            "'update_map' queue: " +
1160                            ioe.getMessage());
1161      }
1162    }
1163
1164    // Required to implement QueueListener
1165    public void dataRequest(Object source)
1166    {
1167    }
1168  }
1169
1170  // Define an inner class that writes the search control data
1171  // to the 'control' queue that ECLiPSe engine reads from having
1172  // found a solution in the 'colouring' predicate call.
1173  // The call will block the thread until the user presses the 'More'
1174  // or 'Finish' button. We use the Java wait()/notify() mechanism to
1175  // coordinate this.
1176  class ControlDataProducer implements QueueListener
1177  {
1178    private ToEclipseQueue output_queue_stream = null;
1179    private EXDROutputStream output_queue_stream_formatted = null;
1180    private Atom continueAction = null;
1181
1182    // Required to implement QueueListener
1183    public void dataAvailable(Object source)
1184    {
1185    }
1186
1187    // Called when Eclipse tries to read from source when it is empty.
1188    // A solution has been found, wait for user action
1189    public synchronized void dataRequest(Object source)
1190    {
1191      int tmpSolutionCount = solutionCount + 1;
1192
1193      if(output_queue_stream == null)
1194      {
1195	output_queue_stream = (ToEclipseQueue) source;
1196	output_queue_stream_formatted =
1197	  new EXDROutputStream(output_queue_stream);
1198      }
1199
1200      statusTextArea.setText(" Status: " + tmpSolutionCount +
1201                             " solution(s) found. Continue search?");
1202      runButton.setEnabled(false);
1203      moreButton.setEnabled(true);
1204      finishButton.setEnabled(true);
1205
1206      while(continueAction == null) {
1207        try
1208        {
1209          wait();
1210        } catch(Exception expn){
1211          System.out.println( "Exception caught whilst waiting " +
1212                              "on user continue action: " +
1213                              expn.getMessage());
1214        }
1215      }
1216
1217      // Write the user action
1218      try
1219      {
1220        output_queue_stream_formatted.write(continueAction);
1221        output_queue_stream_formatted.flush();
1222      } catch(IOException ioe){
1223        System.out.println( "I/O Exception caught whilst writing " +
1224                            "continue action: " +
1225                            ioe.getMessage());
1226      }
1227
1228      continueAction = null;
1229    }
1230
1231    // The user has clicked 'More' or 'Finish' button, indicate this
1232    // action to the paused colouring thread
1233    public synchronized void writeContinueAction(String action)
1234    {
1235      continueAction = new Atom(action);
1236      notify();
1237    }
1238  }
1239}
1240