// Copyright (c) 1996-98 The Regents of the University of California. All // Rights Reserved. Permission to use, copy, modify, and distribute this // software and its documentation for educational, research and non-profit // purposes, without fee, and without a written agreement is hereby granted, // provided that the above copyright notice and this paragraph appear in all // copies. Permission to incorporate this software into commercial products may // be obtained by contacting the University of California. David F. Redmiles // Department of Information and Computer Science (ICS) University of // California Irvine, California 92697-3425 Phone: 714-824-3823. This software // program and documentation are copyrighted by The Regents of the University // of California. The software program and documentation are supplied "as is", // without any accompanying services from The Regents. The Regents do not // warrant that the operation of the program will be uninterrupted or // error-free. The end-user understands that the program was developed for // research purposes and is advised not to rely exclusively on the program for // any reason. IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY // PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, // INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS // DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY // DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE // SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF // CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, // ENHANCEMENTS, OR MODIFICATIONS. // File: RedrawManager.java // Classes: RedrawManager // Original Author: jrobbins@ics.uci.edu // $Id: RedrawManager.java,v 1.19 1997/06/10 23:43:28 jrobbins Exp $ package uci.graphedit; import java.awt.*; import java.awt.image.*; import java.util.*; /** Stores a list of rectangles (and sometimes merges them) for use in * determing the invalid region of a Editor. When a DiagramElement * changes state, it notifies all Editor's that are viewing it, and * the Editor adds bbox of that DiagramElement to its * RedrawManager. Eventually, the RedrawManager will ask the editor * to repaint damaged regions. It is important that the redraw not * happen too soon after the damage, because we would like to use a * single repaint to repair multple damages to the same part of the * screen. * * FEATURE: visual_updates * * FEATURE: redraw_minimal */ public class RedrawManager implements Runnable { //////////////////////////////////////////////////////////////// // constants /** The maximum number of damaged rectangles. */ private final int MAX_NUM_RECTS = 10; //////////////////////////////////////////////////////////////// // instance variables /** The array of damaged rectangles. */ private Rectangle[] _rects = new Rectangle[MAX_NUM_RECTS]; /** The current number of damaged rectangles. */ private int _numRects = 0; /** The editor that controls this RedrawManager. */ private Editor _ed; /** The thread spawned to periodically request repaints. */ private Thread _repairThread; /** Time at which to request next repaint. */ private long deadline = 0; /** Milliseconds between most recent addition of damage and next * redraw. */ private static long _timeDelay = 25; private static String LOCK = new String("LOCK"); // could be any object private static int _lockLevel = 0; //////////////////////////////////////////////////////////////// // constructors /** Construct a new RedrawManager */ public RedrawManager(Editor ed) { for (int i = 0; i < MAX_NUM_RECTS; ++i) { _rects[i] = new Rectangle(); } _ed = ed; _repairThread = new Thread(this, "RepairThread"); // Needs-More-Work: this causes a security violation in Netscape // _repairThread.setDaemon(true); // _repairThread.setPriority(Thread.MAX_PRIORITY); _repairThread.start(); } public void stop() { _repairThread.stop(); } //////////////////////////////////////////////////////////////// // accessors /** Get the minimum number of milliseconds between damage being * added and a repair being done. */ public static long getTimeBetweenRepairs() { return _timeDelay; } /** Set the minimum number of milliseconds between damage being * added and a repair being done. */ public static void setTimeBetweenRepairs(long t) { _timeDelay = t; } /** If screen repainting is fast enough, then try do it more * often. This can be called from the Editor to try to optimize the * tradeoff between screen updates and latency in event * processing. */ public static void moreRepairs() { _timeDelay -= 25; if (_timeDelay < 10) _timeDelay = 10; } /** If screen repainting is getting really slow and the application * cannot process events, then make redraws less frequent */ public static void fewerRepairs() { _timeDelay += 50; if (_timeDelay > 2000) _timeDelay = 2000; } //////////////////////////////////////////////////////////////// // managing damage /** Internal function to forget all damage. */ private void removeAllElements() { _numRects = 0; } /** Reply true iff some damage has been added but not yet redrawn. */ public boolean pendingDamage() { return _numRects != 0; } /** Add a new rectangle of damage, it will soon be redrawn */ public void add(Rectangle r) { if (r.isEmpty()) return; synchronized (LOCK) { if (!merge(r)) _rects[_numRects++].reshape(r.x, r.y, r.width, r.height); if (_numRects == MAX_NUM_RECTS) forceMerge(); if (deadline == 0) deadline = System.currentTimeMillis() + _timeDelay; } } /** Try to merge the given rect into one of my existing damaged * rects. Rects can be merged if they are overlapping. In general, * they shuold be merged when the area added by the merger is * small enough that one large repaint is faster than two smaller * repaints. Reply true on success. */ private boolean merge(Rectangle r) { for (int i = 0; i < _numRects; ++i) { if (r.intersects(_rects[i])) { _rects[i].add(r); return true; } } return false; } /** Merge all the rectangles together, even if that means that a lot * more area is redrawn than needs to be. */ public void forceMerge() { for (int i = 1; i < _numRects; ++i) _rects[0].add(_rects[i]); _numRects = 1; deadline = 1; } //////////////////////////////////////////////////////////////// // locking /** Lock all RedrawManagers during changes to the diagram. This * prevents redrawing of DiagramElement's that are in invalid * states. Needs-More-Work: This takes a fair amount of time. */ public static void lock() { synchronized (LOCK) { _lockLevel++; } } /** Unlock all RedrawManager after changes to the diagram. This * prevents redrawing of DiagramElement's that are in invalid * states. Needs-More-Work: This takes a fair amount of time. */ public static void unlock() { synchronized (LOCK) { _lockLevel--; if (_lockLevel < 0) _lockLevel = 0; } } //////////////////////////////////////////////////////////////// // redraw and frame-rate logic /** The main method of the _repairThread, basically it just keeps * checking if there is damage that has not been repaired, and that * damage is old enough. */ public void run() { while (true) { try { _repairThread.sleep(_timeDelay * 10); } catch (InterruptedException ignore) { } repairDamage(); } } /** Ask the Editor to repaint all damaged regions, if the Editor is * ready and there are not DiagramElement transactions in progress. */ public void repairDamage() { Graphics g = _ed.getGraphics(); if (_lockLevel == 0 && g != null) { synchronized (LOCK) { if (_lockLevel == 0) paint(_ed, g); } } } /** Ask the Editor to repaint all damaged regions, either on screen * or off screen */ private void paint(Editor ed, Graphics g) { long startTime = System.currentTimeMillis(); if (startTime > deadline) { if (Globals.prefs().shouldPaintOffScreen()) paintOffscreen(ed, g); else paintOnscreen(ed, g); Globals.prefs().lastRedrawTime(System.currentTimeMillis() - startTime); deadline = 0; } } /** Ask the Editor to repaint damaged Rectangle's on the Graphics for * the drawing window. This allows the user to see some flicker, but * it gives more feedback that something is happening if the * computer is slow or the diagram is complex. */ private void paintOnscreen(Editor ed, Graphics g) { int F = 16; // fudgefactor, extra redraw area; if (ed == null || g == null) return; for (int i = 0; i < _numRects; ++i) { Rectangle r = _rects[i]; Graphics offG = g.create(); //offG.setColor(ed.getBackground()); // offG.fillRect(r.x, r.y, r.width, r.height); offG.clearRect(r.x-F, r.y-F, r.width+F*2, r.height+F*2); offG.clipRect(r.x-F-1, r.y-F-1, r.width+F*2+2, r.height+F*2+2); ed.paintRect(r, offG); offG.dispose(); } removeAllElements(); } /** Ask the Editor to repaint damaged Rectangle's on an off screen * Image, and then bitblt that Image to the Graphics used by the * drawing window. This takes more time, but the user will never * see any flicker. * * FEATURE: redraw_off_screen */ private void paintOffscreen(Editor ed, Graphics g) { int F = 16; // fudgefactor, extra redraw area; Image offscreen; if (ed == null || g == null) return; for (int i = 0; i < _numRects; ++i) { Rectangle r = _rects[i]; r.reshape(r.x-F, r.y-F, r.width+F*2, r.height+F*2); offscreen = findReusedImage(r.width, r.height, ed); r.width = offscreen.getWidth(null); r.height = offscreen.getHeight(null); if (offscreen == null) { System.out.println("failed to alloc image!!!"); paintOnscreen(ed, g); return; } Graphics offG = offscreen.getGraphics(); offG.translate(-r.x, -r.y); offG.setColor(ed.getBackground()); offG.fillRect(r.x, r.y, r.width, r.height); //offG.clearRect(r.x, r.y, r.width, r.height); offG.clipRect(r.x-1, r.y-1, r.width+2, r.height+2); ed.paintRect(r, offG); // It is important that paintRect and the various Fig draw() // routines do not attempt to call add to register damage. There // should not be any reason to do that. It would cause deadlock... g.drawImage(offscreen, r.x, r.y, null); offG.dispose(); offscreen.flush(); } removeAllElements(); } /** Images used to draw off screen in common sizes. Creating these * images on first use and saving them for later uses, avoids all * lot of allocation and deallocation of large objects. */ protected Image image64x64, image256x64, image64x256, image256x256, image64x512, image512x64, image512x512; /** Reply an Image that is as least as big as (x, y), hopefully not * too much larger. If no retained image is suitable, ask the given * Editor to make a new one. */ protected Image findReusedImage(int x, int y, Editor ed) { if (x < 64 && y < 64) { if (image64x64 == null) image64x64 = ed.createImage(64, 64); return image64x64; } else if (x < 256 && y < 64) { if (image256x64 == null) image256x64 = ed.createImage(256, 64); return image256x64; } else if (x < 64 && y < 256) { if (image64x256 == null) image64x256 = ed.createImage(64, 256); return image64x256; } else if (x < 256 && y < 256) { if (image256x256 == null) image256x256 = ed.createImage(256, 256); return image256x256; } else if (x < 64 && y < 512) { if (image64x512 == null) image64x512 = ed.createImage(64, 512); return image64x512; } else if (x < 512 && y < 64) { if (image512x64 == null) image512x64 = ed.createImage(512, 64); return image512x64; } else if (x < 512 && y < 512) { if (image512x512 == null) image512x512 = ed.createImage(512, 512); return image512x512; } else return ed.createImage(x, y); } } /* end class RedrawManager */