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 java.util.StringTokenizer;
027: import java.util.Vector;
028: import java.awt.*;
029: import java.awt.event.*;
030:
031: /**
032: * A DisplayCanvas is a drawing area that can contain one or more CoordinateRects.
033: * Each CoordinateRect can, in turn, contain Drawable items. If you only want
034: * one CoordinateRect that fills the whole canvas, you can for the most part
035: * ignore the CoordinateRect and work only with the canvas.
036: * The option of using an offscreen image for double buffering is provided.
037: * By default, this option is on.
038: * <p>If a DisplayCanvas is added to a
039: * Controller, then it will take care of calling the checkInput()
040: * and compute() methods of the InputObjects and Computables that
041: * it contains, so there is no need to add them individually to
042: * the Controller. If the DisplayCanvas is added to a JCMPanel, it
043: * is automatically added to the Controller for that JCMPanel.
044: * (On the other hand, if a DisplayCanvas is added to a Controller,
045: * this means that all the
046: * items in the DisplayCanvas will be recomputed, even if only some
047: * of them need to be.)
048: * <p>The canvas can display an error message that goes away
049: * when the canvas is clicked or when clearErrorMessage()
050: * is called. This allows the Canvas to be used as an
051: * ErrorReporter for a Controller or LimitControlPanel.
052: * <p>When any changes are made to the contents of the Canvas,
053: * doRedraw() must be called for those changes to take effect.
054: * Normally, doRedraw() is called by the CoordinateRect or by
055: * one of the Drawables in a CoordinateRect. Use doRedraw(CoordinateRect c)
056: * or doRedraw(int index) to redraw a single CoordinateRect. Note that
057: * repainting the canvas is not enough, because this will not automatically
058: * refresh the off-screen image.
059: *
060: */
061:
062: public class DisplayCanvas extends Canvas implements ErrorReporter,
063: InputObject, Computable {
064:
065: private Vector coordinateRects;
066: // Contains all the CoordinateRects that have been added to this canvas.
067: // The elements are members of the private static nested class CRData.
068:
069: private boolean useOffscreenCanvas = true; // If true, double buffering is used.
070:
071: private boolean handleMouseZooms = false; // If true, clicking on a CoordinateRect
072:
073: // will zoom in on the clicked point, or
074: // will zoom out if the shift key is down.
075:
076: /**
077: * Create a DisplayCanvas with a white background containing no CoordinateRects.
078: */
079: public DisplayCanvas() {
080: setBackground(Color.white);
081: enableEvents(AWTEvent.MOUSE_EVENT_MASK
082: | AWTEvent.MOUSE_MOTION_EVENT_MASK);
083: }
084:
085: /**
086: * Create a DisplayCanvas with a white background and containing the
087: * specified CoordinateRect. The CoordinateRect fills the entire canvas.
088: *
089: * @param c The CoordinateRect that will fill the canvas. If c is null, no CoordinateRect is added to the canvas.
090: */
091: public DisplayCanvas(CoordinateRect c) {
092: this ();
093: if (c != null)
094: addCoordinateRect(c);
095: }
096:
097: /**
098: * Release the memory used the by the off-screen image, if any, that is used for
099: * double-buffering. It's a good idea to call this if the DisplayCanvas is in
100: * an applet and the applet is stopped.
101: */
102: public void releaseResources() {
103: OSC = null;
104: OSG = null;
105: }
106:
107: /**
108: * Set the "handleMouseZooms" property of this DisplayCanvas. IF the value is true,
109: * then clicking on the canvas will zoom in on the point that is clicked and shift-clicking
110: * will zoom out from that point. Only the CoordinateRect, if any, that contains the point
111: * is zoomed. Furthermore, if the user clicks-and-drags, a rectangle
112: * is drawn. When the mouse is released, the interior of the rectangle is zoomed to fill
113: * the CoordinateRect. This property is false by default.
114: */
115: public void setHandleMouseZooms(boolean handle) {
116: handleMouseZooms = handle;
117: }
118:
119: /**
120: * Get the "handleMouseZooms" property of this DisplayCanvas, which determines whether the
121: * canvas reacts to mouse events by zooming the CoordinateRect that is clicked.
122: */
123: public boolean getHandleMouseZooms() {
124: return handleMouseZooms;
125: }
126:
127: /**
128: * Set the "useOffscreenCanvas" property of this DisplayCanvas. IF the value is true,
129: * an off-screen image is used for double buffering. This property is true by default.
130: */
131: public boolean getUseOffscreenCanvas() {
132: return useOffscreenCanvas;
133: }
134:
135: /**
136: * Get the "useOffscreenCanvas" property of this DisplayCanvas, which determines whether
137: * double-buffering is used.
138: */
139: public void setUseOffscreenCanvas(boolean use) {
140: useOffscreenCanvas = use;
141: if (!use) {
142: OSC = null;
143: OSG = null;
144: }
145: }
146:
147: // ----------- For managing CoordinateRects ------------------------
148:
149: /**
150: * This private subclass of DisplayCanvas holds the data for one CoordinateRect
151: * contained in a DisplayCanvas.
152: */
153: private static class CRData implements java.io.Serializable {
154: // Data for one coordinate rect
155: CoordinateRect coords;
156: double xmin, xmax, ymin, ymax;
157: // Values between 0 and 1 that
158: // specify the region of the canvas occupied by this
159: // CoordinateRect.
160: Color background; // Color to fill area with before drawing.
161: // If it's null, no fill is done. (The display color
162: // of the Canvas shows through.)
163: }
164:
165: /**
166: * Add the specified Drawable item to the first CoordinateRect in this DisplayCanvas.
167: * If no CoordinateRect is associated with the canvas, one is created to fill the
168: * entire canvas.
169: */
170: public void add(Drawable d) {
171: if (coordinateRects == null)
172: addCoordinateRect(new CoordinateRect());
173: CoordinateRect c = ((CRData) coordinateRects.elementAt(0)).coords;
174: c.add(d);
175: }
176:
177: /**
178: * Add a Drawable item to one of the CoordinateRects associated with the Canvas.
179: *
180: * @param d The Drawable item to be added to a CoordinateRect
181: * @param coordRectIndex The index of the CoordinateRect, where the index of the first
182: * CoordinateRect that was added to the cavas is zero, the index of the second is one,
183: * and so on. A CoordinateRect with the specified index must already exist in the
184: * canvas, or an IllegalArgumentException is thrown.
185: */
186: public void add(Drawable d, int coordRectIndex) {
187: if (coordinateRects == null || coordRectIndex < 0
188: || coordRectIndex >= coordinateRects.size())
189: throw new IllegalArgumentException(
190: "Internal programming error: CoordinateRect index ("
191: + coordRectIndex + ")out of range.");
192: CoordinateRect c = ((CRData) coordinateRects
193: .elementAt(coordRectIndex)).coords;
194: c.add(d);
195: }
196:
197: /**
198: * Add the specified CoordinateRect to this DisplayCanvas, filling the entire canvas,
199: * and with background color equal to the background color of the canvas.
200: *
201: * @param c the CoordinateRect to be added. If null, an IllegalArgumentException is thrown.
202: */
203: public void addCoordinateRect(CoordinateRect c) {
204: addCoordinateRect(c, 0, 1, 0, 1, null);
205: }
206:
207: /**
208: * Add a CoordinateRect to the canvas, occupying a specified region of the canvas.
209: *
210: * @param coords The CoordinateRect to be added. If this is null, an IllegalArgumentExceptionis thrown.
211: * @param hmin Specifies the left edge of the CoordinateRect in the canvas, as a fraction of the size of the canvas.
212: * This must be in the range form 0 to 1, or an IllegalArgumentException is thrown.
213: * @param hmax Specifies the right edge of the CoordinateRect in the canvas, as a fraction of the size of the canvas.
214: * This must be in the range form 0 to 1 and must be strictly greater than hmin, or an IllegalArgumentException is thrown.
215: * @param vmin Specifies the top edge of the CoordinateRect in the canvas, as a fraction of the size of the canvas.
216: * This must be in the range form 0 to 1, or an IllegalArgumentException is thrown.
217: * @param vmax Specifies the bottom edge of the CoordinateRect in the canvas, as a fraction of the size of the canvas.
218: * This must be in the range form 0 to 1 and must be strictly greater than vmin, or an IllegalArgumentException is thrown.
219: * @param background The background color of the CoordinateRect. The CoordinateRect is filled with this color
220: * before the Drawables that it contains are drawn. If background is null, no filling takes place
221: * and the canvas shows through.
222: */
223: public void addCoordinateRect(CoordinateRect coords, double hmin,
224: double hmax, double vmin, double vmax, Color background) {
225: if (hmin < 0 || hmin > 1 || hmax < 0 || hmax > 1
226: || hmin >= hmax || vmin < 0 || vmin > 1 || vmax < 0
227: || vmax > 1 || vmin >= vmax)
228: throw new IllegalArgumentException(
229: "Illegal values for area covered by CoordinateRect.");
230: if (coords == null)
231: throw new IllegalArgumentException(
232: "Can't add null CoordinateRect to DisplayCanvas.");
233: CRData c = new CRData();
234: c.coords = coords;
235: c.xmin = hmin;
236: c.xmax = hmax;
237: c.ymin = vmin;
238: c.ymax = vmax;
239: c.background = background;
240: if (coordinateRects == null)
241: coordinateRects = new Vector();
242: coordinateRects.addElement(c);
243: coords.setOwner(this );
244: }
245:
246: /**
247: * Add a newly created CoordinateRect covering the specified section of
248: * the canvas. hmin, hmax, vmin, vmax must be in the range 0 to 1.
249: * The index of the new CoordinateRect is returned.
250: */
251: public int addNewCoordinateRect(double hmin, double hmax,
252: double vmin, double vmax) {
253: CoordinateRect c = new CoordinateRect();
254: addCoordinateRect(c, hmin, hmax, vmin, vmax, null);
255: return coordinateRects.size() - 1;
256: }
257:
258: /**
259: * Add a newly created CoordinateRect covering the specified section of
260: * the canvas, with the specfied background color. hmin, hmax, vmin, vmax must be in the range 0 to 1.
261: * The index of the new CoordinateRect is returned.
262: */
263: public int addNewCoordinateRect(double hmin, double hmax,
264: double vmin, double vmax, Color background) {
265: CoordinateRect c = new CoordinateRect();
266: addCoordinateRect(c, hmin, hmax, vmin, vmax, background);
267: return coordinateRects.size() - 1;
268: }
269:
270: /**
271: * Get the first CoordinateRect in this canvas. (If none exists, one is created and
272: * added to the canvas.)
273: */
274: public CoordinateRect getCoordinateRect() {
275: return getCoordinateRect(0);
276: }
277:
278: /**
279: * Get the i-th CoordinateRect in this DisplayCanvas. They are numbered staring from zero.
280: * If there is no i-th rect, null is returned, except that if there
281: * are NO coordinate rects and a request for rect 0 is received,
282: * then a new CoordinateRect is added to fill the entire canvas.
283: */
284: public CoordinateRect getCoordinateRect(int i) {
285: if (i == 0
286: && (coordinateRects == null || coordinateRects.size() == 0))
287: addNewCoordinateRect(0, 1, 0, 1);
288: if (coordinateRects == null || i < 0
289: || i >= coordinateRects.size())
290: return null;
291: else
292: return ((CRData) coordinateRects.elementAt(i)).coords;
293: }
294:
295: /**
296: * Return CoordinateRect that contains the specified pixel, or
297: * null if there is none. The CoordinateRects are searched in
298: * reverse order, so that the "top" CoordinateRect at that point is
299: * returned. Note that this method only makes sense if the canvas
300: * has already been displayed.
301: * (Mostly, this is for internal use in this class.)
302: */
303: public CoordinateRect findCoordinateRectAt(int pixelX, int pixelY) {
304: if (coordinateRects == null)
305: return null;
306: for (int i = coordinateRects.size() - 1; i >= 0; i--) {
307: CRData c = (CRData) coordinateRects.elementAt(i);
308: int width = getSize().width;
309: if (width <= 0)
310: return null;
311: int height = getSize().height;
312: int x = (int) (c.xmin * width);
313: int y = (int) (c.ymin * height);
314: int r = (int) (c.xmax * width);
315: int b = (int) (c.ymax * height);
316: if (pixelX >= x && pixelX < r && pixelY >= y && pixelY < b)
317: return c.coords;
318: }
319: return null;
320: }
321:
322: /**
323: * Should be called whenever the contents of the canvas have changed and so
324: * it needs to need to be redrawn. (This causes the off-screen image to be redrawn.
325: * A simple call to repaint() does not do this.)
326: * If only one CoordinateRect needs to be repainted, you can call doRedraw(int i) or
327: * or doRedraw(CoordinateRect c), which can be more efficient than redrawing the whole canvas.
328: * If an error message is displayed, it will be cleared.
329: */
330: synchronized public void doRedraw() {
331: // Should be called whenever the coordinate rects need to be repainted.
332: // If only one needs to be repainted, you can call doRedraw(int i) or
333: // or doRedraw(CoordinateRect c), which can be more efficient.
334: // If an error message is displayed, this will not take effect until
335: // the error message is cleared.
336: OSCvalid = false;
337: if (errorMessage != null)
338: clearErrorMessage(); // does repaint
339: else
340: repaint();
341: }
342:
343: /**
344: * To be called when the contents of one of the CordinateRects have changed and so
345: * it needs to need to be redrawn. (This causes the off-screen image to be redrawn.
346: * A simple call to repaint() does not do this.)
347: * If an error message is displayed, it will be cleared.
348: *
349: * @param coordRectIndex The index of the CoordinateRect to be redrawn, where the first CoordinateRect is at index zero.
350: * If there is no such CoordinateRect, then nothing is done.
351: */
352: synchronized public void doRedraw(int coordRectIndex) {
353: if (coordinateRects != null && coordRectIndex >= 0
354: && coordRectIndex < coordinateRects.size()) {
355: CRData c = (CRData) coordinateRects
356: .elementAt(coordRectIndex);
357: OSCvalid = false;
358: if (errorMessage != null)
359: clearErrorMessage(); // does repaint
360: else {
361: int width = getSize().width;
362: int height = getSize().height;
363: int x = (int) (c.xmin * width);
364: int y = (int) (c.ymin * height);
365: int w = (int) (c.xmax * width) - x;
366: int h = (int) (c.ymax * height) - y;
367: repaint(x, y, w, h);
368: }
369: }
370: }
371:
372: /**
373: * To be called when the contents of one of the CordinateRects have changed and so
374: * it needs to need to be redrawn. (This causes the off-screen image to be redrawn.
375: * A simple call to repaint() does not do this.)
376: * If an error message is displayed, it will be cleared.
377: *
378: * @param coords The CoordinateRect to be redrawn. If coords is not in this DisplayCanvas, nothing is done.
379: */
380: synchronized public void doRedraw(CoordinateRect coords) {
381: int size = (coordinateRects == null) ? -1 : coordinateRects
382: .size();
383: for (int i = 0; i < size; i++)
384: if (((CRData) coordinateRects.elementAt(i)).coords == coords) {
385: doRedraw(i);
386: break;
387: }
388: }
389:
390: //------------------- InputObject/Computable interfaces ---------------------
391:
392: /**
393: * This is generally called by a Controller. It calls the checkInput() method of
394: * any InputObject displayed on this Canvas.
395: */
396: synchronized public void checkInput() {
397: if (coordinateRects != null) {
398: int top = coordinateRects.size();
399: for (int i = 0; i < top; i++)
400: ((CRData) coordinateRects.elementAt(i)).coords
401: .checkInput();
402: }
403: }
404:
405: /**
406: * This is generally called by a Controller. It calls the compute() method of
407: * any InputObject displayed on this Canvas.
408: */
409: synchronized public void compute() {
410: if (coordinateRects != null) {
411: int top = coordinateRects.size();
412: for (int i = 0; i < top; i++)
413: ((CRData) coordinateRects.elementAt(i)).coords
414: .compute();
415: }
416: }
417:
418: /**
419: * Method required by InputObject interface; in this class, calls the same method
420: * recursively on any CoordinateRects contained in this DisplayCanvas. This is meant to
421: * be called by JCMPanel.gatherInputs().
422: */
423: public void notifyControllerOnChange(Controller c) {
424: if (coordinateRects != null) {
425: int top = coordinateRects.size();
426: for (int i = 0; i < top; i++)
427: ((CRData) coordinateRects.elementAt(i)).coords
428: .notifyControllerOnChange(c);
429: }
430: }
431:
432: //-------------------- Error Reporter Stuff ------------------------------------
433:
434: /**
435: * Get color that is used as a background when the canvas displays an error message.
436: */
437: public Color getErrorBackground() {
438: return errorBackground;
439: }
440:
441: /**
442: * Set color to be used as a background when the canvas displays an error message.
443: * The default is a light green. If the specified Color value is null, nothing is done.
444: */
445: public void setErrorBackground(Color c) {
446: if (c != null)
447: errorBackground = c;
448: }
449:
450: /**
451: * Get color that is used for the text when the canvas displays an error message.
452: */
453: public Color getErrorForeground() {
454: return errorForeground;
455: }
456:
457: /**
458: * Set color to be used for the text when the canvas displays an error message.
459: * The default is a dark green. If the specified Color value is null, nothing is done.
460: */
461: public void setErrorForeground(Color c) {
462: if (c != null)
463: errorForeground = c;
464: }
465:
466: /**
467: * Get the error message that is currently displayed on the canvas. If no error
468: * is displyed, the return value is null.
469: */
470: synchronized public String getErrorMessage() {
471: return errorMessage;
472: }
473:
474: /**
475: * Set an error message to be displayed on the canvas. This method is generally called by
476: * a Controller or a LimitControlPanel. If you call it directly, use null as the first parameter.
477: *
478: * @param c The Controller, if any, that is calling this routine. This controller will be notified
479: * when the error message is cleared. If the method is not being called by a contrller, this
480: * parameter should be set to null.
481: * @param message The error message to be displayed. If the value is null or is a blank string,
482: * the current message, if any, is cleared.
483: */
484: synchronized public void setErrorMessage(Controller c,
485: String message) {
486: if (message == null || message.trim().length() == 0) {
487: if (errorMessage != null) {
488: clearErrorMessage();
489: if (errorSource != c)
490: errorSource.errorCleared();
491: repaint();
492: }
493: } else {
494: errorMessage = message.trim();
495: errorSource = c;
496: OSCvalid = false;
497: repaint();
498: }
499: }
500:
501: /**
502: * Clear the error message, if any, that is currently displayed on the canvas.
503: */
504: synchronized public void clearErrorMessage() {
505: if (errorMessage == null)
506: return;
507: errorMessage = null;
508: if (errorSource != null)
509: errorSource.errorCleared();
510: errorSource = null;
511: repaint();
512: }
513:
514: // ---------------------- ErrorReporter Implementation ----------------------------
515:
516: private Color errorBackground = new Color(220, 255, 220);
517: private Color errorForeground = new Color(0, 120, 0);
518: private String errorMessage;
519: private Controller errorSource;
520: private Draggable dragged; // The draggable object, if any, that is being dragged.
521:
522: /**
523: * This has been overridden to handle the mouse zoom feature.
524: * Not meant to be called directly.
525: */
526: public void processMouseEvent(MouseEvent evt) {
527: // If an error message is displayed, get rid of it and
528: // ignore the mouse click.
529: if (evt.getID() == MouseEvent.MOUSE_PRESSED) {
530: dragging = false; // Shouldn't be possible, but some old, buggy versions of Java made it so.
531: dragged = null;
532: if (errorMessage != null) {
533: if (errorSource != null)
534: errorSource.errorCleared();
535: errorSource = null;
536: errorMessage = null;
537: repaint();
538: evt.consume();
539: return;
540: }
541: CoordinateRect c = findCoords(evt);
542: if (c != null)
543: dragged = c.checkDraggables(evt);
544: if (dragged != null)
545: return;
546: if (handleMouseZooms
547: && !(evt.getClickCount() > 1 || evt.isAltDown()
548: || evt.isMetaDown() || evt.isControlDown())) {
549: super .processMouseEvent(evt);
550: if (!evt.isConsumed())
551: doMouseZoom_pressed(evt);
552: return;
553: }
554: } else if (evt.getID() == MouseEvent.MOUSE_RELEASED
555: && handleMouseZooms && dragged != null) {
556: dragged.finishDrag(evt);
557: dragged = null;
558: return;
559: } else if (evt.getID() == MouseEvent.MOUSE_RELEASED
560: && handleMouseZooms && dragging) {
561: doMouseZoom_released(evt);
562: return;
563: }
564: super .processMouseEvent(evt);
565: }
566:
567: /**
568: * This has been overridden to handle the mouse zoom feature.
569: * Not meant to be called directly.
570: */
571: public void processMouseMotionEvent(MouseEvent evt) {
572: if (dragged != null && evt.getID() == MouseEvent.MOUSE_DRAGGED)
573: dragged.continueDrag(evt);
574: else if (dragging && evt.getID() == MouseEvent.MOUSE_DRAGGED)
575: doMouseZoom_moved(evt);
576: else
577: super .processMouseMotionEvent(evt);
578: }
579:
580: private void drawErrorMessage(Graphics g) {
581: if (errorMessage == null)
582: return;
583:
584: Font font = new Font("Helvetica", Font.BOLD, 12);
585: FontMetrics fm = g.getFontMetrics(font);
586:
587: int width = getSize().width;
588: int height = getSize().height;
589: int lineHeight = fm.getHeight();
590: int leading = fm.getLeading();
591: int messageWidth = width - 80;
592: int left = 30;
593: int maxLines = (height - 60 - lineHeight) / lineHeight;
594: if (maxLines <= 0)
595: maxLines = 1;
596:
597: StringTokenizer t = new StringTokenizer(errorMessage, " \t\r\n");
598: int lineCt = 0;
599: String[] errorMessageList = new String[maxLines];
600: String line = " "; // indent first line
601: while (t.hasMoreTokens()) {
602: String word = t.nextToken();
603: if (fm.stringWidth(word) > messageWidth) {
604: String w = "";
605: int dots = fm.stringWidth("...");
606: for (int c = 0; c < word.length(); c++) {
607: w += word.charAt(c);
608: if (fm.stringWidth(w) + dots > messageWidth)
609: break;
610: }
611: word = w;
612: }
613: String linePlusWord = line + " " + word;
614: if (fm.stringWidth(linePlusWord) > messageWidth) {
615: errorMessageList[lineCt] = line;
616: lineCt++;
617: if (lineCt == maxLines)
618: break;
619: line = word;
620: } else {
621: line = linePlusWord;
622: }
623: }
624: if (lineCt < maxLines) {
625: errorMessageList[lineCt] = line;
626: lineCt++;
627: }
628: if (lineCt == 1)
629: errorMessageList[0] += " "; // for proper centering
630:
631: int boxWidth = width - 60;
632: int boxHeight = (lineCt + 1) * lineHeight + 50;
633: int top = height / 2 - boxHeight / 2;
634: if (top < 0)
635: top = 0;
636:
637: g.setColor(getBackground());
638: g.fillRect(0, 0, width, height);
639: g.setColor(errorBackground);
640: g.fillRect(left, top, boxWidth, boxHeight);
641: g.setColor(errorForeground);
642: g.drawRect(left, top, boxWidth, boxHeight);
643: g.drawRect(left + 1, top + 1, boxWidth - 2, boxHeight - 2);
644: g.drawLine(left, top + 23 + lineHeight, left + boxWidth - 2,
645: top + 23 + lineHeight);
646: g.drawLine(left, top + 24 + lineHeight, left + boxWidth - 2,
647: top + 24 + lineHeight);
648: g.setFont(font);
649: g.drawString("ERROR MESSAGE", width / 2
650: - fm.stringWidth("(Error Message)") / 2, top + 10
651: + lineHeight);
652: if (lineCt == 1)
653: g.drawString(errorMessageList[0], width / 2
654: - fm.stringWidth(errorMessageList[0]) / 2, top + 35
655: + 2 * lineHeight);
656: else {
657: for (int i = 0; i < lineCt; i++) {
658: g.drawString(errorMessageList[i], left + 10, top + 35
659: + (i + 2) * lineHeight - leading);
660: }
661: }
662: } // end drawErrorMessage();
663:
664: // ------------ Handle Mouse Zooming -----------------------------------------
665:
666: private transient boolean dragging, draggingZoomWindow;
667: private transient CRData draggingInRect;
668: private transient int dragXmax, dragXmin, dragYmax, dragYmin;
669: private transient int lastX, lastY, startX, startY;
670:
671: private CoordinateRect findCoords(MouseEvent evt) {
672: // Find coord rect containing the mouse.
673: int xMouse = evt.getX();
674: int yMouse = evt.getY();
675: int size = (coordinateRects == null) ? -1 : coordinateRects
676: .size();
677: int width = getSize().width;
678: int height = getSize().height;
679: for (int i = size - 1; i >= 0; i--) {
680: CRData c = (CRData) coordinateRects.elementAt(i);
681: double xmin = (int) (c.xmin * width);
682: double ymin = (int) (c.ymin * height);
683: double xmax = (int) (c.xmax * width) - 1;
684: double ymax = (int) (c.ymax * height) - 1;
685: if (xMouse >= xmin && xMouse <= xmax && yMouse >= ymin
686: && yMouse <= ymax)
687: return c.coords;
688: }
689: return null;
690: }
691:
692: private synchronized void doMouseZoom_pressed(MouseEvent evt) {
693: // Called from processMouseEvent, above.
694: // Ignore multiple clicks and clicks with other than button 1
695: // and clicks modified with any key except shift.
696: if (evt.getClickCount() > 1 || evt.isAltDown()
697: || evt.isMetaDown() || evt.isControlDown())
698: return;
699: int xMouse = evt.getX();
700: int yMouse = evt.getY();
701: int size = (coordinateRects == null) ? -1 : coordinateRects
702: .size();
703: int width = getSize().width;
704: int height = getSize().height;
705: for (int i = size - 1; i >= 0; i--) {
706: CRData c = (CRData) coordinateRects.elementAt(i);
707: dragXmin = (int) (c.xmin * width);
708: dragYmin = (int) (c.ymin * height);
709: dragXmax = (int) (c.xmax * width) - 1;
710: dragYmax = (int) (c.ymax * height) - 1;
711: if (xMouse >= dragXmin && xMouse <= dragXmax
712: && yMouse >= dragYmin && yMouse <= dragYmax) {
713: dragging = true;
714: draggingZoomWindow = false;
715: draggingInRect = c;
716: startX = xMouse;
717: startY = yMouse;
718: lastX = xMouse;
719: lastY = yMouse;
720: break;
721: }
722: }
723: }
724:
725: private synchronized void doMouseZoom_released(MouseEvent evt) {
726: Graphics g = getGraphics();
727: putDragRect(g);
728: g.dispose();
729: CoordinateRect c = draggingInRect.coords;
730: if ((Math.abs(lastX - startX) < 4 && Math.abs(lastY - startY) < 4)
731: || Math.abs(startX - lastX) < 2
732: || Math.abs(startY - lastY) < 2) {
733: if (draggingZoomWindow)
734: return;
735: if (evt.isShiftDown())
736: c.zoomOutFromPixel(startX, startY);
737: else
738: c.zoomInOnPixel(startX, startY);
739: } else {
740: c.setLimits(c.pixelToX(startX), c.pixelToX(lastX), c
741: .pixelToY(startY), c.pixelToY(lastY));
742: }
743: dragging = false;
744: }
745:
746: private synchronized void doMouseZoom_moved(MouseEvent evt) {
747: Graphics g = getGraphics();
748: putDragRect(g);
749: lastX = evt.getX();
750: lastY = evt.getY();
751: putDragRect(g);
752: g.dispose();
753: }
754:
755: private void putDragRect(Graphics g) { // (Assume dragging = true)
756: if (lastX < dragXmin)
757: lastX = dragXmin;
758: if (lastX > dragXmax)
759: lastX = dragXmax;
760: if (lastY < dragYmin)
761: lastY = dragYmin;
762: if (lastY > dragYmax)
763: lastY = dragYmax;
764: if ((Math.abs(startX - lastX) < 4 && Math.abs(startY - lastY) < 4)
765: || Math.abs(startX - lastX) < 2
766: || Math.abs(startY - lastY) < 2)
767: return;
768: draggingZoomWindow = true;
769: Color bc = draggingInRect.background;
770: if (bc == null)
771: bc = getBackground();
772: g.setXORMode(bc);
773: if (bc.getRed() <= 100 && bc.getGreen() <= 100
774: && bc.getBlue() <= 150)
775: g.setColor(Color.white);
776: else
777: g.setColor(Color.black);
778: int x, y, w, h;
779: if (startX < lastX) {
780: x = startX;
781: w = lastX - startX;
782: } else {
783: x = lastX;
784: w = startX - lastX;
785: }
786: if (startY < lastY) {
787: y = startY;
788: h = lastY - startY;
789: } else {
790: y = lastY;
791: h = startY - lastY;
792: }
793: g.drawRect(x, y, w, h);
794: g.setPaintMode();
795: }
796:
797: //--------- More implementation details... ----------------------------------
798:
799: /**
800: * This has been overridden to return a default size of 350-by-350 pixels.
801: * Not usually called directly.
802: */
803: public Dimension getPreferredSize() {
804: return new Dimension(350, 350);
805: }
806:
807: private transient Image OSC;
808: private transient Graphics OSG;
809: private transient boolean OSCvalid;
810: private transient int OSCwidth = -1, OSCheight = -1;
811:
812: private void drawCoordinateRects(Graphics g, int width, int height,
813: Rectangle clip) {
814: g.setColor(getBackground());
815: g.fillRect(0, 0, width, height);
816: int count = (coordinateRects == null) ? -1 : coordinateRects
817: .size();
818: try {
819: for (int i = 0; i < count; i++) {
820: CRData c = (CRData) coordinateRects.elementAt(i);
821: Rectangle bounds = new Rectangle();
822: bounds.x = (int) (c.xmin * width);
823: bounds.y = (int) (c.ymin * height);
824: bounds.width = (int) (c.xmax * width) - bounds.x;
825: bounds.height = (int) (c.ymax * height) - bounds.y;
826: Rectangle clipThisRect = (clip == null) ? bounds
827: : bounds.intersection(clip);
828: if (clip == null || !clipThisRect.isEmpty()) {
829: g.setClip(clipThisRect);
830: if (c.background != null) {
831: g.setColor(c.background);
832: g.fillRect(bounds.x, bounds.y, bounds.width,
833: bounds.height);
834: }
835: c.coords.draw(g, bounds.x, bounds.y, bounds.width,
836: bounds.height);
837: }
838: }
839: } finally {
840: g.setClip(clip);
841: }
842: }
843:
844: /**
845: * This has been overridden to implemnt double-buffering.
846: * Not meant to be called directly.
847: */
848: public void update(Graphics g) {
849: paint(g);
850: }
851:
852: /**
853: * Draw the contents of the DisplayCanvas.
854: * Not usually called directly.
855: */
856: synchronized public void paint(Graphics g) {
857: if (errorMessage == null) {
858: try {
859: checkOSC();
860: if (OSC != null)
861: g.drawImage(OSC, 0, 0, this );
862: else
863: drawCoordinateRects(g, getSize().width,
864: getSize().height, g.getClipBounds());
865: if (dragging)
866: putDragRect(g);
867: } catch (RuntimeException e) {
868: errorMessage = "Internal Error? (stack trace on System.out): "
869: + e.toString();
870: e.printStackTrace();
871: g.setClip(0, 0, getSize().width, getSize().height);
872: }
873: }
874: if (errorMessage != null) {
875: drawErrorMessage(g);
876: OSCvalid = false;
877: return;
878: }
879: }
880:
881: /**
882: * Draws the specified item in the first CoordinateRect in this canvas.
883: * It is drawn on screen and on the off-screen canvas, if there is one.
884: * However, no information is kept about this item, so the drawing will
885: * disappear the next time the off-screen canvas is re-drawn (if there
886: * is an off-screen canvas) or the next time the canvas is repainted
887: * (is there is no off-screen canvas). If the canvas contains no
888: * CoordinateRect when this is called, a new one is added. Note that
889: * this method should only be called after the canvas has appeared on
890: * the screen.
891: */
892: public void drawTemp(DrawTemp drawItem) {
893: if (coordinateRects == null || coordinateRects.size() == 0)
894: addCoordinateRect(new CoordinateRect());
895: drawTemp(drawItem, 0);
896: }
897:
898: /**
899: * Draws the specified item in the specified CoordinateRect in this canvas.
900: * It is drawn on screen and on the off-screen canvas, if there is one.
901: * However, no information is kept about this item, so the drawing will
902: * disappear the next time the off-screen canvas is re-drawn (if there
903: * is an off-screen canvas) or the next time the canvas is repainted
904: * (is there is no off-screen canvas). Note that
905: * this method should only be called after the canvas has appeared on
906: * the screen.
907: *
908: * @param drawItem The non-null object that is to be drawn
909: * @param coordRectIndex The index of the CoordinateRect in which it
910: * is to be drawn, where the index of the fist CoordinateRect
911: * added to the canvas is zero, and so on. If there is
912: * no CoordinateRect with the specified index, an IllegalArgumentException
913: * is thrown.
914: */
915: synchronized public void drawTemp(DrawTemp drawItem,
916: int coordRectIndex) {
917: if (coordRectIndex < 0
918: || coordRectIndex >= coordinateRects.size())
919: throw new IllegalArgumentException(
920: "Invalid CoordinateRect index, " + coordRectIndex);
921: Graphics g = getGraphics();
922: if (g == null)
923: return;
924: CRData c = (CRData) coordinateRects.elementAt(coordRectIndex);
925: Rectangle bounds = new Rectangle();
926: bounds.x = (int) (c.xmin * getSize().width);
927: bounds.y = (int) (c.ymin * getSize().height);
928: bounds.width = (int) (c.xmax * getSize().width) - bounds.x;
929: bounds.height = (int) (c.ymax * getSize().height) - bounds.y;
930: g.setClip(bounds);
931: drawItem.draw(g, c.coords);
932: g.dispose();
933: if (useOffscreenCanvas && OSCvalid && OSC != null) {
934: g = OSC.getGraphics();
935: g.setClip(bounds);
936: drawItem.draw(g, c.coords);
937: g.dispose();
938: }
939: }
940:
941: synchronized private void checkOSC() { // make off-screen image, if necessary
942: if (!useOffscreenCanvas
943: || (OSCvalid == true && OSC != null
944: && OSCwidth == getSize().width && OSCheight == getSize().height))
945: return;
946: int width = getSize().width;
947: int height = getSize().height;
948: if (OSC == null || width != OSCwidth || height != OSCheight) {
949: OSCvalid = false;
950: OSCwidth = width;
951: OSCheight = height;
952: try {
953: OSC = createImage(OSCwidth, OSCheight);
954: OSG = OSC.getGraphics();
955: } catch (OutOfMemoryError e) {
956: OSC = null;
957: OSG = null;
958: }
959: }
960: if (OSC == null || OSCvalid)
961: return;
962: OSCvalid = true;
963: OSG.setClip(0, 0, width, height);
964: drawCoordinateRects(OSG, width, height, null);
965: }
966:
967: } // end class DisplayCanvas
|