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