Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 150 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
150
Dung lượng
1,98 MB
Nội dung
} g2D.draw(tempElement.getShape()); // and draw it } } The change to the mouseReleased() method is exactly the same as for mousePressed(), so go ahead and modify the if condition in that method, too. The only other change you need is to make the status bar respond to the TEXT element type being set. To do this you can make a small addition to the defini- tion of the setTypePane() method in the StatusBar class: public void setTypePane(int elementType) { String text; // Text for the type pane switch(elementType) { // case labels as before case TEXT: text = “TEXT”; break; default: assert false; } typePane.setText(text); // Set the pane text } How It Works The mouseClicked() handler responds to mouse button 1 being clicked when the element type is TEXT. This method will be called after the mouseReleased() method has been called. Within the if statement that determines that the current element is of type TEXT, you create a dialog to receive the text input by calling the static showInputDialog() in the JOptionPane class. If the Cancel button is clicked in the dialog, text will be null, so in this case you do nothing. If text is not null, you create an Element.Text object at the current cursor position containing the text string that was entered in the dialog. You then add this to the model, as long as it’s not null. It’s important to reset the start and tempElement members back to null; otherwise, subsequent event-handling operations will be confused. Incidentally, although there isn’t a method to detect double-clicks on the mouse button, it’s easy to implement. The getClickCount() method for the MouseEvent object that is passed to mouseClicked() returns the click count. To respond to a double-click, you could write the following statements: if(e.getClickCount() == 2) { //Response to double-click } The other event-handling methods behave as before so far as the geometric elements are concerned, and do nothing if the element type is TEXT. You can try it out. 1022 Chapter 20 23_568744 ch20.qxd 11/23/04 9:39 PM Page 1022 theApp.getWindow().getElementColor(), // The text color new java.awt.font.TextLayout(text, font, // The bounding rectangle g2D.getFontRenderContext()).getBounds().getBounds() ); if(tempElement != null) { // If we created one theApp.getModel().add(tempElement); // add it to the model tempElement = null; // and reset the field } g2D.dispose(); // Release context resources g2D = null; start = null; } The bounding rectangle for the text is produced by the rather fearsome looking expression for the last argument to the Element.Text constructor. It’s much easier than it looks so let’s take it apart. The TextLayout constructor you are using expects three arguments: the text string, the font, and a FontRenderContext object for the context in which the text is to be displayed. You call the getBounds() method for the TextLayout object, which returns a reference to a rectangle of type Rectangle2D. Since you want a rectangle of type Rectangle to pass to the Element.Text constructor, you call the getBounds() method for the Rectangle2D object, hence the repetition in the code. Once the element has been created, you just add it to the model, and clean up the variables that you were using. You must now make sure that the other mouse event handlers do nothing when the current element is TEXT. You don’t want the XOR mode set when you are just creating text elements, for example. A simple additional condition that tests the current element type will take care of it in the mousePressed() method: public void mousePressed(MouseEvent e) { // Code to handle mouse button press start = e.getPoint(); // Save the cursor position in start if((button1Down = (e.getButton()==MouseEvent.BUTTON1)) && (theApp.getWindow().getElementType() != TEXT)) { g2D = (Graphics2D)getGraphics(); // Get graphics context g2D.setXORMode(getBackground()); // Set XOR mode } } The if expression will be true only if button 1 was pressed and the current element type is not TEXT. You can update the mouseDragged() method in a similar way: public void mouseDragged(MouseEvent e) { last = e.getPoint(); // Save cursor position if(button1Down && (theApp.getWindow().getElementType() != TEXT)) { if(tempElement == null) { // Is there an element? tempElement = createElement(start, last); // No, so create one } else { tempElement.draw(g2D); // Yes – draw to erase it tempElement.modify(start, last); // Now modify it 1021 Extending the GUI 23_568744 ch20.qxd 11/23/04 9:39 PM Page 1021 Try It Out Testing the TextDialog Class All you need to do now is recompile Sketcher and run it again. To open the text dialog, select the new toolbar button or the menu item and click in the view where you want the text to appear. You just type the text that you want and click the OK button. The text will be displayed starting at the point in the view where you clicked the mouse button. You can draw text in any of the colors — just like the geometric elements. The application window may look something like that in Figure 20-9 when the text dialog is displayed. Figure 20-9 A Font Selection Dialog You don’t really want to be stuck with a 12-point SansSerif font. You need to be able to release your cre- ativity so your sketches will astound and delight! A font dialog that pops up in response to a click on a suitable menu item should enable you to change the font for text elements to any of those available on the system. It will also give you a chance to see how you can get at and process the fonts that are avail- able. You’ll also learn more about how to add components to a dialog window. The first step is to estab- lish what the font dialog will do. You want to be able to choose the font name from those available on the system on which the application is executing. You’ll also want to select the style of the font, whether plain, bold, or italic, as well as the point size. It would also be nice to see what a font looks like before you decide to use it. The dialog will therefore need to obtain a list of the fonts available and display them in a component. It will also need a component to allow the point size to be selected and some means for choosing the style for the font. 1023 Extending the GUI 23_568744 ch20.qxd 11/23/04 9:39 PM Page 1023 This is not going to be a wimpy pathetic excuse for a dialog like those you have seen so far. This is going to be a real chunky Java programmer’s dialog. You’ll drag in a diversity of components here, just for the experience, and you’ll be building it step-by-step, as it involves quite a lot of code. Just so that you know where you’re headed, the finished dialog is shown in Figure 20-10. Figure 20-10 The component that provides the choice of font in the dialog is a Swing component of type javax.swing.JList that can display a list of any type of component. Below that is a panel holding a JLabel object, which displays a sample of the current font. The list of font names and the panel below are displayed in a split pane defined by the JSplitPane class. Here the pane is split vertically, but a JSplitPane object can also hold two panels side by side. The point size is displayed in another Swing component called a spinner, which is an object of type javax.swing.JSpinner. The choice for the font style options is provided by two radio buttons, and either, neither, or both may be selected. Finally, you have two buttons to close the dialog. You can set the foundations by defining the FontDialog class with its data members and its constructor, and then build on that. Try It Out A FontDialog Class The major work will be in the dialog class constructor. That will set up all the GUI elements as well as the necessary listeners to respond to operations with the dialog. The dialog object will need to know that the SketchFrame object that represents the Sketcher application window is the parent, so you’ll pass a SketchFrame reference to the constructor. 1024 Chapter 20 23_568744 ch20.qxd 11/23/04 9:39 PM Page 1024 Here’s the code for the outline of the FontDialog class: // Class to define a dialog to choose a font import java.awt.Font; import javax.swing.JDialog; class FontDialog extends JDialog { // Constructor public FontDialog(SketchFrame window) { // Code to initialize the data members // Code to create buttons and the button panel // Code to create the data input panel // Code to create the font choice and add it to the input panel // Code to create the font size choice and add it to the input panel // Code to create the font style checkboxes and add them to the input panel // and then some! } private Font font; // Currently selected font private int fontStyle; // Font style – Plain,Bold,Italic private int fontSize; // Font point size } You’ll be adding a few more data members shortly, but at least you know you’ll need the three that are shown here. The code to initialize the data members within the FontDialog constructor is easy. You can initialize the font member and the associated fontStyle and fontSize members from the current font that is stored in the application window: public FontDialog(SketchFrame window) { // Call the base constructor to create a modal dialog super(window, “Font Selection”, true); font = window.getCurrentFont(); // Get the current font fontStyle = font.getStyle(); // style fontSize = font.getSize(); // and size // Plus the code for the rest of the constructor } You call the base class constructor and pass the window object to it as the parent. The second argument is the title for the dialog, and the third argument determines that the dialog is modal. The getCurrentFont() method returns the font stored in the window object, and you use this to initialize the fontStyle and fontSize members; therefore, the first time you open the dialog this will be the default setting. 1025 Extending the GUI 23_568744 ch20.qxd 11/23/04 9:39 PM Page 1025 Creating the Font Dialog Buttons Next you can add the code to the constructor that will create the button panel with the OK and Cancel buttons. You can place the button panel at the bottom of the content pane for the dialog using the default BorderLayout manager: public FontDialog(SketchFrame window) { // Initialization as before // Create the dialog button panel JPanel buttonPane = new JPanel(); // Create a panel to hold buttons // Create and add the buttons to the buttonPane buttonPane.add(ok = createButton(“OK”)); // Add the OK button buttonPane.add(cancel = createButton(“Cancel”)); // Add the Cancel button getContentPane().add(buttonPane, BorderLayout.SOUTH);// Add pane to content pane // Plus the code for the rest of the constructor } The buttonPane object will have a FlowLayout manager by default, so this will take care of positioning the buttons. You add the button pane to the dialog content pane using the BorderLayout.SOUTH speci- fication to place it at the bottom of the window. Because creating each button involves several steps that are the same for both buttons, you are using a helper method, createButton(), that requires only the button label as an argument. You can see that you store each button reference in a class field, so you must add these as members of the FontDialog class: private JButton ok; // OK button private JButton cancel; // Cancel button You’ll use these fields in the listener for the button events, as you’ll see in a moment. You can code the createButton() method as a member of the FontDialog class as follows: JButton createButton(String label) { JButton button = new JButton(label); // Create the button button.setPreferredSize(new Dimension(80,20)); // Set the size button.addActionListener(this); // Listener is the dialog return button; // Return the button } You set the preferred size of the button here to ensure that the buttons are all of the same size. Without this call, each button would be sized to fit its label, so the dialog would look a bit untidy. The listener is the FontDialog class object, so the FontDialog class must implement the ActionListener interface, which implies that an actionPerformed() method must be defined in the class: import java.awt.Font; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.event.ActionListener; import java.awt.event.ActionEvent; import javax.swing.JDialog; 1026 Chapter 20 23_568744 ch20.qxd 11/23/04 9:39 PM Page 1026 import javax.swing.JButton; import javax.swing.JPanel; class FontDialog extends JDialog implements ActionListener { // Constructor definition // createButton() definition public void actionPerformed(ActionEvent e) { if(e.getSource()== ok) { // Is it the OK button? ((SketchFrame)getOwner()).setCurrentFont(font); // Set the selected font } // Now hide the dialog - for ok or cancel setVisible(false); } // Plus the rest of the class definition } The getSource() member of the ActionEvent object e returns a reference to the object that originated the event, so you can use this to determine the button for which the method is being called. You just compare the source object (which is holding the reference to the object to which the event applies) to the OK button object to determine whether it was clicked. If it is the OK button, you call the setCurrentFont() method in the SketchFrame object that is the parent for this dialog to set the font. You then just hide the dialog so Sketcher can continue. This will be the sole action when the Cancel but- ton is selected for the dialog. Of course, you must add the definition of setCurrentFont() to the SketchFrame class: // Method to set the current font public void setCurrentFont(Font font) { this.font = font; } Let’s now get back to the FontDialog constructor. Adding the Data Pane You can now add a panel to contain the components that will receive input. You’ll be using a JList object for the font names, a JSpinner object for the point size of the font, and two JRadioButton objects for selecting the font style. You can add the code to create the panel first: public FontDialog(SketchFrame window) { // Initialization as before // Button panel code as before // Code to create the data input panel JPanel dataPane = new JPanel(); // Create the data entry panel dataPane.setBorder(BorderFactory.createCompoundBorder( // Create pane border BorderFactory.createLineBorder(Color.BLACK), BorderFactory.createEmptyBorder(5, 5, 5, 5))); GridBagLayout gbLayout = new GridBagLayout(); // Create the layout dataPane.setLayout(gbLayout); // Set the pane layout GridBagConstraints constraints = new GridBagConstraints(); // Plus the code for the rest of the constructor } 1027 Extending the GUI 23_568744 ch20.qxd 11/23/04 9:39 PM Page 1027 Here you use a GridBagLayout manager so you can set constraints for each component that you add to the dataPane container. You also set a black line border for dataPane with an inset empty border 5 pix- els wide. This uses the BorderFactory static methods that you have seen before. You have many other possible layout managers that you could use here. BoxLayout managers are very easy to use to lay out components in vertical columns and horizontal rows. The first component that you’ll add to dataPane will be a label that prompts for the font selection: public FontDialog(SketchFrame window) { // Initialization as before // Button panel code as before // Set up the data input panel to hold all input components as before // Code to create the font choice and add it to the input panel JLabel label = new JLabel(“Choose a Font”); constraints.fill = GridBagConstraints.HORIZONTAL; constraints.gridwidth = GridBagConstraints.REMAINDER; gbLayout.setConstraints(label, constraints); dataPane.add(label); // Plus the code for the rest of the constructor } With the fill constraint set as HORIZONTAL, the components in a row will fill the width of the dataPane container, but without affecting the height. With the width constraint set to REMAINDER, the label component will fill the width of the row. You need a few more import statements in the FontDialog source file, so add the following statements: import java.awt.Color; import java.awt.GridBagLayout; import java.awt.GridBagConstraints; import javax.swing.JLabel; import javax.swing.BorderFactory; Implementing the Font List You’ll add the JList object that displays the list of fonts next, but you won’t add this directly to the dataPane panel because the list is likely to be long enough to need scrolling capability. The list of fonts will have to be obtained using the GraphicsEnvironment object that encapsulates information about the system in which the application is running. You’ll recall that you call a static method in the GraphicsEnvironment class to get the GraphicsEnvironment object. Here’s the code to create the list of font names: public FontDialog(SketchFrame window) { // Initialization as before // Button panel code as before // Set up the data input panel to hold all input components as before // Add the font choice prompt label as before 1028 Chapter 20 23_568744 ch20.qxd 11/23/04 9:39 PM Page 1028 // Code to set up font list choice component GraphicsEnvironment e = GraphicsEnvironment.getLocalGraphicsEnvironment(); String[] fontNames = e.getAvailableFontFamilyNames(); // Get the font names fontList = new JList(fontNames); // Create list of font names fontList.setValueIsAdjusting(true); // single event selection fontList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);// Choose 1 font fontList.setSelectedValue(font.getFamily(),true); fontList.addListSelectionListener(this); JScrollPane chooseFont = new JScrollPane(fontList); // Scrollable list chooseFont.setMinimumSize(new Dimension(300,100)); chooseFont.setWheelScrollingEnabled(true); // Enable mouse wheel scroll // Plus the code for the rest of the constructor } You obtain the list of font family names for the system on which Sketcher is running by calling the getAvailableFontFamilyNames() method for the GraphicsEnvironment object. The fontList variable will need to be accessible in the method-handling events for the list, so this will be another data member of the class: private JList fontList; // Font list The fontNames array holds String objects, but you can create a JList object for any kind of object— images, for example. You can also create a JList object by passing a Vector<> object that contains the objects you want in the list to the constructor. It is possible to allow multiple entries from a list to be selected, in which case the selection process may cause multiple events—when you drag the cursor over several list items, for example. You can make certain that there is only one event for a selection, even though multiple items are selected, by calling the setValueIsAdjusting() method with the argument true. Calling setSelectionMode() with the argument SINGLE_SELECTION ensures that only one font name can be selected. You have two possible multiple selections that you can enable for a JList object. Passing the value SINGLE_INTERVAL_SELECTION to the setSelectionMode() method allows a series of consecutive items to be selected. Passing MULTIPLE_SELECTION_INTERVAL provides you with total flexibility and allows any number of items anywhere to be selected. The initial selection in the list is set by the setSelectedValue() call. You pass the family name for the current font as the argument specifying the initial selection. There is a complementary method, getSelectedValue(), that you’ll be using in the event handler. There’s a special kind of listener for JList selection events that is an object of a class type that imple- ments the ListSelectionListener interface. Since you set the FontDialog object as the listener for the list in the call to the addListSelectionListener() method, you had better make sure the FontDialog class implements the interface: class FontDialog extends JDialog implements ActionListener, // For buttons etc. ListSelectionListener { // For list box 1029 Extending the GUI 23_568744 ch20.qxd 11/23/04 9:39 PM Page 1029 the FontDialog constructor. A JScrollPane object creates a pane with scrollbars — either vertical, hori- zontal, or both —as necessary for whatever it contains. You set a minimum size for the JScrollPane object to limit how small it can be made in the split pane into which you’ll insert it in a moment. Note how easy it is to get the mouse wheel supported for scrolling here. You just call the setWheelScrollingEnabled() method for the scroll pane with the argument as true, and it’s done. The new code that you’ve added requires a few more import statements: import java.awt.GraphicsEnvironment; import javax.swing.JList; import javax.swing.ListSelectionModel; import javax.swing.JScrollPane; import javax.swing.event.ListSelectionListener; import javax.swing.event.ListSelectionEvent; Displaying the Selected Font You’ll display the selected font in a JLabel object that you’ll place in another JPanel pane. Adding the following code to the constructor will do this: public FontDialog(SketchFrame window) { // Initialization as before // Button panel code as before // Set up the data input panel to hold all input components as before // Add the font choice prompt label as before // Set up font list choice component as before // Panel to display font sample JPanel display = new JPanel(); fontDisplay = new JLabel(“Sample Size: x X y Y z Z”); fontDisplay.setPreferredSize(new Dimension(300,100)); display.add(fontDisplay); // Plus the code for the rest of the constructor } You create the JPanel object display and add the JLabel object fontDisplay to it. Remember, you update this object in the valueChanged() handler for selections from the list of font names. You’ll also be updating it when the font size or style is changed. The fontDisplay object just represents some sam- ple text. You can choose something different if you like. It’s not strictly necessary here but just for the experience you’ll use a split pane to hold the scroll pane containing the list, chooseFont, and the display panel. Using a Split Pane A JSplitPane object represents a pane with a movable horizontal or vertical split, so that it can hold two components. The split pane divider can be adjusted by dragging it with the mouse. Here’s the code to do that: public FontDialog(SketchFrame window) { // Initialization as before 1031 Extending the GUI 23_568744 ch20.qxd 11/23/04 9:39 PM Page 1031 [...]... you could write: g2D.getTransform().setToRotation(30*Math.PI/ 180 ); This statement gets the current transform object for g2D and sets it to be the rotation specified by the expression 30*Math.PI/ 180 Since ( radians is 180 degrees, this expression produces the equivalent of 30 degrees measured in radians Table continued on following page 1 055 Chapter 20 Transform Default Description setToRotation( double... JPopupMenu(“Element”); private JMenuItem moveItem, deleteItem,rotateItem, sendToBackItem; 10 48 Chapter 20 // Process a rotate } else if(source == sendToBackItem) { // Process a send-to-back } } Of course, you’ll need two more import statements in the SketchView .java file: import java. awt.event.ActionEvent; import java. awt.event.ActionListener; To pop the menu you need to modify the code in the processPopupTrigger()... to actionPerformed() in the SketchView class looks like this: public void actionPerformed(ActionEvent e) { Object source = e.getSource(); 1 050 Extending the GUI You must also add import statements for JPopupMenu and JMenuItem: import javax.swing.JPopupMenu; import javax.swing.JMenuItem; You can create the elementPopup context menu in the SketchView constructor: public SketchView(Sketcher theApp) { this.theApp... radians, which is the same as a rotation of – 45 degrees Rotation angles are expressed in radians, and a positive angle rotates everything from the positive x-axis toward the positive y-axis — therefore clockwise The rotation in the illustration is negative and therefore counterclockwise ❑ A scaling transformation corresponding to an x scale of 2 .5 and a y scale of 1 .5 ❑ A shearing operation where only the... SketcherConstants class to define this: public final static int pointSizeMin = 8; public final static int pointSizeMax = 24; public final static int pointSizeStep = 2; // Minimum font point size // Maximum font point size // Point size step Thus the smallest point size that can be chosen is 8, the largest is 24, and the step from 8 onwards is 2 You create a JSpinner object by passing a SpinnerModel reference... Buttons The inner class, StyleListener, in the FontDialog class will work on principles that you have seen before A radio button (or a checkbox) generates events of type java. awt.ItemEvent, and the listener class must implement the java. awt.ItemListener interface: class StyleListener implements ItemListener { public StyleListener(int style) { this.style = style; } public void itemStateChanged(ItemEvent... isn’t really difficult — there are just a lot of parentheses to keep in sync You now need to add import statements for the ChangeListener interface and the ChangeEvent class: import javax.swing.event.ChangeListener; import javax.swing.event.ChangeEvent; Using Radio Buttons to Select the Font Style Two JRadioButton objects will provide the means for selecting the font style One will select bold or not,... deselected It then derives a font with the new style, sets it in the fontDisplay label, and repaints it This code calls for two more import statements in the FontDialog source file: import java. awt.event.ItemListener; import java. awt.event.ItemEvent; You have now completed the FontDialog class If you have been creating the code yourself, now would be a good time to try compiling the class to see what missing... topleft corner of the dialog window one-third of the way in from the top and left sides of the application window It then calls setVisible() for the dialog object to display it 10 38 Extending the GUI Figure 20-11 Pop-Up Menus The javax.swing package defines the JPopupMenu class, which represents a menu that you can pop up at any position within a component, but conventionally you display it at the current... use another Swing component, a javax.swing.JSpinner object A JSpinner object displays a sequence of numbers or objects and the user can select any one from the set The spinner displays up and down arrows at the side of the spinner for stepping through the list You can also use the keyboard up and down arrow keys for this The sequence of choices in a spinner is managed by a javax.swing.SpinnerModel object . in the class: import java. awt.Font; import java. awt.BorderLayout; import java. awt.Dimension; import java. awt.event.ActionListener; import java. awt.event.ActionEvent; import javax.swing.JDialog; 1026 Chapter. statements: import java. awt.GraphicsEnvironment; import javax.swing.JList; import javax.swing.ListSelectionModel; import javax.swing.JScrollPane; import javax.swing.event.ListSelectionListener; import javax.swing.event.ListSelectionEvent; Displaying. add the following statements: import java. awt.Color; import java. awt.GridBagLayout; import java. awt.GridBagConstraints; import javax.swing.JLabel; import javax.swing.BorderFactory; Implementing