content: package aima.gui.fx.framework;
  
  import javafx.application.Platform;
  import javafx.beans.binding.Bindings;
  import javafx.beans.property.DoubleProperty;
  import javafx.collections.ObservableList;
  import javafx.scene.Scene;
  import javafx.scene.control.Menu;
  import javafx.scene.control.MenuBar;
  import javafx.scene.control.MenuItem;
  import javafx.scene.input.KeyCode;
  import javafx.scene.input.KeyCodeCombination;
  import javafx.scene.input.KeyCombination;
  import javafx.scene.layout.BorderPane;
  import javafx.scene.layout.Pane;
  import javafx.scene.text.Font;
  import javafx.stage.Stage;
  
  /**
   * Builder for integrated applications. To create an integrated application,
   * just create a builder, define a title, define scene size, register apps (integrable JavaFX
   * applications) and demos (command line applications), and finally get the result from the
   * builder and show it.
   * 
   * @author Ruediger Lunde
   */
  public class IntegratedAppBuilder {
  
  	private MenuBar menuBar = new MenuBar();
  	private Menu appsMenu = new Menu("Apps");
  	private Menu demosMenu = new Menu("Demos");
  	private String title = "";
  	private double sceneWidth = 1200;
  	private double sceneHeight = 800;
  
  	private IntegratedAppPaneCtrl paneCtrl;
  
  	public IntegratedAppBuilder() {
  		paneCtrl = new IntegratedAppPaneCtrl();
  		final DoubleProperty scale = paneCtrl.scaleProperty();
  		MenuItem incScaleItem = new MenuItem("Inc Scale");
  		incScaleItem.setOnAction(ev -> scale.set(trunc(scale.get() * 1.3)));
  		incScaleItem.setAccelerator(new KeyCodeCombination(KeyCode.PLUS, KeyCombination.CONTROL_DOWN));
  		MenuItem decScaleItem = new MenuItem("Dec Scale");
  		decScaleItem.setOnAction(ev -> scale.set(trunc(scale.get() / 1.3)));
  		decScaleItem.setAccelerator(new KeyCodeCombination(KeyCode.MINUS, KeyCombination.CONTROL_DOWN));
  		MenuItem exitItem = new MenuItem("Exit");
  		exitItem.setOnAction(ev -> Platform.exit());
  
  		Menu fileMenu = new Menu("File");
  		fileMenu.getItems().addAll(incScaleItem, decScaleItem, exitItem);
  		menuBar.getMenus().addAll(fileMenu, appsMenu, demosMenu);
  		menuBar.styleProperty().bind(Bindings.concat("-fx-font-size: ",
  				paneCtrl.scaleProperty().multiply(Font.getDefault().getSize()).asString()));
  	}
  
  	public void defineTitle(String title) {
  		this.title = title;
  	}
  
  	public void registerApp(Class<? extends IntegrableApplication> appClass) {
  		final IntegratedAppPaneCtrl ctrl = paneCtrl;
  		MenuItem item = new MenuItem(appClass.getSimpleName());
  		item.setOnAction(ev -> ctrl.startApp(appClass));
  		addToMenu(appsMenu, appClass.getPackage().getName(), item);
  	}
  
  	public void registerDemo(Class<?> demoClass) {
  		final IntegratedAppPaneCtrl ctrl = paneCtrl;
  		MenuItem item = new MenuItem(demoClass.getSimpleName());
  		item.setOnAction(ev -> ctrl.startDemo(demoClass));
  		addToMenu(demosMenu, demoClass.getPackage().getName(), item);
  	}
  
  	public void defineSceneSize(double width, double height) {
  		sceneWidth = width;
  		sceneHeight = height;
  	}
  
  	/**
  	 * Creates a scene with menu bar and a scalable container pane,
  	 * assigns it to the stage, and returns a controller instance containing
  	 * user interface logic.
  	 * @return A controller class (will seldom be used by the caller).
  	 */
  	public IntegratedAppPaneCtrl getResultFor(Stage stage) {
  
  		// create a pane, content is affected by scale
  		final DoubleProperty scale = paneCtrl.scaleProperty();
  		BorderPane appPane = new BorderPane();
  		appPane.scaleXProperty().bind(scale);
  		appPane.scaleYProperty().bind(scale);
  
  		Pane appPaneContainer = new Pane();
  		appPaneContainer.getChildren().add(appPane);
  		appPane.prefWidthProperty().bind(appPaneContainer.widthProperty().divide(scale));
  		appPane.prefHeightProperty().bind(appPaneContainer.heightProperty().divide(scale));
  		appPane.translateXProperty()
  				.bind(appPaneContainer.widthProperty().subtract(appPane.prefWidthProperty()).divide(2));
  		appPane.translateYProperty()
  				.bind(appPaneContainer.heightProperty().subtract(appPane.prefHeightProperty()).divide(2));
  
  		paneCtrl.setContext(appPane, stage, title);
  
  		BorderPane root = new BorderPane();
  		root.setTop(menuBar);
  		root.setCenter(appPaneContainer);
  		stage.setScene(new Scene(root, sceneWidth, sceneHeight));
  		// just in case, the builder is called twice...
  		IntegratedAppPaneCtrl result = paneCtrl;
  		paneCtrl = new IntegratedAppPaneCtrl();
  		return result;
  	}
  
  	/**
  	 * Adds a new starter item to the specified menu.
  	 */
  	private MenuItem addToMenu(Menu menu, String packageName, MenuItem item) {
  		Menu subMenu = null;
  		ObservableList<MenuItem> menuComps = menu.getItems();
  		int i;
  		for (i = 0; i < menuComps.size(); i++) {
  			Menu comp = (Menu) menuComps.get(i);
  			if (comp.getText().equals(packageName))
  				subMenu = comp;
  			else if (comp.getText().compareTo(packageName) > 0)
  				break;
  		}
  		if (subMenu == null) {
  			subMenu = new Menu(packageName);
  			menu.getItems().add(i, subMenu);
  		}
  		subMenu.getItems().add(item);
  		return item;
  	}
  
  	private double trunc(double num) {
  		return Math.round(num * 2) / 2.0;
  	}
  }
  