diff --git a/extras/src/build/conf/checkstyle.xml b/extras/src/build/conf/checkstyle.xml index eced04f..d77f404 100644 --- a/extras/src/build/conf/checkstyle.xml +++ b/extras/src/build/conf/checkstyle.xml @@ -149,7 +149,9 @@ - + + + diff --git a/extras/src/main/java/edu/umd/cs/piccolox/PFrame.java b/extras/src/main/java/edu/umd/cs/piccolox/PFrame.java index 69b214f..8290411 100644 --- a/extras/src/main/java/edu/umd/cs/piccolox/PFrame.java +++ b/extras/src/main/java/edu/umd/cs/piccolox/PFrame.java @@ -58,7 +58,7 @@ * @author Jesse Grosjean */ public class PFrame extends JFrame { - private static final Dimension DEFAULT_FRAME_DIMENSION = new Dimension(400,400); + private static final Dimension DEFAULT_FRAME_DIMENSION = new Dimension(400, 400); private static final Point DEFAULT_FRAME_POSITION = new Point(100, 100); @@ -160,7 +160,7 @@ * @return default frame bounds */ public Rectangle getDefaultFrameBounds() { - return new Rectangle(DEFAULT_FRAME_POSITION, DEFAULT_FRAME_DIMENSION); + return new Rectangle(DEFAULT_FRAME_POSITION, DEFAULT_FRAME_DIMENSION); } /** @@ -334,7 +334,7 @@ * Method for testing the creating of PFrame. * * @deprecated since it's not terribly useful - * + * * @param argv command line arguments */ public static void main(final String[] argv) { diff --git a/extras/src/main/java/edu/umd/cs/piccolox/event/PNotification.java b/extras/src/main/java/edu/umd/cs/piccolox/event/PNotification.java index 93853d5..92fdaaf 100644 --- a/extras/src/main/java/edu/umd/cs/piccolox/event/PNotification.java +++ b/extras/src/main/java/edu/umd/cs/piccolox/event/PNotification.java @@ -54,11 +54,20 @@ * @author Jesse Grosjean */ public class PNotification { - + /** Name of the notification. */ protected String name; + /** The Object associated with this notification. */ protected Object source; + /** A free form map of properties to attach to this notification. */ protected Map properties; + /** + * Creates a notification. + * + * @param name Arbitrary name of the notification + * @param source object associated with this notification + * @param properties free form map of information about the notification + */ public PNotification(final String name, final Object source, final Map properties) { this.name = name; this.source = source; @@ -67,7 +76,9 @@ /** * Return the name of the notification. This is the same as the name used to - * register with the notfication center. + * register with the notification center. + * + * @return name of notification */ public String getName() { return name; @@ -75,14 +86,19 @@ /** * Return the object associated with this notification. This is most often - * the same object that posted the notfication. It may be null. + * the same object that posted the notification. It may be null. + * + * @return object associated with this notification */ public Object getObject() { return source; } /** - * Return a property associated with the notfication. + * Return a property associated with the notification, or null if not found. + * + * @param key key used for looking up the property + * @return value associated with the key or null if not found */ public Object getProperty(final Object key) { if (properties != null) { diff --git a/extras/src/main/java/edu/umd/cs/piccolox/event/PNotificationCenter.java b/extras/src/main/java/edu/umd/cs/piccolox/event/PNotificationCenter.java index 26e9185..39a961f 100644 --- a/extras/src/main/java/edu/umd/cs/piccolox/event/PNotificationCenter.java +++ b/extras/src/main/java/edu/umd/cs/piccolox/event/PNotificationCenter.java @@ -54,20 +54,23 @@ * listeners don't need to know about the event source, and the event source * doesn't need to maintain the list of listeners. *

- * Listeners of the notfications center are held by weak references. So the - * notfication center will not create garbage collection problems as standard + * Listeners of the notifications center are held by weak references. So the + * notification center will not create garbage collection problems as standard * java event listeners do. *

* * @author Jesse Grosjean */ public class PNotificationCenter { - + /** Used as a place holder for null names or objects. */ public static final Object NULL_MARKER = new Object(); + /** Singleton instance of the notification center. */ protected static PNotificationCenter DEFAULT_CENTER; + /** A map of listeners keyed by NotificationKey objects. */ protected HashMap listenersMap; + protected ReferenceQueue keyQueue; /** @@ -87,10 +90,6 @@ keyQueue = new ReferenceQueue(); } - // **************************************************************** - // Add Listener Methods - // **************************************************************** - /** * Registers the 'listener' to receive notifications with the name * 'notificationName' and/or containing 'object'. When a matching @@ -106,19 +105,18 @@ * @param object source of notification messages that this listener is * interested in * @return true if listener has been added - * @throws SecurityException when attempting to register method as listener - * that is not accessible */ public boolean addListener(final Object listener, final String callbackMethodName, final String notificationName, - final Object object) throws SecurityException { + final Object object) { processKeyQueue(); - Object name = nullify(notificationName); - Object sanitizedObject = nullify(object); + final Object name = nullify(notificationName); + final Object sanitizedObject = nullify(object); - Method method = extractCallbackMethod(listener, callbackMethodName); - if (method == null) + final Method method = extractCallbackMethod(listener, callbackMethodName); + if (method == null) { return false; + } final NotificationKey key = new NotificationKey(name, sanitizedObject); final NotificationTarget notificationTarget = new NotificationTarget(listener, method); @@ -139,7 +137,9 @@ private Method extractCallbackMethod(final Object listener, final String methodName) { Method method = null; try { - method = listener.getClass().getMethod(methodName, new Class[] { PNotification.class }); + Class[] classes = new Class[1]; + classes[0] = PNotification.class; + method = listener.getClass().getMethod(methodName, classes); } catch (final NoSuchMethodException e) { return null; @@ -153,6 +153,14 @@ return method; } + /** + * Sanitizes the object reference by returning NULL_MARKER if the object is + * null. + * + * @param object object to sanitize + * + * @return NULL_MARKER is object is null, otherwise object + */ private Object nullify(final Object object) { if (object == null) { return NULL_MARKER; @@ -297,9 +305,11 @@ } } - private void notifyListener(final PNotification notification, NotificationTarget listener) { + private void notifyListener(final PNotification notification, final NotificationTarget listener) { try { - listener.getMethod().invoke(listener.get(), new Object[] { notification }); + Object[] objects = new Object[1]; + objects[0] = notification; + listener.getMethod().invoke(listener.get(), objects); } catch (final IllegalAccessException e) { throw new RuntimeException("Impossible Situation: invoking inaccessible method on listener", e); @@ -363,6 +373,10 @@ } } + /** + * Iterates over available keys in the key queue and removes the queue from + * the listener map. + */ protected void processKeyQueue() { NotificationKey key; while ((key = (NotificationKey) keyQueue.poll()) != null) { @@ -370,30 +384,64 @@ } } + /** + * Represents a notification type from a particular object. + */ protected static class NotificationKey extends WeakReference { private final Object name; private final int hashCode; - public NotificationKey(final Object aName, final Object anObject) { - super(anObject); - name = aName; - hashCode = aName.hashCode() + anObject.hashCode(); + /** + * Creates a notification key with the provided name associated to the + * object given. + * + * @param name name of notification + * @param object associated object + */ + public NotificationKey(final Object name, final Object object) { + super(object); + this.name = name; + hashCode = name.hashCode() + object.hashCode(); } - public NotificationKey(final Object aName, final Object anObject, final ReferenceQueue aQueue) { - super(anObject, aQueue); - name = aName; - hashCode = aName.hashCode() + anObject.hashCode(); + /** + * Creates a notification key with the provided name associated with the + * provided object. + * + * @param name name of notification + * @param object associated object + * @param queue + */ + public NotificationKey(final Object name, final Object object, final ReferenceQueue queue) { + super(object, queue); + this.name = name; + hashCode = name.hashCode() + object.hashCode(); } + /** + * Returns name of notification this key represents. + * + * @return name of notification + */ public Object name() { return name; } + /** {@inheritDoc} */ public int hashCode() { return hashCode; } + /** + * Two keys are equal if they have the same name and are associated with + * the same object and conform to all other equals rules. + * + * @param anObject object being tested for equivalence to this + * NotificationKey + * + * @return true if this object is logically equivalent to the one passed + * in + */ public boolean equals(final Object anObject) { if (this == anObject) { return true; @@ -414,16 +462,34 @@ return object != null && object == key.get(); } + /** + * Returns a nice string representation of this notification key. + * + * @return string representation of this notification key + */ public String toString() { return "[CompoundKey:" + name() + ":" + get() + "]"; } } + /** + * A NotificationTarget is a method on a particular object that can be + * invoked. + */ protected static class NotificationTarget extends WeakReference { - + /** Cached hashcode value computed at construction time. */ protected int hashCode; + + /** Method to be invoked on the object. */ protected Method method; + /** + * Creates a notification target representing the method on the + * particular object provided. + * + * @param object object on which method can be invoked + * @param method method to be invoked + */ public NotificationTarget(final Object object, final Method method) { super(object); hashCode = object.hashCode() + method.hashCode(); @@ -452,6 +518,8 @@ * Returns true if this object is logically equivalent to the one passed * in. For this to happen they must have the same method and object. * + * @param object object being tested for logical equivalency to this one + * * @return true if logically equivalent */ public boolean equals(final Object object) { diff --git a/extras/src/main/java/edu/umd/cs/piccolox/event/PSelectionEventHandler.java b/extras/src/main/java/edu/umd/cs/piccolox/event/PSelectionEventHandler.java index 32fb907..993d2a8 100644 --- a/extras/src/main/java/edu/umd/cs/piccolox/event/PSelectionEventHandler.java +++ b/extras/src/main/java/edu/umd/cs/piccolox/event/PSelectionEventHandler.java @@ -510,7 +510,13 @@ } } - protected void initializeMarquee(final PInputEvent e) { + /** + * Creates an empty marquee child for use in displaying the marquee around + * the selection. + * + * @param event event responsible for the initialization + */ + protected void initializeMarquee(final PInputEvent event) { marquee = PPath.createRectangle((float) presspt.getX(), (float) presspt.getY(), 0, 0); marquee.setPaint(marqueePaint); marquee.setTransparency(marqueePaintTransparency); @@ -521,10 +527,20 @@ marqueeMap.clear(); } - protected void startOptionMarqueeSelection(final PInputEvent e) { + /** + * Invoked when the marquee is being used to extend the selection. + * + * @param event event causing the option selection + */ + protected void startOptionMarqueeSelection(final PInputEvent event) { } - protected void startMarqueeSelection(final PInputEvent e) { + /** + * Invoked at the start of the selection. Removes any selections. + * + * @param event event causing a new marquee selection + */ + protected void startMarqueeSelection(final PInputEvent event) { unselectAll(); } @@ -735,7 +751,7 @@ } /** - * Ends the "pressed" state of the previously pressed node (if any) + * Ends the "pressed" state of the previously pressed node (if any). * * @param e event responsible for the end in the selection */ @@ -745,7 +761,7 @@ /** * This gets called continuously during the drag, and is used to animate the - * marquee + * marquee. * * @param aEvent event responsible for this step in the drag sequence */ @@ -762,21 +778,18 @@ } /** - * Delete selection when delete key is pressed (if enabled) + * Delete selection when delete key is pressed (if enabled). * * @param e the key press event */ public void keyPressed(final PInputEvent e) { - switch (e.getKeyCode()) { - case KeyEvent.VK_DELETE: - if (deleteKeyActive) { - final Iterator selectionEn = selection.keySet().iterator(); - while (selectionEn.hasNext()) { - final PNode node = (PNode) selectionEn.next(); - node.removeFromParent(); - } - selection.clear(); - } + if (e.getKeyCode() == KeyEvent.VK_DELETE && deleteKeyActive) { + final Iterator selectionEn = selection.keySet().iterator(); + while (selectionEn.hasNext()) { + final PNode node = (PNode) selectionEn.next(); + node.removeFromParent(); + } + selection.clear(); } } @@ -799,7 +812,7 @@ } /** - * Specifies if the DELETE key should delete the selection + * Specifies if the DELETE key should delete the selection. * * @param deleteKeyActive state to set for the delete action true = enabled */ @@ -824,9 +837,10 @@ } /** - * Returns true if the node intersects with this Filter's configured bounds. + * Returns true if the node is an acceptable selection. * * @param node node being tested + * @return true if node is an acceptable selection */ public boolean accept(final PNode node) { localBounds.setRect(bounds); @@ -840,6 +854,9 @@ /** * Returns whether this filter should accept all children of a node. + * + * @param node node being tested + * @return true if selection should accept children children of the node */ public boolean acceptChildrenOf(final PNode node) { return selectableParents.contains(node) || isCameraLayer(node); diff --git a/extras/src/main/java/edu/umd/cs/piccolox/event/PStyledTextEventHandler.java b/extras/src/main/java/edu/umd/cs/piccolox/event/PStyledTextEventHandler.java index f28f86b..ae8cffc 100644 --- a/extras/src/main/java/edu/umd/cs/piccolox/event/PStyledTextEventHandler.java +++ b/extras/src/main/java/edu/umd/cs/piccolox/event/PStyledTextEventHandler.java @@ -64,21 +64,27 @@ * @author Lance Good */ public class PStyledTextEventHandler extends PBasicInputEventHandler { - + /** Canvas onto which this event handler is attached. */ protected PCanvas canvas; + /** Editor used to edit a PStyledText's content when it is in edit mode. */ protected JTextComponent editor; + /** + * A listener that will handle programatic changes to the underlying + * document and update the view accordingly. + */ protected DocumentListener docListener; + /** The Styled text being edited. */ protected PStyledText editedText; /** - * Basic constructor for PStyledTextEventHandler + * Basic constructor for PStyledTextEventHandler. + * + * @param canvas canvas to which this handler will be attached */ public PStyledTextEventHandler(final PCanvas canvas) { - super(); - final PInputEventFilter filter = new PInputEventFilter(); filter.setOrMask(InputEvent.BUTTON1_MASK | InputEvent.BUTTON3_MASK); setEventFilter(filter); @@ -88,7 +94,10 @@ /** * Constructor for PStyledTextEventHandler that allows an editor to be - * specified + * specified. + * + * @param canvas canvas to which this handler will be attached + * @param editor component to display when editing a PStyledText node */ public PStyledTextEventHandler(final PCanvas canvas, final JTextComponent editor) { super(); @@ -97,6 +106,13 @@ initEditor(editor); } + /** + * Installs the editor onto the canvas. Making it the editor that will be + * used whenever a PStyledText node needs editing. + * + * @param newEditor component responsible for a PStyledText node while it is + * being edited. + */ protected void initEditor(final JTextComponent newEditor) { editor = newEditor; @@ -107,34 +123,23 @@ docListener = createDocumentListener(); } + /** + * Creates a default editor component to be used when editing a PStyledText + * node. + * + * @return a freshly created JTextComponent subclass that can be used to + * edit PStyledText nodes + */ protected JTextComponent createDefaultEditor() { - final JTextPane tComp = new JTextPane() { - - /** - * - */ - private static final long serialVersionUID = 1L; - - /** - * Set some rendering hints - if we don't then the rendering can be - * inconsistent. Also, Swing doesn't work correctly with fractional - * metrics. - */ - public void paint(final Graphics g) { - final Graphics2D g2 = (Graphics2D) g; - - g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); - g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); - g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_OFF); - - super.paint(g); - } - }; - tComp.setBorder(new CompoundBorder(new LineBorder(Color.black), new EmptyBorder(3, 3, 3, 3))); - return tComp; + return new DefaultTextEditor(); } + /** + * Returns a document listener that will reshape the editor whenever a + * change occurs to its attached document. + * + * @return a DocumentListener + */ protected DocumentListener createDocumentListener() { return new DocumentListener() { public void removeUpdate(final DocumentEvent e) { @@ -151,6 +156,12 @@ }; } + /** + * Creates a PStyledText instance and attaches a simple document to it. If + * possible, it configures its font information too. + * + * @return a new PStyledText instance + */ public PStyledText createText() { final PStyledText newText = new PStyledText(); @@ -173,27 +184,41 @@ || !doc.getDefaultRootElement().getAttributes().isDefined(StyleConstants.FontSize); } - public void mousePressed(final PInputEvent inputEvent) { - final PNode pickedNode = inputEvent.getPickedNode(); + /** + * A callback that is invoked any time the mouse is pressed on the canvas. + * If the press occurs directly on the canvas, it create a new PStyledText + * instance and puts it in editing mode. If the click is on a node, it marks + * changes it to editing mode. + * + * @param event mouse click event that can be queried + */ + public void mousePressed(final PInputEvent event) { + final PNode pickedNode = event.getPickedNode(); - stopEditing(inputEvent); + stopEditing(event); - if (inputEvent.getButton() != MouseEvent.BUTTON1) { + if (event.getButton() != MouseEvent.BUTTON1) { return; } if (pickedNode instanceof PStyledText) { - startEditing(inputEvent, (PStyledText) pickedNode); + startEditing(event, (PStyledText) pickedNode); } else if (pickedNode instanceof PCamera) { final PStyledText newText = createText(); final Insets pInsets = newText.getInsets(); - newText.translate(inputEvent.getPosition().getX() - pInsets.left, inputEvent.getPosition().getY() - - pInsets.top); - startEditing(inputEvent, newText); + newText.translate(event.getPosition().getX() - pInsets.left, event.getPosition().getY() - pInsets.top); + startEditing(event, newText); } } + /** + * Begins editing the provided text node as a result of the provided event. + * Will swap out the text node for an editor. + * + * @param event the event responsible for starting the editing + * @param text text node being edited + */ public void startEditing(final PInputEvent event, final PStyledText text) { // Get the node's top right hand corner final Insets pInsets = text.getInsets(); @@ -217,6 +242,11 @@ editedText = text; } + /** + * Stops editing the current text node. + * + * @param event the event responsible for stopping the editing + */ public void stopEditing(final PInputEvent event) { if (editedText == null) { return; @@ -242,7 +272,13 @@ editedText = null; } - public void dispatchEventToEditor(final PInputEvent e) { + /** + * Intercepts Piccolo2D events and dispatches the underlying swing one to + * the current editor. + * + * @param event the swing event being intercepted + */ + public void dispatchEventToEditor(final PInputEvent event) { // We have to nest the mouse press in two invoke laters so that it is // fired so that the component has been completely validated at the new // size and the mouse event has the correct offset @@ -250,10 +286,10 @@ public void run() { SwingUtilities.invokeLater(new Runnable() { public void run() { - final MouseEvent me = new MouseEvent(editor, MouseEvent.MOUSE_PRESSED, e.getWhen(), e + final MouseEvent me = new MouseEvent(editor, MouseEvent.MOUSE_PRESSED, event.getWhen(), event .getModifiers() - | InputEvent.BUTTON1_MASK, (int) (e.getCanvasPosition().getX() - editor.getX()), - (int) (e.getCanvasPosition().getY() - editor.getY()), 1, false); + | InputEvent.BUTTON1_MASK, (int) (event.getCanvasPosition().getX() - editor.getX()), + (int) (event.getCanvasPosition().getY() - editor.getY()), 1, false); editor.dispatchEvent(me); } }); @@ -261,24 +297,36 @@ }); } + /** + * Adjusts the shape of the editor to fit the current document. + */ public void reshapeEditor() { if (editedText != null) { - // Update the size to fit the new document - note that it is a 2 - // stage process Dimension prefSize = editor.getPreferredSize(); - final Insets pInsets = editedText.getInsets(); - final Insets jInsets = editor.getInsets(); + final Insets textInsets = editedText.getInsets(); + final Insets editorInsets = editor.getInsets(); - final int width = editedText.getConstrainWidthToTextWidth() ? (int) prefSize.getWidth() : (int) (editedText - .getWidth() - - pInsets.left - pInsets.right + jInsets.left + jInsets.right + 3.0); + final int width; + if (editedText.getConstrainWidthToTextWidth()) { + width = (int) prefSize.getWidth(); + } + else { + width = (int) (editedText.getWidth() - textInsets.left - textInsets.right + editorInsets.left + + editorInsets.right + 3.0); + } prefSize.setSize(width, prefSize.getHeight()); editor.setSize(prefSize); prefSize = editor.getPreferredSize(); - final int height = editedText.getConstrainHeightToTextHeight() ? (int) prefSize.getHeight() - : (int) (editedText.getHeight() - pInsets.top - pInsets.bottom + jInsets.top + jInsets.bottom + 3.0); + final int height; + if (editedText.getConstrainHeightToTextHeight()) { + height = (int) prefSize.getHeight(); + } + else { + height = (int) (editedText.getHeight() - textInsets.top - textInsets.bottom + editorInsets.top + + editorInsets.bottom + 3.0); + } prefSize.setSize(width, height); editor.setSize(prefSize); } @@ -286,7 +334,7 @@ /** * Sometimes we need to invoke this later because the document events seem - * to get fired before the text is actually incorporated into the document + * to get fired before the text is actually incorporated into the document. */ protected void reshapeEditorLater() { SwingUtilities.invokeLater(new Runnable() { @@ -296,4 +344,27 @@ }); } + private static final class DefaultTextEditor extends JTextPane { + private static final long serialVersionUID = 1L; + + public DefaultTextEditor() { + setBorder(new CompoundBorder(new LineBorder(Color.black), new EmptyBorder(3, 3, 3, 3))); + } + + /** + * Set some rendering hints - if we don't then the rendering can be + * inconsistent. Also, Swing doesn't work correctly with fractional + * metrics. + */ + public void paint(final Graphics g) { + final Graphics2D g2 = (Graphics2D) g; + + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_OFF); + + super.paint(g); + } + } } diff --git a/extras/src/main/java/edu/umd/cs/piccolox/event/PZoomToEventHandler.java b/extras/src/main/java/edu/umd/cs/piccolox/event/PZoomToEventHandler.java index 6b86b56..8e90303 100644 --- a/extras/src/main/java/edu/umd/cs/piccolox/event/PZoomToEventHandler.java +++ b/extras/src/main/java/edu/umd/cs/piccolox/event/PZoomToEventHandler.java @@ -45,18 +45,31 @@ * @author Jesse Grosjean */ public class PZoomToEventHandler extends PBasicInputEventHandler { + private static final int ZOOM_SPEED = 500; + /** + * Constructs a PZoomToEventHandler that only recognizes BUTTON1 events. + */ public PZoomToEventHandler() { setEventFilter(new PInputEventFilter(InputEvent.BUTTON1_MASK)); } - public void mousePressed(final PInputEvent aEvent) { - zoomTo(aEvent); + /** + * Zooms the camera's view to the pressed node when button 1 is pressed. + * + * @param event event representing the mouse press + */ + public void mousePressed(final PInputEvent event) { + zoomTo(event); } - protected void zoomTo(final PInputEvent aEvent) { + /** + * Zooms the camera to the picked node of the event. + * @param event Event from which to extract the zoom target + */ + protected void zoomTo(final PInputEvent event) { PBounds zoomToBounds; - final PNode picked = aEvent.getPickedNode(); + final PNode picked = event.getPickedNode(); if (picked instanceof PCamera) { final PCamera c = (PCamera) picked; @@ -66,6 +79,6 @@ zoomToBounds = picked.getGlobalFullBounds(); } - aEvent.getCamera().animateViewToCenterBounds(zoomToBounds, true, 500); + event.getCamera().animateViewToCenterBounds(zoomToBounds, true, ZOOM_SPEED); } } diff --git a/extras/src/main/java/edu/umd/cs/piccolox/handles/PBoundsHandle.java b/extras/src/main/java/edu/umd/cs/piccolox/handles/PBoundsHandle.java index bdf0781..22f68bb 100644 --- a/extras/src/main/java/edu/umd/cs/piccolox/handles/PBoundsHandle.java +++ b/extras/src/main/java/edu/umd/cs/piccolox/handles/PBoundsHandle.java @@ -55,98 +55,113 @@ * @author Jesse Grosjean */ public class PBoundsHandle extends PHandle { + private static final long serialVersionUID = 1L; /** - * + * Event handler responsible for changing the mouse when it enters the + * handle. */ - private static final long serialVersionUID = 1L; private transient PBasicInputEventHandler handleCursorHandler; - public static void addBoundsHandlesTo(final PNode aNode) { - aNode.addChild(new PBoundsHandle(PBoundsLocator.createEastLocator(aNode))); - aNode.addChild(new PBoundsHandle(PBoundsLocator.createWestLocator(aNode))); - aNode.addChild(new PBoundsHandle(PBoundsLocator.createNorthLocator(aNode))); - aNode.addChild(new PBoundsHandle(PBoundsLocator.createSouthLocator(aNode))); - aNode.addChild(new PBoundsHandle(PBoundsLocator.createNorthEastLocator(aNode))); - aNode.addChild(new PBoundsHandle(PBoundsLocator.createNorthWestLocator(aNode))); - aNode.addChild(new PBoundsHandle(PBoundsLocator.createSouthEastLocator(aNode))); - aNode.addChild(new PBoundsHandle(PBoundsLocator.createSouthWestLocator(aNode))); + /** + * Adds bounds handles to the corners and edges of the provided node. + * + * @param node node to be extended with bounds handles + */ + public static void addBoundsHandlesTo(final PNode node) { + node.addChild(new PBoundsHandle(PBoundsLocator.createEastLocator(node))); + node.addChild(new PBoundsHandle(PBoundsLocator.createWestLocator(node))); + node.addChild(new PBoundsHandle(PBoundsLocator.createNorthLocator(node))); + node.addChild(new PBoundsHandle(PBoundsLocator.createSouthLocator(node))); + node.addChild(new PBoundsHandle(PBoundsLocator.createNorthEastLocator(node))); + node.addChild(new PBoundsHandle(PBoundsLocator.createNorthWestLocator(node))); + node.addChild(new PBoundsHandle(PBoundsLocator.createSouthEastLocator(node))); + node.addChild(new PBoundsHandle(PBoundsLocator.createSouthWestLocator(node))); } - public static void addStickyBoundsHandlesTo(final PNode aNode, final PCamera camera) { - camera.addChild(new PBoundsHandle(PBoundsLocator.createEastLocator(aNode))); - camera.addChild(new PBoundsHandle(PBoundsLocator.createWestLocator(aNode))); - camera.addChild(new PBoundsHandle(PBoundsLocator.createNorthLocator(aNode))); - camera.addChild(new PBoundsHandle(PBoundsLocator.createSouthLocator(aNode))); - camera.addChild(new PBoundsHandle(PBoundsLocator.createNorthEastLocator(aNode))); - camera.addChild(new PBoundsHandle(PBoundsLocator.createNorthWestLocator(aNode))); - camera.addChild(new PBoundsHandle(PBoundsLocator.createSouthEastLocator(aNode))); - camera.addChild(new PBoundsHandle(PBoundsLocator.createSouthWestLocator(aNode))); + /** + * Adds stick handles (always visible regardless of scale since they are + * attached to the camera) to the node provided. + * + * @param node node being extended with bounds handles + * @param camera camera onto which handles will appear + */ + public static void addStickyBoundsHandlesTo(final PNode node, final PCamera camera) { + camera.addChild(new PBoundsHandle(PBoundsLocator.createEastLocator(node))); + camera.addChild(new PBoundsHandle(PBoundsLocator.createWestLocator(node))); + camera.addChild(new PBoundsHandle(PBoundsLocator.createNorthLocator(node))); + camera.addChild(new PBoundsHandle(PBoundsLocator.createSouthLocator(node))); + camera.addChild(new PBoundsHandle(PBoundsLocator.createNorthEastLocator(node))); + camera.addChild(new PBoundsHandle(PBoundsLocator.createNorthWestLocator(node))); + camera.addChild(new PBoundsHandle(PBoundsLocator.createSouthEastLocator(node))); + camera.addChild(new PBoundsHandle(PBoundsLocator.createSouthWestLocator(node))); } - public static void removeBoundsHandlesFrom(final PNode aNode) { + /** + * Removes all bounds from the node provided. + * + * @param node node having its handles removed from + */ + public static void removeBoundsHandlesFrom(final PNode node) { final ArrayList handles = new ArrayList(); - final Iterator i = aNode.getChildrenIterator(); + final Iterator i = node.getChildrenIterator(); while (i.hasNext()) { final PNode each = (PNode) i.next(); if (each instanceof PBoundsHandle) { handles.add(each); } } - aNode.removeChildren(handles); + node.removeChildren(handles); } - public PBoundsHandle(final PBoundsLocator aLocator) { - super(aLocator); + /** + * Creates a bounds handle that will be attached to the provided locator. + * + * @param locator locator used to position the node + */ + public PBoundsHandle(final PBoundsLocator locator) { + super(locator); } + /** + * Installs the handlers to this particular bounds handle. + */ protected void installHandleEventHandlers() { super.installHandleEventHandlers(); - handleCursorHandler = new PBasicInputEventHandler() { - boolean cursorPushed = false; - - public void mouseEntered(final PInputEvent aEvent) { - if (!cursorPushed) { - aEvent.pushCursor(getCursorFor(((PBoundsLocator) getLocator()).getSide())); - cursorPushed = true; - } - } - - public void mouseExited(final PInputEvent aEvent) { - if (cursorPushed) { - final PPickPath focus = aEvent.getInputManager().getMouseFocus(); - - if (focus == null || focus.getPickedNode() != PBoundsHandle.this) { - aEvent.popCursor(); - cursorPushed = false; - } - } - } - - public void mouseReleased(final PInputEvent event) { - if (cursorPushed) { - event.popCursor(); - cursorPushed = false; - } - } - }; + handleCursorHandler = new MouseCursorUpdateHandler(); addInputEventListener(handleCursorHandler); } /** * Return the event handler that is responsible for setting the mouse cursor * when it enters/exits this handle. + * + * @return current handler responsible for changing the mouse cursor */ public PBasicInputEventHandler getHandleCursorEventHandler() { return handleCursorHandler; } + /** + * Is invoked when the a drag starts on this handle. + * + * @param aLocalPoint point in the handle's coordinate system that is + * pressed + * @param aEvent event representing the start of the drag + */ public void startHandleDrag(final Point2D aLocalPoint, final PInputEvent aEvent) { final PBoundsLocator l = (PBoundsLocator) getLocator(); l.getNode().startResizeBounds(); } + /** + * Is invoked when the handle is being dragged. + * + * @param aLocalDimension dimension representing the magnitude of the handle + * drag + * @param aEvent event responsible for the call + */ public void dragHandle(final PDimension aLocalDimension, final PInputEvent aEvent) { final PBoundsLocator l = (PBoundsLocator) getLocator(); @@ -196,6 +211,8 @@ case SwingConstants.SOUTH_EAST: b.setRect(b.x, b.y, b.width + dx, b.height + dy); break; + default: + throw new RuntimeException("Invalid side returned from PBoundsLocator"); } boolean flipX = false; @@ -220,11 +237,26 @@ n.setBounds(b); } + /** + * Call back invoked when the drag is finished. + * + * @param aLocalPoint point on the handle where the drag was ended + * @param aEvent event responsible for the end of the drag + */ public void endHandleDrag(final Point2D aLocalPoint, final PInputEvent aEvent) { final PBoundsLocator l = (PBoundsLocator) getLocator(); l.getNode().endResizeBounds(); } + /** + * Moves locators around so that they are still logically positioned. + * + * This is needed when a node is resized until its width or height is + * negative. + * + * @param flipX whether to allow flipping along the x direction + * @param flipY whether to allow flipping along the y direction + */ public void flipSiblingBoundsHandles(final boolean flipX, final boolean flipY) { final Iterator i = getParent().getChildrenIterator(); while (i.hasNext()) { @@ -235,98 +267,108 @@ } } + /** + * Flips this bounds around if it needs to be. This is required when a node + * is resized until either its height or width is negative. + * + * @param flipX whether to allow flipping along the x direction + * @param flipY whether to allow flipping along the y direction + */ public void flipHandleIfNeeded(final boolean flipX, final boolean flipY) { final PBoundsLocator l = (PBoundsLocator) getLocator(); - if (flipX || flipY) { - switch (l.getSide()) { - case SwingConstants.NORTH: { - if (flipY) { - l.setSide(SwingConstants.SOUTH); - } - break; - } + if (!flipX && !flipY) { + return; + } - case SwingConstants.SOUTH: { - if (flipY) { - l.setSide(SwingConstants.NORTH); - } - break; + switch (l.getSide()) { + case SwingConstants.NORTH: + if (flipY) { + l.setSide(SwingConstants.SOUTH); } + break; - case SwingConstants.EAST: { - if (flipX) { - l.setSide(SwingConstants.WEST); - } - break; + case SwingConstants.SOUTH: + if (flipY) { + l.setSide(SwingConstants.NORTH); } + break; - case SwingConstants.WEST: { - if (flipX) { - l.setSide(SwingConstants.EAST); - } - break; + case SwingConstants.EAST: + if (flipX) { + l.setSide(SwingConstants.WEST); } + break; - case SwingConstants.NORTH_WEST: { - if (flipX && flipY) { - l.setSide(SwingConstants.SOUTH_EAST); - } - else if (flipX) { - l.setSide(SwingConstants.NORTH_EAST); - } - else if (flipY) { - l.setSide(SwingConstants.SOUTH_WEST); - } - - break; + case SwingConstants.WEST: + if (flipX) { + l.setSide(SwingConstants.EAST); } + break; - case SwingConstants.SOUTH_WEST: { - if (flipX && flipY) { - l.setSide(SwingConstants.NORTH_EAST); - } - else if (flipX) { - l.setSide(SwingConstants.SOUTH_EAST); - } - else if (flipY) { - l.setSide(SwingConstants.NORTH_WEST); - } - break; + case SwingConstants.NORTH_WEST: + if (flipX && flipY) { + l.setSide(SwingConstants.SOUTH_EAST); } + else if (flipX) { + l.setSide(SwingConstants.NORTH_EAST); + } + else if (flipY) { + l.setSide(SwingConstants.SOUTH_WEST); + } + break; - case SwingConstants.NORTH_EAST: { - if (flipX && flipY) { - l.setSide(SwingConstants.SOUTH_WEST); - } - else if (flipX) { - l.setSide(SwingConstants.NORTH_WEST); - } - else if (flipY) { - l.setSide(SwingConstants.SOUTH_EAST); - } - break; + case SwingConstants.SOUTH_WEST: + if (flipX && flipY) { + l.setSide(SwingConstants.NORTH_EAST); } + else if (flipX) { + l.setSide(SwingConstants.SOUTH_EAST); + } + else if (flipY) { + l.setSide(SwingConstants.NORTH_WEST); + } + break; - case SwingConstants.SOUTH_EAST: { - if (flipX && flipY) { - l.setSide(SwingConstants.NORTH_WEST); - } - else if (flipX) { - l.setSide(SwingConstants.SOUTH_WEST); - } - else if (flipY) { - l.setSide(SwingConstants.NORTH_EAST); - } - break; + case SwingConstants.NORTH_EAST: + if (flipX && flipY) { + l.setSide(SwingConstants.SOUTH_WEST); } - } + else if (flipX) { + l.setSide(SwingConstants.NORTH_WEST); + } + else if (flipY) { + l.setSide(SwingConstants.SOUTH_EAST); + } + break; + + case SwingConstants.SOUTH_EAST: + if (flipX && flipY) { + l.setSide(SwingConstants.NORTH_WEST); + } + else if (flipX) { + l.setSide(SwingConstants.SOUTH_WEST); + } + else if (flipY) { + l.setSide(SwingConstants.NORTH_EAST); + } + break; + + default: + throw new RuntimeException("Invalid side received from PBoundsLocator"); } // reset locator to update layout setLocator(l); } + /** + * Returns an appropriate handle for the given side of a node. + * + * @param side side given as SwingConstants values. + * + * @return Appropriate cursor, or null if none can be identified. + */ public Cursor getCursorFor(final int side) { switch (side) { case SwingConstants.NORTH: @@ -352,7 +394,56 @@ case SwingConstants.SOUTH_EAST: return new Cursor(Cursor.SE_RESIZE_CURSOR); + default: + return null; } - return null; + } + + private class MouseCursorUpdateHandler extends PBasicInputEventHandler { + boolean cursorPushed; + + public MouseCursorUpdateHandler() { + cursorPushed = false; + } + + /** + * When mouse is entered, push appropriate mouse cursor on cursor stack. + * + * @param aEvent the mouse entered event + */ + public void mouseEntered(final PInputEvent aEvent) { + if (!cursorPushed) { + aEvent.pushCursor(getCursorFor(((PBoundsLocator) getLocator()).getSide())); + cursorPushed = true; + } + } + + /** + * When mouse leaves, pop cursor from stack. + * + * @param aEvent the mouse exited event + */ + public void mouseExited(final PInputEvent aEvent) { + if (cursorPushed) { + final PPickPath focus = aEvent.getInputManager().getMouseFocus(); + + if (focus == null || focus.getPickedNode() != PBoundsHandle.this) { + aEvent.popCursor(); + cursorPushed = false; + } + } + } + + /** + * If mouse is released, cursor should pop as well. + * + * @param event the mouse released event + */ + public void mouseReleased(final PInputEvent event) { + if (cursorPushed) { + event.popCursor(); + cursorPushed = false; + } + } } } diff --git a/extras/src/main/java/edu/umd/cs/piccolox/handles/PHandle.java b/extras/src/main/java/edu/umd/cs/piccolox/handles/PHandle.java index 2a138cc..78ab9d3 100644 --- a/extras/src/main/java/edu/umd/cs/piccolox/handles/PHandle.java +++ b/extras/src/main/java/edu/umd/cs/piccolox/handles/PHandle.java @@ -59,13 +59,34 @@ * @author Jesse Grosjean */ public class PHandle extends PPath { + private final class HandleDragHandler extends PDragSequenceEventHandler { + protected void startDrag(final PInputEvent event) { + super.startDrag(event); + startHandleDrag(event.getPositionRelativeTo(PHandle.this), event); + } - /** - * - */ + protected void drag(final PInputEvent event) { + super.drag(event); + final PDimension aDelta = event.getDeltaRelativeTo(PHandle.this); + if (aDelta.getWidth() != 0 || aDelta.getHeight() != 0) { + dragHandle(aDelta, event); + } + } + + protected void endDrag(final PInputEvent event) { + super.endDrag(event); + endHandleDrag(event.getPositionRelativeTo(PHandle.this), event); + } + } + private static final long serialVersionUID = 1L; + + /** The default size for a handle. */ public static float DEFAULT_HANDLE_SIZE = 8; + /** Default shape to use when drawing handles. */ public static Shape DEFAULT_HANDLE_SHAPE = new Ellipse2D.Float(0f, 0f, DEFAULT_HANDLE_SIZE, DEFAULT_HANDLE_SIZE); + + /** Default color to paint handles. */ public static Color DEFAULT_COLOR = Color.white; private PLocator locator; @@ -74,6 +95,8 @@ /** * Construct a new handle that will use the given locator to locate itself * on its parent node. + * + * @param aLocator locator to use when laying out the handle */ public PHandle(final PLocator aLocator) { super(DEFAULT_HANDLE_SHAPE); @@ -82,26 +105,11 @@ installHandleEventHandlers(); } + /** + * Installs the handler that notify its subclasses of handle interaction. + */ protected void installHandleEventHandlers() { - handleDragger = new PDragSequenceEventHandler() { - protected void startDrag(final PInputEvent event) { - super.startDrag(event); - startHandleDrag(event.getPositionRelativeTo(PHandle.this), event); - } - - protected void drag(final PInputEvent event) { - super.drag(event); - final PDimension aDelta = event.getDeltaRelativeTo(PHandle.this); - if (aDelta.getWidth() != 0 || aDelta.getHeight() != 0) { - dragHandle(aDelta, event); - } - } - - protected void endDrag(final PInputEvent event) { - super.endDrag(event); - endHandleDrag(event.getPositionRelativeTo(PHandle.this), event); - } - }; + handleDragger = new HandleDragHandler(); addPropertyChangeListener(PNode.PROPERTY_TRANSFORM, new PropertyChangeListener() { public void propertyChange(final PropertyChangeEvent evt) { @@ -122,6 +130,8 @@ /** * Return the event handler that is responsible for the drag handle * interaction. + * + * @return current handler for HandleDrag events */ public PDragSequenceEventHandler getHandleDraggerHandler() { return handleDragger; @@ -130,6 +140,8 @@ /** * Get the locator that this handle uses to position itself on its parent * node. + * + * @return the locator associated with this handle */ public PLocator getLocator() { return locator; @@ -138,27 +150,30 @@ /** * Set the locator that this handle uses to position itself on its parent * node. + * + * @param locator the locator to assign to this handle */ - public void setLocator(final PLocator aLocator) { - locator = aLocator; + public void setLocator(final PLocator locator) { + this.locator = locator; invalidatePaint(); relocateHandle(); } - // **************************************************************** - // Handle Dragging - These are the methods the subclasses should - // normally override to give a handle unique behavior. - // **************************************************************** - /** * Override this method to get notified when the handle starts to get * dragged. + * + * @param aLocalPoint point on the handle at which the event occurred + * @param aEvent the event responsible for starting the dragging */ public void startHandleDrag(final Point2D aLocalPoint, final PInputEvent aEvent) { } /** * Override this method to get notified as the handle is dragged. + * + * @param aLocalDimension size of the drag in handle coordinates + * @param aEvent event representing the drag */ public void dragHandle(final PDimension aLocalDimension, final PInputEvent aEvent) { } @@ -166,22 +181,29 @@ /** * Override this method to get notified when the handle stops getting * dragged. + * + * @param aLocalPoint point in handle coordinate system of the end of the + * drag + * @param aEvent event responsible for ending the drag */ public void endHandleDrag(final Point2D aLocalPoint, final PInputEvent aEvent) { } - // **************************************************************** - // Layout - When a handle's parent's layout changes the handle - // invalidates its own layout and then repositions itself on its - // parents bounds using its locator to determine that new - // position. - // **************************************************************** - + /** + * Set's this handle's parent. Handles respond to changes in their parent's + * bounds by invalidating themselves. + * + * @param newParent the new parent to assign to this handle + */ public void setParent(final PNode newParent) { super.setParent(newParent); relocateHandle(); } + /** + * Forces the handles to reposition themselves using their associated + * locator. + */ public void parentBoundsChanged() { relocateHandle(); } @@ -190,36 +212,44 @@ * Force this handle to relocate itself using its locator. */ public void relocateHandle() { - if (locator != null) { - final PBounds b = getBoundsReference(); - final Point2D aPoint = locator.locatePoint(null); + if (locator == null) { + return; + } - if (locator instanceof PNodeLocator) { - final PNode located = ((PNodeLocator) locator).getNode(); - final PNode parent = getParent(); + final PBounds b = getBoundsReference(); + final Point2D aPoint = locator.locatePoint(null); - located.localToGlobal(aPoint); - globalToLocal(aPoint); + if (locator instanceof PNodeLocator) { + final PNode located = ((PNodeLocator) locator).getNode(); + final PNode parent = getParent(); - if (parent != located && parent instanceof PCamera) { - ((PCamera) parent).viewToLocal(aPoint); - } - } + located.localToGlobal(aPoint); + globalToLocal(aPoint); - final double newCenterX = aPoint.getX(); - final double newCenterY = aPoint.getY(); - - if (newCenterX != b.getCenterX() || newCenterY != b.getCenterY()) { - - centerBoundsOnPoint(newCenterX, newCenterY); + if (parent != located && parent instanceof PCamera) { + ((PCamera) parent).viewToLocal(aPoint); } } + + final double newCenterX = aPoint.getX(); + final double newCenterY = aPoint.getY(); + + if (newCenterX != b.getCenterX() || newCenterY != b.getCenterY()) { + + centerBoundsOnPoint(newCenterX, newCenterY); + } + } - // **************************************************************** - // Serialization - // **************************************************************** - + /** + * Deserializes a PHandle from the input stream provided. Ensures tha all + * event handles are correctly installed. + * + * @param in stream from which to read the handle + * @throws IOException is thrown if the underlying input stream fails + * @throws ClassNotFoundException should never happen but can happen if the + * classpath gets messed up + */ private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); installHandleEventHandlers(); diff --git a/extras/src/main/java/edu/umd/cs/piccolox/handles/PStickyHandleManager.java b/extras/src/main/java/edu/umd/cs/piccolox/handles/PStickyHandleManager.java index 0db22d0..dab2c83 100644 --- a/extras/src/main/java/edu/umd/cs/piccolox/handles/PStickyHandleManager.java +++ b/extras/src/main/java/edu/umd/cs/piccolox/handles/PStickyHandleManager.java @@ -33,26 +33,49 @@ import edu.umd.cs.piccolo.util.PBounds; import edu.umd.cs.piccolo.util.PPickPath; +/** + * This class relays adjustments to its bounds to its target. + */ public class PStickyHandleManager extends PNode { - - /** - * - */ private static final long serialVersionUID = 1L; private PNode target; private PCamera camera; + /** + * Constructs a sticky handle manager responsible for updating the position + * of its associated node on the camera provided. + * + * @param newCamera camera on which this manager is operating + * @param newTarget node to be positioned on the camera + */ public PStickyHandleManager(final PCamera newCamera, final PNode newTarget) { setCameraTarget(newCamera, newTarget); PBoundsHandle.addBoundsHandlesTo(this); } + /** + * Changes the node and camera on which this manager is operating. + * + * @param newCamera camera on which this manager is operating + * @param newTarget node to be positioned on the camera + */ public void setCameraTarget(final PCamera newCamera, final PNode newTarget) { camera = newCamera; camera.addChild(this); target = newTarget; } + /** + * By changing this sticky handle's bounds, it propagates that change to its + * associated node. + * + * @param x x position of bounds + * @param y y position of bounds + * @param width width to apply to the bounds + * @param height height to apply to the bounds + * + * @return true if bounds were successfully changed + */ public boolean setBounds(final double x, final double y, final double width, final double height) { final PBounds b = new PBounds(x, y, width, height); camera.localToGlobal(b); @@ -62,10 +85,24 @@ return super.setBounds(x, y, width, height); } + /** + * Since this node's bounds are always dependent on its target, it is + * volatile. + * + * @return true since sticky handle manager's bounds are completely + * dependent on its children + */ protected boolean getBoundsVolatile() { return true; } + /** + * The sticky handle manager's bounds as computed by examining its target + * through its camera. + * + * @return the sticky handle manager's bounds as computed by examining its + * target through its camera + */ public PBounds getBoundsReference() { final PBounds targetBounds = target.getFullBounds(); camera.viewToLocal(targetBounds); @@ -75,16 +112,30 @@ return super.getBoundsReference(); } + /** + * Dispatches this event to its target as well. + */ public void startResizeBounds() { super.startResizeBounds(); target.startResizeBounds(); } + /** + * Dispatches this event to its target as well. + */ public void endResizeBounds() { super.endResizeBounds(); target.endResizeBounds(); } + /** + * Since this node is invisible, it doesn't make sense to have it be + * pickable. + * + * @return false since it's invisible + * @param pickPath path in which we're trying to determine if this node is + * pickable + */ public boolean pickAfterChildren(final PPickPath pickPath) { return false; } diff --git a/extras/src/main/java/edu/umd/cs/piccolox/nodes/PLine.java b/extras/src/main/java/edu/umd/cs/piccolox/nodes/PLine.java index 3ef4468..4175487 100644 --- a/extras/src/main/java/edu/umd/cs/piccolox/nodes/PLine.java +++ b/extras/src/main/java/edu/umd/cs/piccolox/nodes/PLine.java @@ -47,120 +47,158 @@ import edu.umd.cs.piccolox.util.LineShape; /** - * PLine a class for drawing multisegment lines. Submitted by Hallvard - * Traetteberg. + * PLine a class for drawing multisegment lines. + * + * @author Hallvard Traetteberg. */ public class PLine extends PNode { - /** - * - */ private static final long serialVersionUID = 1L; private static final PAffineTransform TEMP_TRANSFORM = new PAffineTransform(); private static final BasicStroke DEFAULT_STROKE = new BasicStroke(1.0f); private static final Color DEFAULT_STROKE_PAINT = Color.black; - private transient final LineShape line; + private final transient LineShape lineShape; private transient Stroke stroke; private Paint strokePaint; - public PLine(LineShape line) { - strokePaint = DEFAULT_STROKE_PAINT; - stroke = DEFAULT_STROKE; - if (line == null) { - line = new LineShape(null); - } - this.line = line; - } - + /** + * Constructs a new PLine with an empty LineShape. + */ public PLine() { this(null); } + /** + * Constructs a PLine object for displaying the provided line. + * + * @param lineShape will be displayed by this PLine + */ + public PLine(final LineShape lineShape) { + strokePaint = DEFAULT_STROKE_PAINT; + stroke = DEFAULT_STROKE; + + if (lineShape == null) { + this.lineShape = new LineShape(null); + } + else { + this.lineShape = lineShape; + } + } + + /** + * Constructs a PLine for the given lineShape and the given stroke. + * + * @param line line to be wrapped by this PLine + * @param aStroke stroke to use when drawling the line + */ public PLine(final LineShape line, final Stroke aStroke) { this(line); stroke = aStroke; } - // **************************************************************** - // Stroke - // **************************************************************** - + /** + * Returns the paint to be used while drawing the line. + * + * @return paint used when drawing the line + */ public Paint getStrokePaint() { return strokePaint; } - public void setStrokePaint(final Paint aPaint) { - final Paint old = strokePaint; - strokePaint = aPaint; + /** + * Changes the paint to be used while drawing the line. + * + * @param newStrokePaint paint to use when drawing the line + */ + public void setStrokePaint(final Paint newStrokePaint) { + final Paint oldPaint = strokePaint; + strokePaint = newStrokePaint; invalidatePaint(); - firePropertyChange(PPath.PROPERTY_CODE_STROKE_PAINT, PPath.PROPERTY_STROKE_PAINT, old, strokePaint); + firePropertyChange(PPath.PROPERTY_CODE_STROKE_PAINT, PPath.PROPERTY_STROKE_PAINT, oldPaint, strokePaint); } + /** + * Returns the stroke that will be used when drawing the line. + * + * @return stroke used to draw the line + */ public Stroke getStroke() { return stroke; } - public void setStroke(final Stroke aStroke) { - final Stroke old = stroke; - stroke = aStroke; + /** + * Sets stroke to use when drawing the line. + * + * @param newStroke stroke to use when drawing the line + */ + public void setStroke(final Stroke newStroke) { + final Stroke oldStroke = stroke; + stroke = newStroke; updateBoundsFromLine(); invalidatePaint(); - firePropertyChange(PPath.PROPERTY_CODE_STROKE, PPath.PROPERTY_STROKE, old, stroke); + firePropertyChange(PPath.PROPERTY_CODE_STROKE, PPath.PROPERTY_STROKE, oldStroke, stroke); } - // **************************************************************** - // Bounds - // **************************************************************** - - public boolean setBounds(double x, double y, double width, double height) { - if (line == null || !super.setBounds(x, y, width, height)) { + /** {@inheritDoc} */ + public boolean setBounds(final double x, final double y, final double width, final double height) { + if (lineShape == null || !super.setBounds(x, y, width, height)) { return false; } - final Rectangle2D lineBounds = line.getBounds2D(); + final Rectangle2D lineBounds = lineShape.getBounds2D(); final Rectangle2D lineStrokeBounds = getLineBoundsWithStroke(); final double strokeOutset = Math.max(lineStrokeBounds.getWidth() - lineBounds.getWidth(), lineStrokeBounds .getHeight() - lineBounds.getHeight()); - x += strokeOutset / 2; - y += strokeOutset / 2; - width -= strokeOutset; - height -= strokeOutset; + double adjustedX = x + strokeOutset / 2; + double adjustedY = y + strokeOutset / 2; + double adjustedWidth = width - strokeOutset; + double adjustedHeight = height - strokeOutset; TEMP_TRANSFORM.setToIdentity(); - TEMP_TRANSFORM.translate(x, y); - TEMP_TRANSFORM.scale(width / lineBounds.getWidth(), height / lineBounds.getHeight()); + TEMP_TRANSFORM.translate(adjustedX, adjustedY); + TEMP_TRANSFORM.scale(adjustedWidth / lineBounds.getWidth(), adjustedHeight / lineBounds.getHeight()); TEMP_TRANSFORM.translate(-lineBounds.getX(), -lineBounds.getY()); - line.transformPoints(TEMP_TRANSFORM); + lineShape.transformPoints(TEMP_TRANSFORM); return true; } + /** {@inheritDoc} */ public boolean intersects(final Rectangle2D aBounds) { if (super.intersects(aBounds)) { - if (line.intersects(aBounds)) { + if (lineShape.intersects(aBounds)) { return true; } else if (stroke != null && strokePaint != null) { - return stroke.createStrokedShape(line).intersects(aBounds); + return stroke.createStrokedShape(lineShape).intersects(aBounds); } } return false; } + /** + * Calculates the bounds of the line taking stroke width into account. + * + * @return rectangle representing the bounds of the line taking stroke width + * into account + */ public Rectangle2D getLineBoundsWithStroke() { if (stroke != null) { - return stroke.createStrokedShape(line).getBounds2D(); + return stroke.createStrokedShape(lineShape).getBounds2D(); } else { - return line.getBounds2D(); + return lineShape.getBounds2D(); } } + /** + * Recalculates the bounds when a change to the underlying line occurs. + */ public void updateBoundsFromLine() { - if (line.getPointCount() == 0) { + if (lineShape.getPointCount() == 0) { resetBounds(); } else { @@ -169,65 +207,112 @@ } } - // **************************************************************** - // Painting - // **************************************************************** - + /** + * Paints the PLine in the provided context if it has both a stroke and a + * stroke paint assigned. + * + * @param paintContext the context into which the line should be drawn + */ protected void paint(final PPaintContext paintContext) { final Graphics2D g2 = paintContext.getGraphics(); if (stroke != null && strokePaint != null) { g2.setPaint(strokePaint); g2.setStroke(stroke); - g2.draw(line); + g2.draw(lineShape); } } + /** + * Returns a reference to the underlying line shape. Be careful! + * + * @return direct reference to the underlying line shape + */ public LineShape getLineReference() { - return line; + return lineShape; } + /** + * Returns the number of points in the line. + * + * @return number of points in the line + */ public int getPointCount() { - return line.getPointCount(); + return lineShape.getPointCount(); } - public Point2D getPoint(final int i, Point2D dst) { + /** + * Returns the point at the provided index. If dst is not null, it will + * populate it with the point's coordinates rather than create a new point. + * + * @param pointIndex index of desired point in line + * @param dst point to populate, may be null + * @return the desired point, or dst populate with its coordinates + */ + public Point2D getPoint(final int pointIndex, final Point2D dst) { + final Point2D result; if (dst == null) { - dst = new Point2D.Double(); + result = new Point2D.Double(); + } + else { + result = dst; } - return line.getPoint(i, dst); + return lineShape.getPoint(pointIndex, result); } + /** + * Fires appropriate change events, updates line bounds and flags the PLine + * as requiring a repaint. + */ protected void lineChanged() { - firePropertyChange(PPath.PROPERTY_CODE_PATH, PPath.PROPERTY_PATH, null, line); + firePropertyChange(PPath.PROPERTY_CODE_PATH, PPath.PROPERTY_PATH, null, lineShape); updateBoundsFromLine(); invalidatePaint(); } - public void setPoint(final int i, final double x, final double y) { - line.setPoint(i, x, y); + /** + * Changes the point at the provided index. + * + * @param pointIndex index of point to change + * @param x x component to assign to the point + * @param y y component to assign to the point + */ + public void setPoint(final int pointIndex, final double x, final double y) { + lineShape.setPoint(pointIndex, x, y); lineChanged(); } - public void addPoint(final int i, final double x, final double y) { - line.addPoint(i, x, y); + /** + * Inserts a point at the provided index. + * + * @param pointIndex index at which to add the point + * @param x x component of new point + * @param y y component of new point + */ + public void addPoint(final int pointIndex, final double x, final double y) { + lineShape.addPoint(pointIndex, x, y); lineChanged(); } - public void removePoints(final int i, final int n) { - line.removePoints(i, n); + /** + * Removes points from the line. + * + * @param startIndex index from which to remove the points + * @param numberOfPoints number of points to remove + */ + public void removePoints(final int startIndex, final int numberOfPoints) { + lineShape.removePoints(startIndex, numberOfPoints); lineChanged(); } + /** + * Removes all points from the underlying line. + */ public void removeAllPoints() { - line.removePoints(0, line.getPointCount()); + lineShape.removePoints(0, lineShape.getPointCount()); lineChanged(); } - // **************************************************************** - // Serialization - // **************************************************************** - private void writeObject(final ObjectOutputStream out) throws IOException { out.defaultWriteObject(); PUtil.writeStroke(stroke, out); diff --git a/extras/src/main/java/edu/umd/cs/piccolox/nodes/PNodeCache.java b/extras/src/main/java/edu/umd/cs/piccolox/nodes/PNodeCache.java index af4bf42..f0cd9f0 100644 --- a/extras/src/main/java/edu/umd/cs/piccolox/nodes/PNodeCache.java +++ b/extras/src/main/java/edu/umd/cs/piccolox/nodes/PNodeCache.java @@ -58,10 +58,6 @@ * @author Jesse Grosjean */ public class PNodeCache extends PNode { - - /** - * - */ private static final long serialVersionUID = 1L; private transient Image imageCache; private boolean validatingCache; @@ -71,11 +67,20 @@ * example if you want to create a shadow effect you would do that here. * Fill in the cacheOffsetRef if needed to make your image cache line up * with the nodes children. + * + * @param cacheOffsetRef output parameter that can be changed to make the + * cached offset line up with the node's children + * @return an image representing this node */ public Image createImageCache(final Dimension2D cacheOffsetRef) { return toImage(); } + /** + * Returns an image that is a cached representation of its children. + * + * @return image representation of its children + */ public Image getImageCache() { if (imageCache == null) { final PDimension cacheOffsetRef = new PDimension(); @@ -90,16 +95,30 @@ return imageCache; } + /** + * Clears the cache, forcing it to be recalculated on the next call to + * getImageCache. + */ public void invalidateCache() { imageCache = null; } + /** + * Intercepts the normal invalidatePaint mechanism so that the node will not + * be repainted unless it's cache has been invalidated. + */ public void invalidatePaint() { if (!validatingCache) { super.invalidatePaint(); } } + /** + * Handles a repaint event issued from a node in this node's tree. + * + * @param localBounds local bounds of this node that need repainting + * @param childOrThis the node that emitted the repaint notification + */ public void repaintFrom(final PBounds localBounds, final PNode childOrThis) { if (!validatingCache) { super.repaintFrom(localBounds, childOrThis); @@ -107,6 +126,11 @@ } } + /** + * Repaints this node, using the cached result if possible. + * + * @param paintContext context in which painting should occur + */ public void fullPaint(final PPaintContext paintContext) { if (validatingCache) { super.fullPaint(paintContext); @@ -117,6 +141,12 @@ } } + /** + * By always returning false, makes the PNodeCache instance NOT pickable. + * + * @param pickPath path which this node is being tested for inclusion + * @return always returns false + */ protected boolean pickAfterChildren(final PPickPath pickPath) { return false; } diff --git a/extras/src/main/java/edu/umd/cs/piccolox/nodes/PStyledText.java b/extras/src/main/java/edu/umd/cs/piccolox/nodes/PStyledText.java index ea7e420..95bd462 100644 --- a/extras/src/main/java/edu/umd/cs/piccolox/nodes/PStyledText.java +++ b/extras/src/main/java/edu/umd/cs/piccolox/nodes/PStyledText.java @@ -64,6 +64,7 @@ private static final long serialVersionUID = 1L; + /** Font rendering context used for all PStyledText instances. */ protected static FontRenderContext SWING_FRC = new FontRenderContext(null, true, false); /** Used while painting underlines. */ @@ -97,12 +98,14 @@ * Constructs an empty PStyledText element. */ public PStyledText() { - super(); } /** * Controls whether this node changes its width to fit the width of its * text. If flag is true it does; if flag is false it doesn't + * + * @param constrainWidthToTextWidth whether node's width should be + * constrained to the width of its text */ public void setConstrainWidthToTextWidth(final boolean constrainWidthToTextWidth) { this.constrainWidthToTextWidth = constrainWidthToTextWidth; @@ -112,6 +115,9 @@ /** * Controls whether this node changes its height to fit the height of its * text. If flag is true it does; if flag is false it doesn't + * + * @param constrainHeightToTextHeight whether node's height should be + * constrained to the height of its text */ public void setConstrainHeightToTextHeight(final boolean constrainHeightToTextHeight) { this.constrainHeightToTextHeight = constrainHeightToTextHeight; @@ -121,6 +127,8 @@ /** * Controls whether this node changes its width to fit the width of its * text. If flag is true it does; if flag is false it doesn't + * + * @return true if node is constrained to the width of its text */ public boolean getConstrainWidthToTextWidth() { return constrainWidthToTextWidth; @@ -129,20 +137,28 @@ /** * Controls whether this node changes its height to fit the height of its * text. If flag is true it does; if flag is false it doesn't + * + * @return true if node is constrained to the height of its text */ public boolean getConstrainHeightToTextHeight() { return constrainHeightToTextHeight; } /** - * Get the document for this PStyledText + * Get the document for this PStyledText. Document is used as the node's + * model. + * + * @return internal document used as a model of this PStyledText */ public Document getDocument() { return document; } /** - * Set the document on this PStyledText + * Set the document on this PStyledText. Document is used as the node's + * model. + * + * @param document to be used as the model for this PStyledText */ public void setDocument(final Document document) { // Save the document @@ -152,7 +168,7 @@ } /** - * Ensures that the current display matches the styling of the underlying + * Enforce that the current display matches the styling of the underlying * document as closely as possible. */ public void syncWithDocument() { @@ -263,14 +279,14 @@ * document */ private Element drillDownFromRoot(final int pos, final Element rootElement) { - Element curElement; // Before each pass, start at the root - curElement = rootElement; + Element curElement = rootElement; // Now we descend the hierarchy until we get to a leaf while (!curElement.isLeaf()) { curElement = curElement.getElement(curElement.getElementIndex(pos)); } + return curElement; } @@ -307,9 +323,8 @@ private void applyBackgroundAttribute(final StyleContext style, final RunInfo paragraphRange, final AttributedString attributedString, final Element curElement, final AttributeSet attributes) { - final Color background = attributes.isDefined(StyleConstants.Background) ? style.getBackground(attributes) - : null; - if (background != null) { + if (attributes.isDefined(StyleConstants.Background)) { + final Color background = style.getBackground(attributes); attributedString.addAttribute(TextAttribute.BACKGROUND, background, Math.max(0, curElement.getStartOffset() - paragraphRange.startIndex), Math.min(paragraphRange.endIndex - paragraphRange.startIndex, curElement.getEndOffset() - paragraphRange.startIndex)); @@ -318,9 +333,14 @@ private Font extractFont(final StyleContext style, final int pos, final Element rootElement, final AttributeSet attributes) { - Font font = attributes.isDefined(StyleConstants.FontSize) || attributes.isDefined(StyleConstants.FontFamily) ? style - .getFont(attributes) - : null; + Font font; + if (attributes.isDefined(StyleConstants.FontSize) || attributes.isDefined(StyleConstants.FontFamily)) { + font = style.getFont(attributes); + } + else { + font = null; + } + if (font == null) { if (document instanceof DefaultStyledDocument) { font = style.getFont(((DefaultStyledDocument) document).getCharacterElement(pos).getAttributes()); @@ -440,9 +460,8 @@ if (newLine) { newLine = false; - // Add in the old line dimensions - final double lineHeight = lineInfo == null ? 0 : lineInfo.maxAscent + lineInfo.maxDescent - + lineInfo.leading; + final double lineHeight = calculateLineHeightFromLineInfo(lineInfo); + textHeight = textHeight + lineHeight; textWidth = Math.max(textWidth, lineWidth); @@ -478,8 +497,7 @@ lineWidth = lineWidth + aTextLayout.getAdvance(); } - final double lineHeight = lineInfo == null ? 0 : lineInfo.maxAscent + lineInfo.maxDescent - + lineInfo.leading; + final double lineHeight = calculateLineHeightFromLineInfo(lineInfo); textHeight = textHeight + lineHeight; textWidth = Math.max(textWidth, lineWidth); } @@ -489,6 +507,21 @@ constrainDimensionsIfNeeded(textWidth, textHeight); } + /** + * @param lineInfo + * @return + */ + private double calculateLineHeightFromLineInfo(final LineInfo lineInfo) { + final double lineHeight; + if (lineInfo == null) { + lineHeight = 0; + } + else { + lineHeight = lineInfo.maxAscent + lineInfo.maxDescent + lineInfo.leading; + } + return lineHeight; + } + private void constrainDimensionsIfNeeded(final double textWidth, final double textHeight) { if (!constrainWidthToTextWidth && !constrainHeightToTextHeight) { return; @@ -528,6 +561,8 @@ /** * Get the height of the font at the beginning of the document. + * + * @return height of font at the start of the document. */ public double getInitialFontHeight() { @@ -614,7 +649,7 @@ } /** - * Set whether this text is editing. + * Set whether this node is current in editing mode. * * @param editing value to set editing flag */ @@ -656,9 +691,7 @@ return (Insets) insets.clone(); } - /** - * Add a call to recompute the layout after each bounds change. - */ + /** {@inheritDoc} */ public boolean setBounds(final double x, final double y, final double width, final double height) { if (document == null || !super.setBounds(x, y, width, height)) { return false; @@ -672,9 +705,16 @@ * Simple class to represent an range within the document. */ protected static class RunInfo { - public int startIndex; - public int endIndex; + private int startIndex; + private int endIndex; + /** + * Constructs a RunInfo representing the range within the document from + * runStart to runLimit. + * + * @param runStart starting index of the range + * @param runLimit ending index of the range + */ public RunInfo(final int runStart, final int runLimit) { startIndex = runStart; endIndex = runLimit; @@ -705,8 +745,14 @@ protected static class LineInfo { /** Segments which make up this line's formatting segments. */ public List segments; + + /** Maximum of the line segments' ascents. */ public double maxAscent; + + /** Maximum of the line segments' descents. */ public double maxDescent; + + /** Leading space at front of line segment. */ public double leading; /** @@ -717,16 +763,35 @@ } } + /** + * Encapsulates information about a particular LineSegment. + */ protected static class SegmentInfo { + /** Text Layout applied to the segment. */ public TextLayout layout; + + /** Font being used to render the segment. */ public Font font; + + /** Foreground (text) color of the segment. */ public Color foreground; + + /** Background color of the segment. */ public Color background; + + /** Whether the segment is underlined. */ public Boolean underline; + /** Construct a segment with null properties. */ public SegmentInfo() { } + /** + * Applies this particular SegmentInfo's font to the graphics object + * passed in. + * + * @param g2 will have the font of this segment applied + */ public void applyFont(final Graphics2D g2) { if (font != null) { g2.setFont(font); diff --git a/extras/src/main/java/edu/umd/cs/piccolox/util/PFixedWidthStroke.java b/extras/src/main/java/edu/umd/cs/piccolox/util/PFixedWidthStroke.java index 608da39..bc91070 100644 --- a/extras/src/main/java/edu/umd/cs/piccolox/util/PFixedWidthStroke.java +++ b/extras/src/main/java/edu/umd/cs/piccolox/util/PFixedWidthStroke.java @@ -29,7 +29,6 @@ package edu.umd.cs.piccolox.util; import java.awt.BasicStroke; -import java.awt.Shape; import java.awt.Stroke; import java.io.ObjectStreamException; import java.io.Serializable; @@ -61,30 +60,51 @@ */ public class PFixedWidthStroke extends PSemanticStroke implements Serializable { + private static final float DEFAULT_MITER_LIMIT = 10.0f; + private static final long serialVersionUID = 1L; // avoid repeated cloning: - private transient final float dash[]; - // avoid repeated instantiations: - private transient final float tmpDash[]; + private final transient float[] dash; + // avoid repeated instantiations: + private final transient float[] tmpDash; + + /** + * Constructs a simple PFixedWidthStroke with thickness 1, square caps, join + * meter, and no dashing. + */ public PFixedWidthStroke() { - this(1.0f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10.0f, null, 0.0f); + this(1.0f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, DEFAULT_MITER_LIMIT, null, 0.0f); } - /** This should be "public" and the "main" constructor. */ + /** + * This should be "public" and the "main" constructor. + * + * @param stroke stroke being used by this PFixedWithStroke + */ private PFixedWidthStroke(final BasicStroke stroke) { super(stroke); dash = stroke.getDashArray(); - tmpDash = dash == null ? null : new float[dash.length]; + if (dash == null) { + tmpDash = null; + } + else { + tmpDash = new float[dash.length]; + } } + /** + * Constructs a simple PFixedWidthStroke with the width provided. + * + * @param width desired width of the stroke + */ public PFixedWidthStroke(final float width) { - this(width, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10.0f, null, 0.0f); + this(width, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, DEFAULT_MITER_LIMIT, null, 0.0f); } public PFixedWidthStroke(final float width, final int cap, final int join) { - this(width, cap, join, 10.0f, null, 0.0f); + this(width, cap, join, DEFAULT_MITER_LIMIT, null, 0.0f); } public PFixedWidthStroke(final float width, final int cap, final int join, final float miterlimit) { @@ -92,7 +112,7 @@ } public PFixedWidthStroke(final float width, final int cap, final int join, final float miterlimit, - final float dash[], final float dash_phase) { + final float[] dash, final float dash_phase) { this(new BasicStroke(width, cap, join, miterlimit, dash, dash_phase)); } @@ -100,14 +120,29 @@ throw new UnsupportedOperationException("Not implemented."); } + /** + * Returns the array used for specifying dash style. + * + * @return array used to specify dash style + */ public float[] getDashArray() { return ((BasicStroke) stroke).getDashArray(); } + /** + * Returns the dash phase of the current stroke. + * + * @return dash phase of stroke + */ public float getDashPhase() { return ((BasicStroke) stroke).getDashPhase(); } + /** + * Returns the cap to be used at the end of open segments. + * + * @return cap style to use at end of segments + */ public int getEndCap() { return ((BasicStroke) stroke).getEndCap(); } @@ -116,6 +151,11 @@ return ((BasicStroke) stroke).getLineJoin(); } + /** + * Returns the width of the line. + * + * @return stroke width + */ public float getLineWidth() { return ((BasicStroke) stroke).getLineWidth(); } @@ -131,11 +171,24 @@ } } final float ml = getMiterLimit() / activeScale; - return new BasicStroke(getLineWidth() / activeScale, getEndCap(), getLineJoin(), ml < 1.0f ? 1.0f : ml, - tmpDash, getDashPhase() / activeScale); + final float sanitizedMiterLimit; + if (ml < 1.0f) { + sanitizedMiterLimit = 1f; + } + else { + sanitizedMiterLimit = ml; + } + + return new BasicStroke(getLineWidth() / activeScale, getEndCap(), getLineJoin(), sanitizedMiterLimit, tmpDash, + getDashPhase() / activeScale); } - /** Is it really necessary to implement {@link Serializable}? */ + /** + * Is it really necessary to implement {@link Serializable}? + * + * @throws ObjectStreamException doesn't actually throw this at all, why's + * this here? + */ protected Object readResolve() throws ObjectStreamException { return new PFixedWidthStroke((BasicStroke) stroke); }