import java.io.File; import javafx.application.Application; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.ColorPicker; import javafx.scene.control.ComboBox; import javafx.scene.control.Label; import javafx.scene.control.Menu; import javafx.scene.control.MenuBar; import javafx.scene.control.MenuItem; import javafx.scene.control.RadioButton; import javafx.scene.control.Slider; import javafx.scene.control.ToggleGroup; import javafx.scene.layout.BorderPane; import javafx.scene.layout.FlowPane; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.stage.FileChooser; import javafx.stage.Stage; //listener: traditional named outer class // no access modifier --> default: accessible within package/project // outer: class defined within another class, // has access to all member variables/methods within the enclosing class // named: class is given a name class BnClearListener implements EventHandler { private MyCanvas canvas; public BnClearListener(MyCanvas c) { canvas = c; } public void handle(ActionEvent e) { canvas.clearCanvas(); // accessing canvas (member variable) directly } } public class PaintApp extends Application { // constants private static final int APP_WIDTH = 800; private static final int APP_HEIGHT = 650; private static final int CANVAS_HEIGHT = 600; private static final Color DEFAULT_COLOR = Color.BLACK; // GUI variables private BorderPane mainPane; private VBox vBox; private FlowPane controlPanel; private MyCanvas canvas; private Button bnClear; private Slider slSize; // stroke size private ComboBox comboStrokeType; private RadioButton rbFilled, rbHollow; private ColorPicker colorPicker; private MenuBar menuBar; private Menu fileMenu, aboutMenu; private MenuItem miOpen, miSave; private FileChooser fileChooser; // non-GUI variables private boolean isFilled = true; @Override public void start(Stage stage) { mainPane = new BorderPane(); vBox = new VBox(); mainPane.setTop(vBox); setupCanvas(); setupControls(); setupMenu(); vBox.getChildren().addAll(menuBar, controlPanel); Scene scene = new Scene(mainPane, APP_WIDTH, APP_HEIGHT); stage.setTitle("CS112 PaintApp FX"); stage.setScene(scene); stage.show(); } private void setupMenu() { menuBar = new MenuBar(); fileMenu = new Menu("File"); aboutMenu = new Menu("About"); menuBar.getMenus().addAll(fileMenu, aboutMenu); miOpen = new MenuItem("Open"); miSave = new MenuItem("Save"); fileMenu.getItems().addAll(miOpen, miSave); fileChooser = new FileChooser(); miOpen.setOnAction(e -> { System.out.println("Open a drawing from a file"); fileChooser.setTitle("Open..."); File selectedFile = fileChooser.showOpenDialog(null); if (selectedFile != null) { // canvas.openFile(selectedFile); } }); miSave.setOnAction(e -> { System.out.println("Save current drawing to a file"); fileChooser.setTitle("Save..."); File selectedFile = fileChooser.showSaveDialog(null); if (selectedFile != null) { canvas.saveTextFile(selectedFile); } }); } // listener: traditional named inner class // inner: class defined within another class, // has access to all member variables/methods within the enclosing class // named: class is given a name private class BnClearListenerI implements EventHandler { public void handle(ActionEvent e) { canvas.clearCanvas(); // accessing canvas (member variable) directly } } private void setupControls() { // Q. flow pane vs. hbox? controlPanel = new FlowPane(); // (1) size slSize = new Slider(1, 10, 4); // (2) type comboStrokeType = new ComboBox<>(); comboStrokeType.getItems().addAll("Circular", "Square"); comboStrokeType.setValue("Circular"); // (3) filled or not rbFilled = new RadioButton("Filled"); rbHollow = new RadioButton("Outline"); rbFilled.setSelected(true); // default option // mutually-exclusive ToggleGroup tgFilled = new ToggleGroup(); tgFilled.getToggles().addAll(rbFilled, rbHollow); // rbFilled.setToggleGroup(tgFilled); // rbFilled.setOnAction(e -> { Color color // if (rbFilled.isSelected()) { // isFilled = true; // } // else { // isFilled = false; // } // isFilled = rbFilled.isSelected(); // }); // rbHollow.setOnAction(e -> { // if (rbHollow.isSelected()) { // isFilled = false; Color color // } // else {olorPicker.getValue(); // isFilled = true; // } // }); // (4) color colorPicker = new ColorPicker(DEFAULT_COLOR); // (5) clear canvas bnClear = new Button("Clear Canvas"); // listener: using full lambda expression //bnClear.setOnAction(e -> { canvas.clearCanvas(); }); // listener: traditional named inner class, anonymous object //bnClear.setOnAction( new BnClearListener() ); // listener: traditional named inner class, named object //BnClearListener bnClearListener = new BnClearListener(); // listener: traditional named outer class, named object // must ensure the parameters to this class are NOT null BnClearListener bnClearListener = new BnClearListener(canvas); bnClear.setOnAction( bnClearListener ); controlPanel.getChildren().addAll( comboStrokeType, new Label("size: "), slSize, rbFilled, rbHollow, colorPicker, bnClear); } private void setupCanvas() { canvas = new MyCanvas(APP_WIDTH, CANVAS_HEIGHT); mainPane.setCenter(canvas); // listener: MouseDragged canvas.setOnMouseDragged(e -> { // 1. olorPicker.getValue();get user's mouse location from e (of MouseEvent type) int mx = (int) e.getX(); int my = (int) e.getY(); int size = (int) slSize.getValue(); boolean filled = rbFilled.isSelected(); Color color = colorPicker.getValue(); String type = comboStrokeType.getValue(); // 2. create one CircularStroke at location from (1) Stroke s = null; if (type.equals("Circular")) { s = new CircularStroke(mx, my, size, filled, color); } else { s = new SquareStroke(mx, my, size, filled, color); } // 3. add the stroke from (2) to canvas canvas.addStroke(s); }); } public static void main(String[] args) { launch(args); } }