/**
* FrameGraphics.java
* Copyright (C) 2010 New Zealand Digital Library, http://expeditee.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package org.expeditee.gui;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import org.expeditee.core.Clip;
import org.expeditee.core.Colour;
import org.expeditee.core.Dimension;
import org.expeditee.core.Image;
import org.expeditee.core.bounds.PolygonBounds;
import org.expeditee.gio.EcosystemManager;
import org.expeditee.gio.input.KBMInputEvent.Key;
import org.expeditee.gio.input.StandardInputEventListeners;
import org.expeditee.items.Circle;
import org.expeditee.items.Dot;
import org.expeditee.items.Item;
import org.expeditee.items.Item.HighlightMode;
import org.expeditee.items.Line;
import org.expeditee.items.UserAppliedPermission;
import org.expeditee.items.XRayable;
import org.expeditee.items.widgets.Widget;
import org.expeditee.items.widgets.WidgetEdge;
import org.expeditee.settings.UserSettings;
public class FrameGraphics {
// Final passes to rendering the current frame
private static LinkedList _frameRenderPasses = new LinkedList();
private static Item _lastToolTippedItem = null;
/** Static-only class. */
private FrameGraphics()
{
}
/**
* Gets an image of the given frame that has the given dimensions. If clip
* is not null, only the areas inside clip are guaranteed to be drawn.
*/
public static Image getFrameImage(Frame toPaint, Clip clip, Dimension size)
{
return getFrameImage(toPaint, clip, size, true, true);
}
/**
* Gets an image of the given frame that has the given dimensions. If clip
* is not null, only the areas inside clip are guaranteed to be drawn.
*/
public static Image getFrameImage(Frame toPaint, Clip clip, Dimension size, boolean isActualFrame, boolean createVolatile)
{
if (toPaint == null) {
return null;
}
// the buffer is not valid, so it must be recreated
if (!toPaint.isBufferValid()) {
Image buffer = toPaint.getBuffer();
// Get the size if it hasn't been given
if (size == null) {
// Can't get the size if there is no buffer
if (buffer == null) {
return null;
} else {
size = buffer.getSize();
}
}
if (buffer == null || !buffer.getSize().equals(size)) {
buffer = Image.createImage(size.width, size.height, createVolatile);
toPaint.setBuffer(buffer);
clip = null;
}
EcosystemManager.getGraphicsManager().pushDrawingSurface(buffer);
EcosystemManager.getGraphicsManager().pushClip(clip);
paintFrame(toPaint, isActualFrame, createVolatile);
EcosystemManager.getGraphicsManager().popDrawingSurface();
}
return toPaint.getBuffer();
}
/** TODO: Comment. cts16 */
public static void paintFrame(Frame toPaint, boolean isActualFrame, boolean createVolitile)
{
Clip clip = EcosystemManager.getGraphicsManager().peekClip();
// Prepare render passes
if (isActualFrame) {
for (FrameRenderPass pass : _frameRenderPasses) {
clip = pass.paintStarted(clip);
}
}
// TODO: Revise images and clip - VERY IMPORTANT
// Nicer looking lines, but may be too jerky while
// rubber-banding on older machines
if (UserSettings.AntiAlias.get()) {
EcosystemManager.getGraphicsManager().setAntialiasing(true);
}
// If we are doing @f etc... then have a clear background if its the default background color
Colour backgroundColor = null;
// Need to allow transparency for frameImages
if (createVolitile) {
backgroundColor = toPaint.getPaintBackgroundColor();
} else {
backgroundColor = toPaint.getBackgroundColor();
if (backgroundColor == null) {
backgroundColor = Item.TRANSPARENT;
}
}
EcosystemManager.getGraphicsManager().clear(backgroundColor);
List- itemsToPaintCanditates = new LinkedList
- ();
List paintWidgets;
if (isActualFrame) {
// Add all the items for this frame and any other from other
// frames
itemsToPaintCanditates.addAll(toPaint.getAllItems());
paintWidgets = toPaint.getAllOverlayWidgets();
paintWidgets.addAll(toPaint.getInteractiveWidgets());
} else {
itemsToPaintCanditates.addAll(toPaint.getVisibleItems());
itemsToPaintCanditates.addAll(toPaint.getVectorItems());
paintWidgets = toPaint.getInteractiveWidgets();
}
HashSet
- paintedFillsAndLines = new HashSet
- ();
// FIRST: Paint widgets swing gui (not expeditee gui) .
// Note that these are the anchored widgets
ListIterator widgetItor = paintWidgets.listIterator(paintWidgets.size());
while (widgetItor.hasPrevious()) {
// Paint first-in-last-serve ordering - like swing
// If it is done the other way around then widgets are covered up by
// the box that is supposed to be underneath
Widget iw = widgetItor.previous();
if (clip == null || clip.getBounds() == null || clip.getBounds().intersects(iw.getBounds())) {
paintedFillsAndLines.addAll(iw.getItems());
//iw.paint(bg);
//PaintItem(bg, iw.getItems().get(4));
}
}
// Filter out items that do not need to be painted
List
- paintItems;
HashSet
- fillOnlyItems = null; // only contains items that do
// not need drawing but fills
// might
if (clip == null) {
paintItems = itemsToPaintCanditates;
} else {
fillOnlyItems = new HashSet
- ();
paintItems = new LinkedList
- ();
for (Item i : itemsToPaintCanditates) {
if (clip == null || i.isInDrawingArea(clip.getBounds())) {
paintItems.add(i);
} else if (i.isEnclosed()) {
// just add all fill items despite possibility of fills
// not being in clip
// because it will be faster than having to test twice
// for fills that do need
// repainting.
fillOnlyItems.add(i);
}
}
}
// Only paint files and lines once ... between anchored AND free items
PaintPictures(paintItems, fillOnlyItems, paintedFillsAndLines);
PaintLines(itemsToPaintCanditates);
widgetItor = paintWidgets.listIterator(paintWidgets.size());
while (widgetItor.hasPrevious()) {
// Paint first-in-last-serve ordering - like swing
// If it is done the other way around then widgets are covered up by
// the box that is supposed to be underneath
Widget iw = widgetItor.previous();
if (clip == null || clip.isNotClipped() || clip.getBounds().intersects(iw.getClip().getBounds())) {
iw.paint();
PaintItem(iw.getItems().get(4));
}
}
// Filter out free items that do not need to be painted
// This is efficient in cases with animation while free items exist
List
- freeItemsToPaint = new LinkedList
- ();
// Dont paint the free items for the other frame in twin frames mode
// if (toPaint == DisplayIO.getCurrentFrame()) {
if (clip == null || clip.isNotClipped()) {
freeItemsToPaint = FreeItems.getInstance();
} else {
freeItemsToPaint = new LinkedList
- ();
fillOnlyItems.clear();
for (Item i : FreeItems.getInstance()) {
if (i.isInDrawingArea(clip.getBounds())) {
freeItemsToPaint.add(i);
} else if (i.isEnclosed()) {
fillOnlyItems.add(i);
}
}
}
// }
if (isActualFrame && toPaint == DisplayController.getCurrentFrame()) {
PaintPictures(freeItemsToPaint, fillOnlyItems, paintedFillsAndLines);
}
// TODO if we can get transparency with FreeItems.getInstance()...
// then text can be done before freeItems
PaintNonLinesNonPicture(paintItems);
// toPaint.setBufferValid(true);
if (isActualFrame && !DisplayController.isAudienceMode()) {
PaintItem(toPaint.getNameItem());
}
if (DisplayController.isTwinFramesOn()) {
List
- lines = new LinkedList
- ();
for (Item i : freeItemsToPaint) {
if (i instanceof Line) {
Line line = (Line) i;
if (toPaint == DisplayController.getCurrentFrame()) {
// If exactly one end of the line is floating...
if (line.getEndItem().isFloating()
^ line.getStartItem().isFloating()) {
// Line l = TransposeLine(line,
// line.getEndItem(),
// toPaint, 0, 0);
// if (l == null)
// l = TransposeLine(line,
// line.getStartItem(), toPaint, 0, 0);
// if (l == null)
// l = line;
// lines.add(l);
} else {
// lines.add(line);
}
} else {
// if (line.getEndItem().isFloating()
// ^ line.getStartItem().isFloating()) {
// lines.add(TransposeLine(line,
// line.getEndItem(), toPaint,
// FrameMouseActions.getY(), -DisplayIO
// .getMiddle()));
// lines.add(TransposeLine(line, line
// .getStartItem(), toPaint,
// FrameMouseActions.getY(), -DisplayIO
// .getMiddle()));
// }
}
}
}
if (isActualFrame) {
PaintLines(lines);
}
} else {
if (isActualFrame) {
PaintLines(freeItemsToPaint);
}
}
if (isActualFrame && toPaint == DisplayController.getCurrentFrame()) {
PaintNonLinesNonPicture(freeItemsToPaint);
}
// Repaint popups / drags... As well as final passes
if (isActualFrame) {
for (FrameRenderPass pass : _frameRenderPasses) {
pass.paintPreLayeredPanePass();
}
//if (PopupManager.getInstance() != null) PopupManager.getInstance().paintLayeredPane(clip == null ? null : clip.getBounds());
for (FrameRenderPass pass : _frameRenderPasses) {
pass.paintFinalPass();
}
}
// paint tooltip
if(!FreeItems.hasItemsAttachedToCursor()) {
Item current = FrameUtils.getCurrentItem();
if(current != null) {
current.paintTooltip();
}
if (_lastToolTippedItem != null) {
_lastToolTippedItem.clearTooltips();
}
_lastToolTippedItem = current;
}
if (FreeItems.hasCursor() && DisplayController.getCursor() == Item.DEFAULT_CURSOR) {
PaintNonLinesNonPicture(FreeItems.getCursor());
}
}
private static void PaintNonLinesNonPicture(List
- toPaint)
{
for (Item i : toPaint) {
if (!(i instanceof Line) && !(i instanceof XRayable)) {
PaintItem(i);
}
}
}
/**
* Paint the lines that are not part of an enclosure.
*
* @param g
* @param toPaint
*/
private static void PaintLines(List
- toPaint)
{
// Use this set to keep track of the items that have been painted
Collection
- done = new HashSet
- ();
for (Item i : toPaint) {
if (i instanceof Line) {
Line l = (Line) i;
if (done.contains(l)) {
l.paintArrows();
} else {
// When painting a line all connected lines are painted too
done.addAll(l.getAllConnected());
if (l.getStartItem().getEnclosedArea() == 0) {
PaintItem(i);
}
}
}
}
}
/**
* Paint filled areas and their surrounding lines as well as pictures. Note:
* floating widgets are painted as fills
*
* @param g
* @param toPaint
*/
private static void PaintPictures(List
- toPaint, HashSet
- fillOnlyItems, HashSet
- done)
{
List
- toFill = new LinkedList
- ();
for (Item i : toPaint) {
// Ignore items that have already been done!
// Also ignore invisible items..
// TODO possibly ignore invisible items before coming to this method?
if (done.contains(i)) {
continue;
}
if (i instanceof XRayable) {
toFill.add(i);
done.addAll(i.getConnected());
} else if (i.hasEnclosures()) {
for (Item enclosure : i.getEnclosures()) {
if (!toFill.contains(enclosure)) {
toFill.add(enclosure);
}
}
done.addAll(i.getConnected());
} else if (i.isLineEnd() && (!DisplayController.isAudienceMode() || !i.isConnectedToAnnotation())) {
toFill.add(i);
done.addAll(i.getAllConnected());
}
}
if (fillOnlyItems != null) {
for (Item i : fillOnlyItems) {
if (done.contains(i)) {
continue;
} else if (!DisplayController.isAudienceMode() || !i.isConnectedToAnnotation()) {
toFill.add(i);
}
done.addAll(i.getAllConnected());
}
}
// Sort the items to fill
Collections.sort(toFill, new Comparator
- () {
@Override
public int compare(Item a, Item b) {
Double aArea = a.getEnclosedArea();
Double bArea = b.getEnclosedArea();
int cmp = aArea.compareTo(bArea);
if (cmp == 0) {
// Shapes to the left go underneath
PolygonBounds pA = a.getEnclosedShape();
PolygonBounds pB = b.getEnclosedShape();
if (pA == null || pB == null) {
return 0;
}
return new Integer(pA.getMinX()).compareTo(pB.getMinX());
}
return cmp * -1;
}
});
for (Item i : toFill) {
if (i instanceof XRayable) {
PaintItem(i);
} else {
// Paint the fill and lines
i.paintFill();
List lines = i.getLines();
if (lines.size() > 0) {
PaintItem(lines.get(0));
}
}
}
}
/** Displays the given Item on the screen. */
public static void PaintItem(Item i)
{
if (i == null) {
return;
}
// do not paint annotation items in audience mode
if (!DisplayController.isAudienceMode() || (!i.isConnectedToAnnotation() && !i.isAnnotation()) || i == FrameUtils.getLastEdited())
{
i.paint();
}
}
/**
* Highlights an item on the screen Note: All graphics are handled by the
* Item itself.
*
* @param i
* The Item to highlight.
* @param val
* True if the highlighting is being shown, false if it is being
* erased.
* @return the item that was highlighted
*/
public static Item Highlight(Item i) {
if ((i instanceof Line)) {
// Check if within 20% of the end of the line
Line l = (Line) i;
Item toDisconnect = l.getEndPointToDisconnect(DisplayController.getMousePosition());
// Brook: Widget Edges do not have such a context
if (toDisconnect != null && !(i instanceof WidgetEdge)) {
Item.HighlightMode newMode = toDisconnect.getHighlightMode();
if (FreeItems.hasItemsAttachedToCursor()) {
newMode = Item.HighlightMode.Normal;
}
// unhighlight all the other dots
for (Item conn : toDisconnect.getAllConnected()) {
conn.setHighlightMode(Item.HighlightMode.None);
conn.setHighlightColorToDefault();
}
l.setHighlightMode(newMode);
l.setHighlightColorToDefault();
// highlight the dot that will be in disconnect mode
toDisconnect.setHighlightMode(newMode);
toDisconnect.setHighlightColorToDefault();
i = toDisconnect;
} else {
if (StandardInputEventListeners.kbmStateListener.isKeyDown(Key.SHIFT)) {
for(Item j : i.getAllConnected()) {
if(j instanceof Dot && !j.equals(i)) {
j.setHighlightMode(HighlightMode.None);
j.setHighlightColorToDefault();
}
}
l.getStartItem().setHighlightMode(HighlightMode.Connected);
l.getStartItem().setHighlightColorToDefault();
l.getEndItem().setHighlightMode(HighlightMode.Connected);
l.getEndItem().setHighlightColorToDefault();
} else {
for(Item j : i.getAllConnected()) {
if(j instanceof Dot && !j.equals(i)) {
j.setHighlightMode(HighlightMode.Connected);
j.setHighlightColorToDefault();
}
}
}
// Collection
- connected = i.getAllConnected();
// for (Item conn : connected) {
// conn.setHighlightMode(Item.HighlightMode.Connected);
// }
}
} else if (i instanceof Circle) {
i.setHighlightMode(Item.HighlightMode.Connected);
i.setHighlightColorToDefault();
} else if (!i.isVisible()) {
changeHighlightMode(i, Item.HighlightMode.Connected, null);
} else if (i instanceof Dot) {
// highlight the dot
if (i.hasPermission(UserAppliedPermission.full)) {
changeHighlightMode(i, Item.HighlightMode.Normal, Item.HighlightMode.None);
} else {
changeHighlightMode(i, Item.HighlightMode.Connected, Item.HighlightMode.Connected);
}
// highlight connected dots, but only if there aren't items being carried on the cursor
if(FreeItems.getInstance().size() == 0) {
if (StandardInputEventListeners.kbmStateListener.isKeyDown(Key.SHIFT)) {
for(Item j : i.getAllConnected()) {
if(j instanceof Dot && !j.equals(i)) {
j.setHighlightMode(HighlightMode.Connected);
j.setHighlightColorToDefault();
}
}
} else {
for(Item j : i.getAllConnected()) {
if(j instanceof Dot && !j.equals(i)) {
j.setHighlightMode(HighlightMode.None);
j.setHighlightColorToDefault();
}
}
for(Line l : i.getLines()) {
Item j = l.getOppositeEnd(i);
j.setHighlightMode(HighlightMode.Connected);
j.setHighlightColorToDefault();
}
}
}
} else {
// FrameGraphics.ChangeSelectionMode(i,
// Item.SelectedMode.Normal);
// For polygons need to make sure all other endpoints are
// unHighlighted
if (i.hasPermission(UserAppliedPermission.full)) {
changeHighlightMode(i, Item.HighlightMode.Normal, Item.HighlightMode.None);
} else {
changeHighlightMode(i, Item.HighlightMode.Connected, Item.HighlightMode.Connected);
}
}
DisplayController.requestRefresh(true);
return i;
}
public static void changeHighlightMode(Item item, Item.HighlightMode newMode)
{
changeHighlightMode(item, newMode, newMode);
}
public static void changeHighlightMode(Item item, Item.HighlightMode newMode, Item.HighlightMode connectedNewMode)
{
if (item == null) {
return;
}
if (item.hasVector()) {
for (Item i : item.getParentOrCurrentFrame().getVectorItems()) {
if (i.getEditTarget() == item) {
i.setHighlightMode(newMode);
i.setHighlightColorToDefault();
}
}
item.setHighlightMode(newMode);
item.setHighlightColorToDefault();
} else {
// Mike: TODO comment on why the line below is used!!
// I forgot already!! Oops
boolean freeItem = FreeItems.getInstance().contains(item);
for (Item i : item.getAllConnected()) {
if (/* freeItem || */!FreeItems.getInstance().contains(i)) {
i.setHighlightMode(connectedNewMode);
i.setHighlightColorToDefault();
}
}
if (!freeItem && newMode != connectedNewMode) {
item.setHighlightMode(newMode);
item.setHighlightColorToDefault();
}
}
DisplayController.requestRefresh(true);
}
/*
*
* FrameRenderPass stuff. (TODO: Not sure if this is used for anything? In Apollo. cts16)
*
*/
/**
* Adds a FinalFrameRenderPass to the frame-render pipeline...
*
* Note that the last added FinalFrameRenderPass will be rendered at the
* very top.
*
* @param pass
* The pass to add. If already added then nothing results in the
* call.
*
* @throws NullPointerException
* If pass is null.
*/
public static void addFrameRenderPass(FrameRenderPass pass) {
if (pass == null) {
throw new NullPointerException("pass");
}
if (!_frameRenderPasses.contains(pass)) {
_frameRenderPasses.add(pass);
}
}
/**
* Adds a FinalFrameRenderPass to the frame-render pipeline...
*
* Note that the last added FinalFrameRenderPass will be rendered at the
* very top.
*
* @param pass
* The pass to remove
*
*/
public static void removeFrameRenderPass(FrameRenderPass pass) {
_frameRenderPasses.remove(pass);
}
/**
* A FinalFrameRenderPass is invoked at the very final stages for rendering
* a frame: that is, after the popups are drawn.
*
* There can be many applications for FinalFrameRenderPass. Such as tool
* tips, balloons, or drawing items at the highest Z-order in special
* situations.
*
* Although if there are multiples FinalFrameRenderPasses attach to the
* frame painter then it is not guaranteed to be rendered very last.
*
* @see FrameGraphics#addFinalFrameRenderPass(org.expeditee.gui.FrameGraphics.FrameRenderPass)
* @see FrameGraphics#removeFinalFrameRenderPass(org.expeditee.gui.FrameGraphics.FrameRenderPass)
*
* @author Brook Novak
*/
public interface FrameRenderPass {
/**
*
* @param currentClip
*
* @return The clip that the pass should use instead. i.e. if there are
* any effects that cannot invalidate prior to paint call.
*/
Clip paintStarted(Clip currentClip);
void paintFinalPass();
void paintPreLayeredPanePass();
}
}