/** * 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(); } }