001: /*************************************************************************
002: * *
003: * 1) This source code file, in unmodified form, and compiled classes *
004: * derived from it can be used and distributed without restriction, *
005: * including for commercial use. (Attribution is not required *
006: * but is appreciated.) *
007: * *
008: * 2) Modified versions of this file can be made and distributed *
009: * provided: the modified versions are put into a Java package *
010: * different from the original package, edu.hws; modified *
011: * versions are distributed under the same terms as the original; *
012: * and the modifications are documented in comments. (Modification *
013: * here does not include simply making subclasses that belong to *
014: * a package other than edu.hws, which can be done without any *
015: * restriction.) *
016: * *
017: * David J. Eck *
018: * Department of Mathematics and Computer Science *
019: * Hobart and William Smith Colleges *
020: * Geneva, New York 14456, USA *
021: * Email: eck@hws.edu WWW: http://math.hws.edu/eck/ *
022: * *
023: *************************************************************************/package edu.hws.jcm.draw;
024:
025: import edu.hws.jcm.awt.*;
026: import edu.hws.jcm.data.*;
027: import java.awt.*;
028: import java.awt.event.*;
029:
030: /**
031: * A MouseTracker can be added to a CoordinateRect in a DisplayCanvas to respond to user
032: * mouse actions in the rectangular area occupied by the CoordinateRect. Two
033: * Variable objects, which can be retrieved by calling getXVar() and getYVar(),
034: * represent the location of the most recent mouse action in terms of the coordinates
035: * of the CoordinateRect. Note that these variables are Tieable objects, so they
036: * can be synchronized with other means of inputting the same information.
037: * The default names of the variables, if you don't change them, are "xMouse"
038: * and "yMouse".
039: *
040: * <p>A MouseTracker is an InputObject. The values of the variables associated with the
041: * MouseTracker can change only when the checkInput() method is called (or when
042: * the setVal() method of the variable is called to set its value explicitely).
043: * If you want the value of the variables to track the mouse, you must
044: * add the MouseTracker (or the DisplayCanvas that contains it) to a Controller
045: * and set that Controller to listen for changes from the MouseTracker object by
046: * passing the Controller to the setOnUserAction() method of this class.
047: *
048: */
049:
050: public class MouseTracker extends Drawable implements MouseListener,
051: MouseMotionListener, InputObject {
052:
053: /**
054: * If true, the MouseTracker responds to both clicks and drags.
055: * If false, it responds only to clicks.
056: */
057: protected boolean listenForDrags;
058:
059: /**
060: * If true, the values of the associated variables are
061: * undefined except during the time that the user is
062: * clicking and dragging the mouse. This is ignored
063: * if listenForDrags is false.
064: */
065: protected boolean undefinedWhenNotDragging;
066:
067: /**
068: * If this is non-null, then its compute() method is called
069: * when the user clicks the mouse and, if listenForDrags is also
070: * true, when the user drags and releases the mouse.
071: */
072: protected Controller onUserAction;
073:
074: /**
075: * If thie is true, then the value of the variable associated with
076: * the x-ccordinate of the mouse is clamped to lie within the
077: * xmin and xmax of the coordinate rect.
078: */
079: protected boolean clampX = true;
080:
081: /**
082: * If thie is true, then the value of the variable associated with
083: * the y-ccordinate of the mouse is clamped to lie within the
084: * ymin and ymax of the coordinate rect.
085: */
086: protected boolean clampY = true;
087:
088: private MTVariable xVar, yVar; //The variables associated with this MouseTracker. The class MTVarible
089: // is a private nested class defined below.
090:
091: private int xClick, yClick; //Pixel where the mose recent user mouse action occured.
092:
093: private boolean inRect; //This is set to true while the user is dragging (if listenForDrags is true).
094:
095: /**
096: * Create a MouseTracker that responds to both clicks and drags. The values of the
097: * associated variables remain defined even after the user stops dragging.
098: */
099: public MouseTracker() {
100: this (true, false);
101: }
102:
103: /**
104: * Creates a mouse tracker. The first parameter specifies whether the values of
105: * the variables change when the user drags the mouse, or only when the user clicks.
106: * The second parameter is only used if the first is true. It specifies whether
107: * the values of the variables become undefined after the user stops dragging the
108: * mouse.
109: *
110: */
111: public MouseTracker(boolean listenForDrags,
112: boolean undefinedWhenNotDragging) {
113: this .listenForDrags = listenForDrags;
114: this .undefinedWhenNotDragging = undefinedWhenNotDragging;
115: xVar = new MTVariable(true);
116: yVar = new MTVariable(false);
117: }
118:
119: /**
120: * Get the variable whose value represents the x-coordinate of the MouseTracker.
121: * Note that this variable implements the Tieable interface, so can legally
122: * be type-cast to type Tieable. It can be tied to other objects that
123: * implement the Tieable and Value interfaces to synchronize their values.
124: */
125: public Variable getXVar() {
126: return xVar;
127: }
128:
129: /**
130: * Get the variable whose value represents the y-coordinate of the MouseTracker.
131: * Note that this variable implements the Tieable interface, so can legally
132: * be type-cast to type Tieable. It can be tied to other objects that
133: * implement the Tieable and Value interfaces to synchronize their values.
134: */
135: public Variable getYVar() {
136: return yVar;
137: }
138:
139: /**
140: * Sets the "listenForDrags" property of the MouseTracker.
141: * If set to true, then the MouseTracker responds to both clicks and drags if false,
142: * it responds only to clicks.
143: *
144: */
145: public void setListenForDrags(boolean listen) {
146: if (listen != listenForDrags) {
147: listenForDrags = listen;
148: if (canvas != null) {
149: if (listen)
150: canvas.addMouseMotionListener(this );
151: else
152: canvas.removeMouseMotionListener(this );
153: }
154: }
155: }
156:
157: /**
158: * Gets the "listenForDrags" property of the MouseTracker, which determines
159: * if the MouseTracker responds to both clicks and drags, or only to clicks.
160: */
161: public boolean getListenForDrags() {
162: return listenForDrags;
163: }
164:
165: /**
166: * Sets the "undefinedWhenNotDragging" property of the MouseTracker.
167: * This is ignored if the MouseTracker is not listening for drags.
168: * If set to true, the values of the variables associated with this
169: * variable become undefined when the user is not dragging.
170: *
171: */
172: public void setUndefinedWhenNotDragging(boolean b) {
173: undefinedWhenNotDragging = b;
174: }
175:
176: /**
177: * Gets the "undefinedWhenNotDragging" property of the MouseTracker.
178: *
179: */
180: public boolean getUndefinedWhenNotDragging() {
181: return undefinedWhenNotDragging;
182: }
183:
184: /**
185: * Set a Controller to respond to user mouse actions tracked
186: * by this MouseTracker. The MouseTracker should also be added
187: * to the Controller, so that the values of its variables will
188: * actually change when a user action occurs.
189: *
190: */
191: public void setOnUserAction(Controller onUserAction) {
192: this .onUserAction = onUserAction;
193: }
194:
195: /**
196: * Method required by InputObject interface; in this class, it simply calls
197: * setOnUserAction(c). This is meant to be called by JCMPanel.gatherInputs().
198: */
199: public void notifyControllerOnChange(Controller c) {
200: setOnUserAction(c);
201: }
202:
203: /**
204: * Get the Controller that responds when a user mouse action is detected by this MouseTracker.
205: */
206: public Controller getOnUserAction() {
207: return onUserAction;
208: }
209:
210: /**
211: * Set the "clampX" property of the MouseTracker.
212: * If set to true, which is the default, the value of
213: * the variable associated with the horizontal position of
214: * the mouse is clamped to lie within the containing
215: * CoordinateRect.
216: *
217: */
218: public void setClampX(boolean clamp) {
219: clampX = clamp;
220: }
221:
222: /**
223: * Get the "clampX" property of the MouseTracker.
224: */
225: public boolean getClampX() {
226: return clampX;
227: }
228:
229: /**
230: * Set the "clampY" property of the MouseTracker.
231: * If set to true, which is the default, the value of
232: * the variable associated with the vertical position of
233: * the mouse is clamped to lie within the containing
234: * CoordinateRect.
235: *
236: */
237: public void setClampY(boolean clamp) {
238: clampY = clamp;
239: }
240:
241: /**
242: * Get the "clampY" property of the MouseTracker.
243: */
244: public boolean getClampY() {
245: return clampX;
246: }
247:
248: //------------------ Implementation details --------------------------------------------
249:
250: /**
251: * Set the values of the associated variables. This is part of the InputObject interface,
252: * and it is meant to be called by a Controller.
253: */
254: public void checkInput() {
255: if (coords == null || (undefinedWhenNotDragging && !inRect)) {
256: ;
257: xVar.setVal(Double.NaN);
258: yVar.setVal(Double.NaN);
259: } else {
260: double newX, newY; // The new values.
261: newX = coords.pixelToX(xClick);
262: if (clampX) {
263: if (newX < coords.getXmin())
264: newX = coords.getXmin();
265: else if (newX > coords.getXmax())
266: newX = coords.getXmax();
267: }
268: xVar.setVal(newX);
269: newY = coords.pixelToY(yClick);
270: if (clampY) {
271: if (newY < coords.getYmin())
272: newY = coords.getYmin();
273: else if (newY > coords.getYmax())
274: newY = coords.getYmax();
275: }
276: yVar.setVal(newY);
277: }
278: }
279:
280: /**
281: * A MouseTracker doesn't actually draw anything, but this method is required in
282: * a Drawable object.
283: */
284: public void draw(Graphics g, boolean coordsChanged) {
285: }
286:
287: /**
288: * This is called automatically by CoordinateRect when the
289: * MouseTracker is added to the CoordinateRect. It is not
290: * meant to be used directly.
291: *
292: */
293: protected void setOwnerData(DisplayCanvas canvas,
294: CoordinateRect coords) {
295: if (this .canvas != null) {
296: canvas.removeMouseListener(this );
297: canvas.removeMouseMotionListener(this );
298: }
299: this .canvas = canvas;
300: this .coords = coords;
301: canvas.addMouseListener(this );
302: if (listenForDrags)
303: canvas.addMouseMotionListener(this );
304: }
305:
306: /**
307: * Responds when the user clicks the mouse in the rectangular
308: * area occupied by the CoordinateRect that contains this MouseTracker.
309: * Since the MouseTracker listens for clicks on the whole DisplayCanvas
310: * and the CoordinateRect might only occupy part of that, it is necessary
311: * to check whether the user click was in that rect. This is not meant to be called directly.
312: *
313: */
314: public void mousePressed(MouseEvent evt) {
315: if (evt.isConsumed() || coords == null)
316: return;
317: inRect = (evt.getX() >= coords.getLeft()
318: && evt.getX() <= coords.getLeft() + coords.getWidth()
319: && evt.getY() >= coords.getTop() && evt.getY() <= coords
320: .getTop()
321: + coords.getHeight());
322: if (!inRect)
323: return;
324: evt.consume();
325: xClick = evt.getX();
326: yClick = evt.getY();
327: xVar.serialNumber++;
328: yVar.serialNumber++;
329: if (onUserAction != null)
330: onUserAction.compute();
331: }
332:
333: /**
334: * Responds when the user releases the mouse. This is not meant to be called directly.
335: *
336: */
337: public void mouseReleased(MouseEvent evt) {
338: if (inRect == false)
339: return;
340: inRect = false;
341: if (listenForDrags && undefinedWhenNotDragging) {
342: xVar.serialNumber++;
343: yVar.serialNumber++;
344: if (onUserAction != null)
345: onUserAction.compute();
346: }
347: }
348:
349: /**
350: * Responds when the user drags the mouse. This is not meant to be called directly.
351: *
352: */
353: public void mouseDragged(MouseEvent evt) {
354: if (listenForDrags && inRect) {
355: xClick = evt.getX();
356: yClick = evt.getY();
357: xVar.serialNumber++;
358: yVar.serialNumber++;
359: if (onUserAction != null)
360: onUserAction.compute();
361: }
362: }
363:
364: /**
365: * Empty method, required by MouseListener interface.
366: */
367: public void mouseClicked(MouseEvent evt) {
368: }
369:
370: /**
371: * Empty method, required by MouseMotionListener interface.
372: */
373: public void mouseEntered(MouseEvent evt) {
374: }
375:
376: /**
377: * Empty method, required by MouseMotionListener interface.
378: */
379: public void mouseExited(MouseEvent evt) {
380: }
381:
382: /**
383: * Empty method, required by MouseMotionListener interface.
384: */
385: public void mouseMoved(MouseEvent evt) {
386: }
387:
388: //The class to which the variables associated with this MouseTracker belong.
389:
390: private class MTVariable extends Variable implements Tieable {
391:
392: //True for xVar; false for yVar.
393: private boolean isXVar;
394:
395: //This object's serial number, which can
396: //change in MouseTracker.checkInput() as
397: //well as in the setVal() and sync() methods in this class.
398: long serialNumber;
399:
400: //Create the variable.
401: MTVariable(boolean isXVar) {
402: super (isXVar ? "xMouse" : "yMouse");
403: this .isXVar = isXVar;
404: super .setVal(Double.NaN);
405: }
406:
407: //Set the value of the variable. Note that the
408: //value can be set to lie outside the coordinate rect,
409: //even if clampX and clampY are true. The next checkInput(),
410: //however, will apply the clamp.
411: public void setVal(double val) {
412: if (isXVar) {
413: if (coords != null) // set xClick to match the value.
414: xClick = coords.xToPixel(val);
415: } else {
416: if (coords != null) // set yClick to match the value
417: yClick = coords.yToPixel(val);
418: }
419: super .setVal(val);
420: }
421:
422: // Return this Tieable object's serial number.
423: public long getSerialNumber() {
424: return serialNumber;
425: }
426:
427: //Synchronize values and serial numbers with newest.
428: public void sync(Tie tie, Tieable newest) {
429: if (!(newest instanceof Value))
430: throw new IllegalArgumentException(
431: "Internal Error: A MouseTracker variable can only be tied to a Value object.");
432: if (newest != this ) {
433: setVal(((Value) newest).getVal());
434: serialNumber = newest.getSerialNumber();
435: }
436: }
437:
438: } // end nested class MTVariable
439:
440: } // end class MouseTracker
|