// VoronoiTool.java // by Scot Drysdale, Spring/Summer 2006 // This is an applet to allow the user to draw Voronoi diagrams. // Sites can be points or line segments. // Metrics include Euclidean, L_1, L_infinity, and Aspect Ratio. // Closest or farthest diagrams can be drawn. // Based on a graphics editor written by Tom Cormen for CS 5, HW 3. // Much of the GUI code and shape ideas are from that editor. import java.awt.*; import java.awt.geom.*; import java.awt.event.*; import javax.swing.*; import javax.swing.border.EtchedBorder; import javax.swing.border.TitledBorder; public class VoronoiTool extends JApplet { private static final long serialVersionUID = 1L; // Elim. warning private static final int APPLET_WIDTH = 601, APPLET_HEIGHT = 755; private Command cmd; // the command being executed private Command emptyCmd; // the singleton Command to do nothing private Drawing dwg; // the drawing: shapes in order private JRadioButton mouseInputButton; // Indicates mouse input private JRadioButton textInputButton; // Indicates text input private JRadioButton closestButton; // Indicates mouse input private JRadioButton farthestButton; // Indicates text input public void init() { cmd = emptyCmd = new Command(); // all methods in Command are empty // The drawing will appear in an off-white CanvasPanel. CanvasPanel canvasPanel = new CanvasPanel(); canvasPanel.setBackground(new Color(225,225,225)); dwg = new Drawing(canvasPanel); // make an empty drawing // Make JButton objects for all the command buttons. JButton pointButton = new JButton("Point"); JButton segButton = new JButton("Segment"); JButton polyButton = new JButton("Polygon"); JButton kGonButton = new JButton("Regular k-gon"); JButton moveButton = new JButton("Move"); JButton deleteButton = new JButton("Delete"); JButton deleteAreaButton = new JButton("Delete Area"); JButton euclideanButton = new JButton("Euclid."); JButton lOneButton = new JButton("L_1"); JButton lInfButton = new JButton("L_inf"); JButton aspectRatioButton = new JButton("Asp. Ratio"); JButton angularButton = new JButton("Angular"); JButton resetButton = new JButton("Reset"); // Add listeners for all the command buttons. polyButton.addActionListener(new PolyButtonListener()); kGonButton.addActionListener(new KGonButtonListener()); pointButton.addActionListener(new PointButtonListener()); segButton.addActionListener(new SegButtonListener()); moveButton.addActionListener(new MoveButtonListener()); deleteButton.addActionListener(new DeleteButtonListener()); deleteAreaButton.addActionListener(new DeleteAreaButtonListener()); euclideanButton.addActionListener(new EuclideanButtonListener()); lOneButton.addActionListener(new LOneButtonListener()); lInfButton.addActionListener(new LInfButtonListener()); aspectRatioButton.addActionListener(new AspectRatioButtonListener()); angularButton.addActionListener(new AngularButtonListener()); resetButton.addActionListener(new ResetButtonListener()); // The command buttons will be arranged in 3 rows. // Each row will appear in its own JPanel, and the 3 JPanels will // be stacked vertically. JPanel shapePanel = new JPanel(); // holds buttons for adding shapes JLabel shapeLabel = new JLabel("Add shape:"); shapePanel.setLayout(new FlowLayout()); shapePanel.add(shapeLabel); polyButton.setBackground(Color.cyan); polyButton.setToolTipText("Input successive poly vertices. Close by repeating first vertex"); kGonButton.setBackground(Color.cyan); kGonButton.setToolTipText("Draws a regular k-gon, given center, radius, and k"); pointButton.setBackground(Color.cyan); pointButton.setToolTipText("Input the point"); segButton.setBackground(Color.cyan); segButton.setToolTipText("Input a pair of points"); shapePanel.add(pointButton); shapePanel.add(segButton); shapePanel.add(polyButton); shapePanel.add(kGonButton); JPanel editPanel = new JPanel(); // holds buttons for editing operations JLabel editLabel = new JLabel("Edit:"); editPanel.setLayout(new FlowLayout()); editPanel.add(editLabel); moveButton.setBackground(Color.cyan); moveButton.setToolTipText("Drag a shape"); deleteButton.setBackground(Color.cyan); deleteButton.setToolTipText("Delete a shape"); deleteAreaButton.setBackground(Color.cyan); deleteAreaButton.setToolTipText("Delete all shapes in chosen rectangle"); resetButton.setBackground(Color.cyan); resetButton.setToolTipText("Clear all shapes"); editPanel.add(moveButton); editPanel.add(deleteButton); editPanel.add(deleteAreaButton); editPanel.add(resetButton); JPanel diagramPanel = new JPanel(); JLabel diagramLabel = new JLabel("Draw"); diagramPanel.setLayout(new FlowLayout()); diagramPanel.add(diagramLabel); euclideanButton.setBackground(Color.cyan); euclideanButton.setToolTipText("Draw Euclidean Voronoi Diagram"); lOneButton.setBackground(Color.cyan); lOneButton.setToolTipText("Draw L_1 metric Voronoi Diagram"); lInfButton.setBackground(Color.cyan); lInfButton.setToolTipText("Draw L_infinity metric Voronoi Diagram"); aspectRatioButton.setBackground(Color.cyan); aspectRatioButton.setToolTipText("Draw aspect ratio Voronoi Diagram"); angularButton.setBackground(Color.cyan); angularButton.setToolTipText("Draw angular Voronoi Diagram"); diagramPanel.add(euclideanButton); diagramPanel.add(lOneButton); diagramPanel.add(lInfButton); diagramPanel.add(aspectRatioButton); diagramPanel.add(angularButton); // Use a grid layout to stack the button panels vertically. // Also, give them a cyan background. JPanel buttonPanel = new JPanel(); buttonPanel.setLayout(new GridLayout(3, 1)); shapePanel.setBackground(Color.cyan); editPanel.setBackground(Color.cyan); diagramPanel.setBackground(Color.cyan); buttonPanel.add(shapePanel); buttonPanel.add(editPanel); buttonPanel.add(diagramPanel); // Create a panel for radio buttons to decide input mode mouseInputButton = new JRadioButton("Mouse Input"); mouseInputButton.setSelected(true); mouseInputButton.setToolTipText("Input point coordinates via the mouse"); mouseInputButton.setBackground(Color.cyan); mouseInputButton.addActionListener(new InputButtonListener()); textInputButton = new JRadioButton("Text Input"); textInputButton.setToolTipText("Input point coordinates via dialog boxes"); textInputButton.setBackground(Color.cyan); textInputButton.addActionListener(new InputButtonListener()); // Add the radio buttons to a button group to ensure that // only one button is selected at a time. ButtonGroup inputGroup = new ButtonGroup(); inputGroup.add(mouseInputButton); inputGroup.add(textInputButton); // A panel for the input radio buttons. Put a titled and etched border around it. JPanel inputModePanel = new JPanel(); inputModePanel.setLayout(new GridLayout(2, 1)); inputModePanel.setBackground(Color.cyan); inputModePanel.add(mouseInputButton); inputModePanel.add(textInputButton); inputModePanel.setBorder(new TitledBorder(new EtchedBorder(), "Input mode")); // Create a panel for radio buttons to decide diagram type (closest, farthest) closestButton = new JRadioButton("Closest VD"); closestButton.setSelected(true); closestButton.setToolTipText("Closest-type Voronoi Diagram"); closestButton.setBackground(Color.cyan); closestButton.addActionListener(new TypeButtonListener()); farthestButton = new JRadioButton("Farthest VD"); farthestButton.setToolTipText("Farthest-type Voronoi Diagram"); farthestButton.setBackground(Color.cyan); farthestButton.addActionListener(new TypeButtonListener()); // Add the radio buttons to a button group to ensure that // only one button is selected at a time. ButtonGroup typeGroup = new ButtonGroup(); typeGroup.add(closestButton); typeGroup.add(farthestButton); // A panel for the type radio buttons. Put a titled and etched border around it. JPanel typeModePanel = new JPanel(); typeModePanel.setLayout(new GridLayout(2, 1)); typeModePanel.setBackground(Color.cyan); typeModePanel.add(closestButton); typeModePanel.add(farthestButton); typeModePanel.setBorder(new TitledBorder(new EtchedBorder(), "Diagram type")); // A panel to hold the two radio button groups, one over the other JPanel radioButtonPanel = new JPanel(); radioButtonPanel.setLayout(new GridLayout(2, 1)); radioButtonPanel.setBackground(Color.cyan); radioButtonPanel.add(inputModePanel); radioButtonPanel.add(typeModePanel); // Put all the controls in one panel JPanel controlsPanel = new JPanel(); controlsPanel.setBackground(Color.cyan); controlsPanel.add(buttonPanel); controlsPanel.add(radioButtonPanel); // Now we have two panels: buttonPanel and canvasPanel. We want buttonPanel // to appear above canvasPanel, and canvasPanel should grow with the applet. Container cp = getContentPane(); cp.setLayout(new BorderLayout()); cp.add(controlsPanel, BorderLayout.NORTH); cp.add(canvasPanel, BorderLayout.CENTER); setSize(APPLET_WIDTH, APPLET_HEIGHT); } // Paint the page public void paint(Graphics page) { super.paint(page); // make all the GUI components paint themselves } // Input a Double from a dialog box. // message is message displayed in the dialog box // min and max give the range of acceptable values // Returns null if box is cancelled public Double inputDouble(String message, double min, double max) { String input = JOptionPane.showInputDialog(message + " (between " + min + " and " + max + ")"); double value = min - 1; while(input != null && (value < min || value > max)) { try { value = Double.parseDouble(input); } catch (NumberFormatException exception) { JOptionPane.showMessageDialog(null, "Invalid number"); value = -1; } if(value < min || value > max) input = JOptionPane.showInputDialog(message + " (between " + min + " and " + max + ")"); } if(input == null) return null; else return new Double(value); } // What to do when PolyButton is pressed. private class PolyButtonListener implements ActionListener { public void actionPerformed(ActionEvent event) { dwg.setTempShape(null); // In case aborted shape add if(mouseInputButton.isSelected()) cmd = new AddPoly(); // current command is AddPoly else { cmd = new Command(); // No current command Double xCoord = inputDouble("x-coord. of first point", 0, dwg.getWidth()); if(xCoord == null) return; Double yCoord = inputDouble("y-coord. of first point", 0, dwg.getHeight()); if(yCoord == null) return; PointShape pOrig = new PointShape( new Point2D.Double(xCoord, yCoord)); PointShape last = pOrig; while(true) // Loop adding segments until closed or cancelled { xCoord = inputDouble("x-coord. of next point", 0, dwg.getWidth()); if(xCoord == null) return; yCoord = inputDouble("y-coord. of next point", 0, dwg.getHeight()); if(yCoord == null) return; PointShape current = new PointShape( new Point2D.Double(xCoord, yCoord)); if(pOrig.euclideanSq(current.getPoint()) > 0) // Continue current polygon? { dwg.add(new Segment(last, current)); last = current; repaint(); } else // Close polygon { dwg.add(new Segment(last, pOrig)); repaint(); return; } } } } } // What to do when KGonButton is pressed. private class KGonButtonListener implements ActionListener { public void actionPerformed(ActionEvent event) { dwg.setTempShape(null); // In case aborted shape add if(mouseInputButton.isSelected()) { cmd = new AddKGon(); // current command is AddPoly repaint(); JOptionPane.showMessageDialog(null, "Enter center of polygon, then point on circumcircle"); } else { cmd = new Command(); // No current command Double centerX = inputDouble("x-coord. of k-gon center", 0, dwg.getWidth()); if(centerX == null) return; Double centerY = inputDouble("y-coord. of k-gon center", 0, dwg.getHeight()); if(centerY == null) return; Double radius = inputDouble("radius of k-gon circumcircle", 0, dwg.getHeight()); if(radius == null) return; AddKGon.createKGon( new PointShape(new Point2D.Double(centerX, centerY)), radius, dwg); repaint(); } } } // What to do when ovalButton is pressed. private class PointButtonListener implements ActionListener { public void actionPerformed(ActionEvent event) { dwg.setTempShape(null); // In case aborted shape add if(mouseInputButton.isSelected()) { cmd = new AddPoint(); // current command is AddPoint } else { cmd = new Command(); // No current command Double xCoord = inputDouble("x-coord. of point", 0, dwg.getWidth()); if(xCoord == null) return; Double yCoord = inputDouble("y-coord. of point", 0, dwg.getHeight()); if(yCoord == null) return; dwg.add(new PointShape(new Point2D.Double(xCoord, yCoord))); } repaint(); } } // What to do when lineButton is pressed. private class SegButtonListener implements ActionListener { public void actionPerformed(ActionEvent event) { dwg.setTempShape(null); // In case aborted shape add if(mouseInputButton.isSelected()) cmd = new AddSeg(); // current command is AddSeg else { cmd = new Command(); // No current command Double xCoord1 = inputDouble("x-coord. of first endpoint", 0, dwg.getWidth()); if(xCoord1 == null) return; Double yCoord1 = inputDouble("y-coord. of first endpoint", 0, dwg.getHeight()); if(yCoord1 == null) return; Double xCoord2 = inputDouble("x-coord. of second endpoint", 0, dwg.getWidth()); if(xCoord2 == null) return; Double yCoord2 = inputDouble("y-coord. of second endpoint", 0, dwg.getHeight()); if(yCoord2 == null) return; dwg.add(new Segment( new PointShape(new Point2D.Double(xCoord1, yCoord1)), new PointShape(new Point2D.Double(xCoord2, yCoord2)))); } repaint(); } } // What to do when moveButton is pressed. private class MoveButtonListener implements ActionListener { public void actionPerformed(ActionEvent event) { cmd = new MoveCmd(); // current command is MoveCmd dwg.setTempShape(null); // In case aborted shape add repaint(); } } // What to do when deleteButton is pressed. private class DeleteButtonListener implements ActionListener { public void actionPerformed(ActionEvent event) { cmd = new DeleteCmd(); // current command is DeleteCmd dwg.setTempShape(null); // In case aborted shape add repaint(); } } // What to do when deleteAreaButton is pressed. private class DeleteAreaButtonListener implements ActionListener { public void actionPerformed(ActionEvent event) { cmd = new DeleteAreaCmd(); // current command is DeleteCmd dwg.setTempShape(null); // In case aborted shape add repaint(); } } // What to do when an input radio button is pressed. private class InputButtonListener implements ActionListener { public void actionPerformed(ActionEvent event) { cmd = new Command(); // No current command dwg.setTempShape(null); // In case aborted shape add repaint(); } } // What to do when a type radio button is pressed. private class TypeButtonListener implements ActionListener { public void actionPerformed(ActionEvent event) { cmd = new Command(); // No current command dwg.setTempShape(null); // In case aborted shape add repaint(); } } // What to do when euclideanButton is pressed. private class EuclideanButtonListener implements ActionListener { public void actionPerformed(ActionEvent event) { cmd = emptyCmd; // current command is none dwg.setDrawVD(); dwg.setMetric(new Euclidean()); dwg.setClosest(closestButton.isSelected()); dwg.setTempShape(null); // In case aborted shape add repaint(); } } // What to do when lOne is pressed. private class LOneButtonListener implements ActionListener { public void actionPerformed(ActionEvent event) { cmd = emptyCmd; // current command is none dwg.setDrawVD(); dwg.setMetric(new LOne()); dwg.setClosest(closestButton.isSelected()); dwg.setTempShape(null); // In case aborted shape add repaint(); } } // What to do when lInf is pressed. private class LInfButtonListener implements ActionListener { public void actionPerformed(ActionEvent event) { cmd = emptyCmd; // current command is none dwg.setDrawVD(); dwg.setMetric(new LInf()); dwg.setClosest(closestButton.isSelected()); dwg.setTempShape(null); // In case aborted shape add repaint(); } } // What to do when aspectRatioButton is pressed. private class AspectRatioButtonListener implements ActionListener { public void actionPerformed(ActionEvent event) { cmd = emptyCmd; // current command is none dwg.setDrawVD(); dwg.setMetric(new AspectRatio()); dwg.setClosest(closestButton.isSelected()); dwg.setTempShape(null); // In case aborted shape add repaint(); } } // What to do when angularButton is pressed. private class AngularButtonListener implements ActionListener { public void actionPerformed(ActionEvent event) { cmd = emptyCmd; // current command is none dwg.setDrawVD(); dwg.setMetric(new Angular()); dwg.setClosest(closestButton.isSelected()); dwg.setTempShape(null); // In case aborted shape add repaint(); } } // What to do when resetButton is pressed. private class ResetButtonListener implements ActionListener { public void actionPerformed(ActionEvent event) { cmd = emptyCmd; // current command is none dwg.reset(); repaint(); } } // CanvasPanel is the class upon which we actually draw. // It listens for mouse events and calls the appropriate method of the current // command. private class CanvasPanel extends JPanel implements MouseListener, MouseMotionListener { private static final long serialVersionUID = 1L; // elim. warning // Constructor just needs to set up the CanvasPanel as a listener. public CanvasPanel() { addMouseListener(this); addMouseMotionListener(this); } public void paint(Graphics page) { super.paint(page); // execute the paint method of JPanel dwg.draw(page); // have the drawing draw itself } // When the mouse is clicked, call the executeClick method of the current command. public void mouseClicked(MouseEvent event) { cmd.executeClick(event.getPoint(), dwg); if(cmd != emptyCmd) // Only repaint if performing a command repaint(); } // When the mouse is pressed, call the executePress method of the current command. public void mousePressed(MouseEvent event) { cmd.executePress(event.getPoint(), dwg); if(cmd != emptyCmd) // Only repaint if performing a command repaint(); } // When the mouse is dragged, call the executeDrag method of the current command. public void mouseDragged(MouseEvent event) { cmd.executeDrag(event.getPoint(), dwg); if(cmd != emptyCmd) // Only repaint if performing a command repaint(); } public void mouseMoved(MouseEvent event) { cmd.executeMove(event.getPoint(), dwg); if(cmd != emptyCmd) // Only repaint if performing a command repaint(); } // We don't care about the other mouse events. public void mouseReleased(MouseEvent event) { } public void mouseEntered(MouseEvent event) { } public void mouseExited(MouseEvent event) { } } }