The final applet requires the ability to stretch the package, so we begin with a new pin class that can stretch.
01: public class StretchableActivePin extends ViewerImpl { 02: private String name_; 03: private int row_; 04: private int col_; 05: private float natural_; 06: private float stretch_; 07: private float shrink_; 08: private Allocation allocation_; 09: private Glyph ring_; 10: private boolean editing_ = false; 11: private TextFieldGlyph field_ = new TextFieldGlyph(); 12: private Sgraphics s_ = new Sgraphics(); 13: 14: private float editHeight = 26; 15: private float editWidth = 80; 16: private int fontSize = s_.convertVertical(14F); 17: 18: public StretchableActivePin (String name, int row, int column, float natural, float stretch, float shrink, ViewerCallback callback) { 19: super(null); 20: name_ = name; 21: row_ = row; 22: col_ = column; 23: natural_ = natural; 24: stretch_ = stretch; 25: shrink_ = shrink; 26: 27: FigureStyle style = new FigureStyle(); 28: style.foreground = Color.yellow; 29: style.brushSize = 1; 30: 31: ring_ = LayoutKit.marginFlexible( 32: LayoutKit.flexible( 33: FigureKit.circle(FigureMode.Fill, style, 0, 0, natural_ * 0.8F), 34: (natural_ * 0.8F), 35: (natural_ * 0.8F)), 36: (natural_ * 0.2F)/2F, 37: (stretch_ * 0.2F)/2F, 38: (shrink_ * 0.2F)/2F); 39: 40: setBody(ring_); 41: 42: setCallback(callback); 43: field_.setCallback(callback); 44: field_.setFont(new Font("Courier", Font.PLAIN, fontSize)); 45: } 46: 47: public Requirements requirements () { 48: Requirements r = new Requirements(); 49: r.x.defined = true; 50: r.x.natural = natural_; 51: r.x.maximum = natural_ + stretch_; 52: r.x.minimum = natural_ - shrink_; 53: r.x.align = 0; 54: 55: r.y.defined = true; 56: r.y.natural = natural_; 57: r.y.maximum = natural_ + stretch_; 58: r.y.minimum = natural_ - shrink_; 59: r.y.align = 0; 60: return r; 61: } 62: 63: public void allocate (Allocation allocation) { 64: if (!editing_) 65: super.allocate(allocation); 66: else { 67: BoundingSpan xSpan = allocation.allocation.span(Axis.xAxis); 68: BoundingSpan ySpan = allocation.allocation.span(Axis.yAxis); 69: Allocation a = new Allocation(); 70: a.allocation = new Area(xSpan.begin, ySpan.begin + ySpan.length/2F - editHeight/2F, 0F, xSpan.begin + editWidth, ySpan.begin + ySpan.length/2F + editHeight/2F, 0F); 71: a.clipping = allocation.clipping; 72: super.allocate(a); 73: } 74: allocation_ = allocation; 75: } 76: 77: public void draw (Painter painter) { 78: super.draw(painter); 79: 80: if (allocation_ != null && !editing_) { 81: BoundingSpan xSpan = allocation_.allocation.span(Area.xAxis); 82: BoundingSpan ySpan = allocation_.allocation.span(Area.yAxis); 83: 84: float width = xSpan.length * 0.25F; 85: float height = ySpan.length * 0.25F; 86: 87: painter.setColor(Color.black); 88: painter.fillOval(xSpan.begin + xSpan.length/2F - width/2F, 89: ySpan.begin + ySpan.length/2F - height/2F, 90: width, 91: height); 92: } 93: } 94: 95: public String getName () { 96: return name_; 97: } 98: 99: public void beginEdit () { 100: if (editing_) 101: return; 102: editing_ = true; 103: 104: field_.setText(name_); 105: field_.select(0,0); 106: setBody(field_); 107: } 108: 109: public void endEdit () { 110: if (!editing_) 111: return; 112: editing_ = false; 113: name_ = field_.getText(); 114: setBody(ring_); 115: needResize(); 116: } 117: 118: public boolean editing () { 119: return editing_; 120: } 121: }
This pin is constructed on lines 18-45 with a name, row, column, and callback, like ActivePin; however, natural, shrink, and stretch values are added. natural is the starting size of the pin; shrink is the amount the pin can decrease in size; stretch is the amount the pin can increase in size. Therefore, the smallest the pin can be is natural - shrink. Instead of using an overlay, a circle is created on line 33. The circle is 80% of the natural size. Because the circle can not stretch, it is encapsulated in a flexible on lines 32-38. The flexible can shrink 80% of the natural size, i.e. to 0, and can stretch 80% of the natural size. The flexible is placed in a flexible margin that has sides equal to 10% of the natural, so that the flexible margin and the flexible together are equal to the natural. The flexible margin can stretch and shrink by 10% of the natural. The result is a circle that is 80% of the natural size inside a margin that is the natural size. When the circle stretches and shrinks, the margin will stretch and shrink proportionally with it.
Lines 42 and 43 are a little tricky. Both the text field widget and the pin use the same callback object. This allows the callback object to watch for carriage returns and line feeds in the text field, and watch for mouse events in the grid package. (See the discussion below on the callback object.)
StretchableActivePin is a glyph, and so the requirements() and allocate(Allocation) functions are implemented. requirements() simply sets up the size using natural, stretch, and shrink. Looking at lines 49-59, notice that the minimum size will be natural - shrink and that the maximum will be natural + shrink. The alignment is 0, so the pin will align on the top left corner.
When the pin name is not being edited, allcoation(Allcoation) calls it superclass on line 65, and saves the allocation on line 74. When editing, the allocation of the body, which was set to the circle with the margins on line 40, is changed to the size of the TextFieldGlyph.
beginEdit() changes the body to a TextFieldGlyph on line 106. (A TextFieldGlyph is really an AWT TextField that has a Glyph interface so that it can be embedded in the glyph tree.) The text field is centered vertically and aligned horizontally along the left edge of the pin. endEdit() changes the body back to a ring on line 114.
The draw(Painter) function calls the superclass to draw the yellow circle and then draws a smaller black circle on on lines 88-91, but only if not editing the pin name as a result of the beginEdit() call.
StretchableActivePinCallback calls the beginEdit() and endEdit() functions.
01: public class StretchableActivePinCallback implements ViewerCallback { 02: private TextField pinNameField_ = null; 03: private StretchableActivePin lastPin_ = null; 04: 05: public StretchableActivePinCallback(TextField pinNameField) { 06: pinNameField_ = pinNameField; 07: } 08: 09: public boolean mouseDown(Viewer v, GraphicsEvent evt, float X, float Y) { 10: return false; 11: } 12: 13: public boolean mouseDrag(Viewer v, GraphicsEvent evt, float X, float Y) { 14: return false; 15: } 16: 17: public boolean mouseUp(Viewer v, GraphicsEvent evt, float X, float Y) { 18: StretchableActivePin pin = (StretchableActivePin) v; 19: if (pin == lastPin_) 20: return true; 21: if (lastPin_ != null) { 22: pinNameField_.setText(lastPin_.getName()); 23: lastPin_.endEdit(); 24: lastPin_ = null; 25: return true; 26: } 27: pin.beginEdit(); 28: lastPin_ = pin; 29: return true; 30: } 31: 32: public boolean mouseMove (Viewer v, GraphicsEvent evt, float X, float Y) { 33: return false; 34: } 35: 36: public boolean mouseEnter (Viewer v, GraphicsEvent event, float x, float y) { 37: if (lastPin_ != null) 38: return true; 39: if (v instanceof StretchableActivePin) { 40: StretchableActivePin pin = (StretchableActivePin) v; 41: pinNameField_.setText(pin.getName()); 42: return true; 43: } 44: return false; 45: } 46: 47: public boolean mouseExit (Viewer v, GraphicsEvent event, float x, float y) { 48: if (lastPin_ != null) 49: return true; 50: if (v instanceof StretchableActivePin) { 51: pinNameField_.setText(""); 52: return true; 53: } 54: return false; 55: } 56: 57: public boolean keyDown(Viewer v, GraphicsEvent evt, int key) { 58: if (v instanceof TextField) { 59: Glyph field = (Glyph) v; 60: StretchableActivePin pin = (StretchableActivePin) field.getParentGlyph(); 61: if (pin.editing() && (key == 13 || key == 10)) { 62: pin.endEdit(); 63: pinNameField_.setText(pin.getName()); 64: lastPin_ = null; 65: return true; 66: } 67: } 68: return false; 69: } 70: 71: public boolean keyUp(Viewer v, GraphicsEvent evt, int key) { 72: return false; 73: } 74: 75: public boolean keyActionDown(Viewer v, GraphicsEvent evt, int key) { 76: return false; 77: } 78: 79: public boolean keyActionUp (Viewer v, GraphicsEvent evt, int key) { 80: return false; 81: } 82: 83: public boolean action (Viewer v, GraphicsEvent evt, Object what) { 84: return false; 85: } 86: }
StretchableActivePinCallback is constructed with a reference to the TextFieldGlyph used in StretchableActivePin.
mouseEnter(Viewer viewer, GraphicsEvent event, float x, float y) sets the text field to the name of the pin that called it on lines 40-41. mouseExit(Viewer viewer, GraphicsEvent event , float x, float y) clears the text field on line 51.
mouseUp(Viewer viewer, GraphicsEvent event , float x, float y) begins the editing by calling beginEdit() on line 28. If another pin was being edited, the old edit is committed by calling endEdit() on line 23.
keyDown(Viewer viewer, GraphicsEvent event , float x, float y)watches for a carriage return or a line feed and then when it sees them it calls endEdit().
In all the above calls, the Viewer that is passed to the method is really a StretchableActivePin and so it is cast as such before beginEdit() and endEdit() are called.
Also note that StretchableActivePinCallback is sharable among multiple pins, i.e. all pins use the same callback object.
Now that we have a stretchable pin and its callback, we need a stretchable package that can be resized with the mouse using handles:
01: public class GridPackage extends MonoGlyph { 02: private float graphicalSize_; 03: private StretchableActivePin selectedPin_ = null; 04: 05: public GridPackage(int gridSize, int openSize, float graphicalSize, ViewerCallback pinCallback) { 06: graphicalSize_ = graphicalSize; 07: 08: FigureStyle style = new FigureStyle(); 09: style.foreground = Color.yellow; 10: style.brushSize = 1; 11: 12: Glyph verticalBox = LayoutKit.vbox(); 13: 14: float itemSize = graphicalSize / (float) gridSize; 15: 16: for (int row = 0; row < gridSize; row++) { 17: Glyph horizontalRow = LayoutKit.hbox(); 18: for (int col = 0; col < gridSize; col++) 19: if (row >= (gridSize - openSize) / 2 && 20: row < (gridSize - openSize) / 2 + openSize && 21: col >= (gridSize - openSize) / 2 && 22: col < (gridSize - openSize) / 2 + openSize) { 23: horizontalRow.append(LayoutKit.hglue(itemSize, itemSize, itemSize)); 24: } 25: else { 26: String pinName = "Pin " + String.valueOf(row) + "X" + String.valueOf(col); 27: horizontalRow.append(new StretchableActivePin(pinName, row, col, itemSize, itemSize, itemSize, pinCallback)); 28: } 29: verticalBox.append(horizontalRow); 30: } 31: 32: ResizableFrame frame = new ResizableFrame (new Background(verticalBox, Color.gray, true, true), 33: Color.black, 34: Color.white); 35: frame.setConstraints(160F, 300F, 160F, 300F); 36: frame.lockSides(); 37: setBody(frame); 38: } 39: }
GridPackage contains the code that tiles the pins into an array, and is constructed with the size of the grid, the size of the open area in the center, the physical size on the screen, and the shared callback, on line 5. The physical size is in printers points, e.g. 72 dots per inch. On line 14 itemSize is the size of the area each pin takes, and is calculated by dividing the physical size of the package by the size of the grid. Lines 16-29 construct the grid, skipping the middle pins. As in the previous example, glue is used to hold the space in the middle. Both the glue and the active pins are equal in size, shrink, and stretch.
On line 32 a ResizableFrame is wrapped around the vertical box. This is what displays the black box with handles around the package when it is selected with the mouse. Line 35 puts some constraints on how far the package can stretch and shrink. Line 36 forces the frame to keep the package square.
Now, lets move on to the last class:
01: public class TutorialExample7 extends Applet { 02: private TextField pinNameField = new TextField(20); 03: private StretchableActivePinCallback callback = new StretchableActivePinCallback(pinNameField); 04: private GridPackage part_; 05: private Glyph hbox_; 06: private Glyph vbox_; 07: private Glyph columnBox_; 08: private Glyph rowBox_; 09: private float columnBoxHeight_; 10: private SgraphicsAdapter adapter_; 11: 12: public TutorialExample7() { 13: setLayout(new BorderLayout()); 14: setBackground(Color.white); 15: 16: part_ = new GridPackage(8, 4, 256, callback); 17: 18: columnBox_ = LayoutKit.hbox(); 19: FigureStyle style = new FigureStyle(); 20: style.foreground = Color.black; 21: style.font = new Font("Courier", Font.PLAIN, 16); 22: for (int col = 0; col < 8; col++) { 23: columnBox_.append(LayoutKit.hfil()); 24: columnBox_.append(LayoutKit.align(FigureKit.label(style, String.valueOf(col)), 0F, 0F)); 25: columnBox_.append(LayoutKit.hfil()); 26: } 27: 28: rowBox_ = LayoutKit.vbox(); 29: Toolkit kit = Toolkit.getDefaultToolkit(); 30: FontMetrics metrics = kit.getFontMetrics(style.font); 31: Sgraphics s = new Sgraphics(); 32: columnBoxHeight_ = s.convertVertical(metrics.getHeight()); 33: rowBox_.append(LayoutKit.vspace(columnBoxHeight_)); 34: for (int row = 0; row < 8; row++) { 35: rowBox_.append(LayoutKit.vfil()); 36: rowBox_.append(LayoutKit.align(FigureKit.label(style, String.valueOf((char) (row + 66))), 0F, 0F)); 37: rowBox_.append(LayoutKit.vfil()); 38: } 39: 40: vbox_ = LayoutKit.vbox(); 41: vbox_.append(LayoutKit.align(columnBox_, 0, 0)); 42: vbox_.append(LayoutKit.fixed(part_, 256F, 256F)); 43: 44: hbox_ = LayoutKit.hbox(); 45: hbox_.append(LayoutKit.vfixed(rowBox_, columnBoxHeight_ + 256F)); 46: hbox_.append(LayoutKit.hspace(4F)); 47: hbox_.append(vbox_); 48: 49: Glyph device = LayoutKit.margin(hbox_, (450F - 256F - 40F) / 2F); 50: adapter_ = new SgraphicsAdapter(device); 51: 52: add("Center", adapter_); 53: 54: pinNameField_.setEditable(false); 55: add("South", pinNameField_); 56: } 57: 58: public boolean handleEvent (Event event) { 59: if (event instanceof FrameResizeEvent) { 60: FrameResizeEvent resizeEvent = (FrameResizeEvent) event; 61: float newSize = Math.min(resizeEvent.frameWidth, resizeEvent.frameHeight); 62: 63: vbox_.empty(); 64: vbox_.append(LayoutKit.align(columnBox_, 0, 0)); 65: vbox_.append(LayoutKit.fixed(part_, newSize, newSize)); 66: 67: hbox_.empty(); 68: hbox_.append(LayoutKit.vfixed(rowBox_, columnBoxHeight_ + newSize)); 69: hbox_.append(LayoutKit.hspace(4F)); 70: hbox_.append(vbox_); 71: 72: Glyph device = LayoutKit.margin(hbox_, (450F - newSize - 40F) / 2F); 73: adapter_.setTopGlyph(device); 74: part_.needResize(); 75: return true; 76: } 77: 78: return super.handleEvent(event); 79: } 80: }
The construction of the labels on lines 12-56 is the same as the last example, except that the GridPackage is used on line 16. However, look at the handleEvent(Event) function. Line 59 watches for a FrameResizeEvent. The size is recorded on line 61, and 63-74 fix the size of the labels to the new package size. Line 74 forces the package to redraw itself. The ResizableFrame is not responsible for resizing the part. It only makes handles and the outline when dragging. When the user lets go of the handles, the FrameResizeEvent is posted.
Well, if you made it this far, congratulations on a job well done. Now it is time to write your own application with Sgraphics. You can use Sgraphics to create dialogs with the support for AWT components, make graphical applications with the figure kit, create your own figures, or mix them all.
Good luck.
For comments or questions contact Mike Jones (Mike.Jones@mass.com)