/*
* funCKit - functional Circuit Kit
* Copyright (C) 2013 Lukas Elsner <open@mindrunner.de>
* Copyright (C) 2013 Peter Dahlberg <catdog2@tuxzone.org>
* Copyright (C) 2013 Julian Stier <mail@julian-stier.de>
* Copyright (C) 2013 Sebastian Vetter <mail@b4sti.eu>
* Copyright (C) 2013 Thomas Poxrucker <poxrucker_t@web.de>
* Copyright (C) 2013 Alexander Treml <alex.treml@directbox.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package de.sep2011.funckit.view;
import de.sep2011.funckit.controller.AbstractTool;
import de.sep2011.funckit.drawer.DecisionTable;
import de.sep2011.funckit.drawer.Drawer;
import de.sep2011.funckit.drawer.ElementState;
import de.sep2011.funckit.drawer.ElementStateResolver;
import de.sep2011.funckit.drawer.FancyDrawer;
import de.sep2011.funckit.drawer.Layout;
import de.sep2011.funckit.drawer.LayoutResolver;
import de.sep2011.funckit.drawer.SimpleDrawer;
import de.sep2011.funckit.drawer.SlimmedDrawer;
import de.sep2011.funckit.drawer.action.DrawAction;
import de.sep2011.funckit.model.graphmodel.Brick;
import de.sep2011.funckit.model.graphmodel.Circuit;
import de.sep2011.funckit.model.graphmodel.Component;
import de.sep2011.funckit.model.graphmodel.Element;
import de.sep2011.funckit.model.graphmodel.Gate;
import de.sep2011.funckit.model.graphmodel.Wire;
import de.sep2011.funckit.model.graphmodel.implementations.BrickWireDistinguishDispatcher;
import de.sep2011.funckit.model.sessionmodel.EditPanelModel;
import de.sep2011.funckit.model.sessionmodel.Project;
import de.sep2011.funckit.model.sessionmodel.SessionModel;
import de.sep2011.funckit.model.sessionmodel.Settings;
import de.sep2011.funckit.model.simulationmodel.Simulation;
import de.sep2011.funckit.observer.EditPanelModelInfo;
import de.sep2011.funckit.observer.EditPanelModelObserver;
import de.sep2011.funckit.observer.GraphModelInfo;
import de.sep2011.funckit.observer.GraphModelObserver;
import de.sep2011.funckit.observer.ProjectInfo;
import de.sep2011.funckit.observer.ProjectObserver;
import de.sep2011.funckit.observer.SessionModelInfo;
import de.sep2011.funckit.observer.SessionModelObserver;
import de.sep2011.funckit.observer.SettingsInfo;
import de.sep2011.funckit.observer.SettingsObserver;
import de.sep2011.funckit.observer.SimulationModelInfo;
import de.sep2011.funckit.observer.SimulationModelObserver;
import de.sep2011.funckit.util.DrawUtil;
import de.sep2011.funckit.util.Profiler;
import de.sep2011.funckit.validator.LiveCheckValidatorFactory;
import de.sep2011.funckit.validator.Result;
import javax.swing.JPanel;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.Transparency;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static de.sep2011.funckit.util.Log.gl;
import static de.sep2011.funckit.util.internationalization.Language.tr;
/**
* Panel for displaying and editing {@link Circuit}s and interacting with a
* {@link Simulation} of a {@link Circuit}.
*/
public class EditPanel extends JPanel implements EditPanelModelObserver,
GraphModelObserver, SimulationModelObserver, ProjectObserver,
SettingsObserver, SessionModelObserver {
private static final long serialVersionUID = -3211767963743871988L;
/**
* Stroke object to accept our selection with.
*/
private static final Stroke SELECTION_STROKE = new BasicStroke(1.0f, // Width
BasicStroke.CAP_SQUARE, // End cap
BasicStroke.JOIN_MITER, // Join style
100.0f, // Miter limit
new float[]{3.0f, 5.0f}, // Dash pattern
0.0f); // Dash phase
/**
* Name for image layer, grid gets drawn in.
*/
private static final String LAYER_GRID = "grid";
/**
* Name for layer, model (all elements) gets drawn in.
*/
private static final String LAYER_MODEL = "model";
/**
* Name for layer, ghost elements get drawn in.
*/
private static final String LAYER_GHOSTS = "ghosts";
/**
* Name for layer, selection feedback gets drawn in.
*/
private static final String LAYER_SELECTION = "selection";
/**
* Name for layer, tooltips are drawn in.
*/
private static final String LAYER_TOOLTIPS = "tooltips";
/**
* TODO comment new volatile image feature.
*/
private final Map<String, BufferedImage> imageLayers =
new LinkedHashMap<String, BufferedImage>();
/**
* List that defines the order of {@link EditPanel#imageLayers}. First layer
* in list is deepest layer, last layer the image on top.
*/
private List<String> imageLayerOrder;
/**
* Model object that is associated with this edit panel tab.
*/
private final EditPanelModel panelModel;
private boolean lowQualityMode;
/**
* Parent view object.
*/
private final View view;
private final Drawer drawer;
/* Drawer without using decision table and element state resolver. */
private final Drawer simpleDrawer;
private static final double ZOOM_BREAKPOINT_SIMPLE_DRAWER = 0.25;
private final Drawer slimmedDrawer;
private static final double ZOOM_BREAKPOINT_SLIMMED_DRAWER = 0.1;
private static final LayoutResolver LAYOUT_RESOLVER = new LayoutResolver();
private final ElementStateResolver elementStateResolver;
private final ContextMenu contextMenu;
private int layerWidth = 0;
private int layerHeight = 0;
/**
* Constructs a new edit panel with parent view object and the edit panel's
* associated panel model.
*
* @param view
* @param panelModel
*/
public EditPanel(View view, EditPanelModel panelModel) {
this.view = view;
this.panelModel = panelModel;
panelModel.addObserver(this);
panelModel.getCircuit().addObserver(this);
view.getSessionModel().getSettings().addObserver(this);
view.getSessionModel().addObserver(this);
elementStateResolver = new ElementStateResolver(panelModel,
view.getSessionModel());
drawer = new FancyDrawer(
view.getSessionModel().getSettings(),
LAYOUT_RESOLVER,
elementStateResolver
);
simpleDrawer = new SimpleDrawer(view.getSessionModel().getSettings());
slimmedDrawer = new SlimmedDrawer(view.getSessionModel().getSettings());
initializeImageLayerOrder();
initializeImageLayers();
this.setLayout(null);
addComponentListener(new EditPanelResizeListener());
setBackground(Color.WHITE);
setFocusable(true);
this.contextMenu = new ContextMenu(view, panelModel);
this.contextMenu.setInvoker(this);
this.setFocusable(true);
lowQualityMode = view.getSessionModel().getSettings()
.getBoolean(Settings.LOW_RENDERING_QUALITY_MODE);
setCursor(panelModel.getCursor());
}
@Override
@SuppressWarnings("deprecation")
public void reshape(int x, int y, int width, int height) {
super.reshape(x, y, width, height);
if (width != layerWidth || height != layerHeight) {
resizeLayers();
layerWidth = width;
layerHeight = height;
}
}
public void showContextMenu(int x, int y) {
this.contextMenu.show(this, x, y);
}
public void hideContextMenu() {
this.contextMenu.setVisible(false);
}
/**
* Initializes layers with their name. If we want to use further image
* layers, we just have to add them here.
*/
private void initializeImageLayerOrder() {
imageLayerOrder = new LinkedList<String>();
imageLayerOrder.add(LAYER_GRID);
imageLayerOrder.add(LAYER_MODEL);
imageLayerOrder.add(LAYER_TOOLTIPS);
imageLayerOrder.add(LAYER_GHOSTS);
imageLayerOrder.add(LAYER_SELECTION);
}
/**
* (Re-)Initializes image layer map with list of image layers to directly
* access a layer by given name.
*/
private void initializeImageLayers() {
long time = 0;
if (Profiler.ON) {
time = System.currentTimeMillis();
}
assert imageLayerOrder != null;
imageLayers.clear();
for (String key : imageLayerOrder) {
imageLayers.put(key, createLayer());
}
if (Profiler.ON) {
Profiler.rendering(Profiler.INITIALIZE_LAYERS,
System.currentTimeMillis() - time);
}
}
/**
* Creates a new layer to accept on. Important condition is, that layers
* support (semi-)transparent colors to accept several layers.
*
* @return
*/
private BufferedImage createLayer() {
GraphicsEnvironment ge = GraphicsEnvironment
.getLocalGraphicsEnvironment();
GraphicsDevice gs = ge.getDefaultScreenDevice();
GraphicsConfiguration gc = gs.getDefaultConfiguration();
// Create an image that supports transparent pixels
int width = Math.max(this.getWidth(), 1);
int height = Math.max(this.getHeight(), 1);
BufferedImage bufferedImage = gc.createCompatibleImage(width, height,
Transparency.TRANSLUCENT);
if (bufferedImage.getCapabilities(gc).isAccelerated()) {
gl().debug("YEAH! HW accelerated image!");
}
return bufferedImage;
}
/**
* Observing method for graph model. If graph model gets changed, this
* method is called and we can react on it.
*
* @param source the source {@link Circuit}
* @param i the Info Object
*/
@Override
public void graphModelChanged(Circuit source, GraphModelInfo i) {
if (panelModel != view.getSessionModel().getSelectedEditPanelModel()) {
return; // only draw if visible
}
if (!i.isElementBoundsChanged()
&& (i.getAddedBricks().size() > 0 || i.getAddedWires().size() > 0)
&& i.getChangedBricks().size() == 0
&& i.getChangedWires().size() == 0
&& i.getRemovedBricks().size() == 0
&& i.getRemovedWires().size() == 0) {
/* Only added elements, so just draw new elements. */
// drawModel();
Set<Element> elements = new LinkedHashSet<Element>();
elements.addAll(i.getAddedBricks());
elements.addAll(i.getAddedWires());
Graphics2D graphics = imageLayers.get(LAYER_MODEL).createGraphics();
// Graphics2D graphics =
// volatileImageLayers.get(LAYER_MODEL).createGraphics();
drawElements(graphics, elements);
} else {
drawModel();
}
updateRealTimeValidation(source);
repaint();
}
private void updateRealTimeValidation(Circuit c) {
if (view.getSessionModel().getSettings()
.getBoolean(Settings.REALTIME_VALIDATION)) {
LiveCheckValidatorFactory liveCheckValidatorFactory = new LiveCheckValidatorFactory();
List<Result> results = liveCheckValidatorFactory.getValidator()
.validate(c);
// NOTE: the notifying circuit has to be the main circuit of the
// current project.
view.getSessionModel().getCurrentProject().setCheckResults(results);
} else {
view.getSessionModel().getCurrentProject().setCheckResults(null);
}
}
/**
* Observing method for associated edit panel model. If it gets changed,
* this method is called and we can react on the changed state. For better
* performance it is reasonable to react on the type of changed data to
* repaint only parts of user interface.
*
* @param source the source Observable
* @param i the Info Object
*/
@Override
public void editPanelModelChanged(EditPanelModel source,
EditPanelModelInfo i) {
if (i.isCursorChanged()) {
setCursor(source.getCursor());
}
if (panelModel != view.getSessionModel().getSelectedEditPanelModel()) {
return; // only draw if visible
}
boolean drawGhosts = false;
boolean drawModel = false;
boolean drawGrid = false;
boolean drawSelection = false;
boolean drawToolTips = false;
if (i.isGhostsChanged()) {
drawGhosts = true;
}
if (i.isSelectionChanged()) {
Point selectionStart = source.getSelectionStart();
Point selectionEnd = source.getSelectionEnd();
if (selectionStart != null && selectionEnd != null) {
/*
* Selection start and end points are given, so accept
* selection.
*/
drawSelection(selectionStart, selectionEnd);
drawSelection = true;
} else {
/* Otherwise clear background and repaint model. */
clearBackground(imageLayers.get(LAYER_SELECTION)
.createGraphics());
// clearBackground(volatileImageLayers.get(LAYER_SELECTION).createGraphics());
drawModel = true;
}
}
if (i.isTransformChanged()) {
drawGrid = true;
drawModel = true;
drawGhosts = true;
source.setActiveBrick(null);
}
if (i.isActiveChanged()) {
drawModel = true;
}
/* Draw grid if it has been changed. */
if (drawGrid) {
drawGrid();
}
/* Draw tooltips. */
if (i.isActiveChanged()) {
clearBackground(imageLayers.get(LAYER_TOOLTIPS).createGraphics());
if (view.getSessionModel().getSettings()
.getBoolean(Settings.SHOW_TOOLTIPS)) {
if (source.getActiveBrick() != null) {
drawToolTip(source.getActiveBrick());
drawToolTips = true;
}
}
}
/* Draw elements from model if changed. */
if (drawModel) {
drawModel();
}
/* Possibly draw ghosts */
if (drawGhosts) {
drawGhosts();
}
/* If anything changed, repaint! */
if (drawGhosts || drawGrid || drawModel || drawSelection
|| drawToolTips) {
repaint();
}
}
/**
* Observer method for {@link Simulation}. If simulation changes, this method
* gets invoked and we can react on its changed data.
*
* @param source the source Observable
* @param i the Info Object
*/
@Override
public void simulationModelChanged(Simulation source, SimulationModelInfo i) {
if (panelModel != view.getSessionModel().getSelectedEditPanelModel()) {
return; // only draw if visible
}
if (i.isSimulationChanged()) {
drawModel();
repaint();
}
}
@Override
public void projectChanged(Project source, ProjectInfo i) {
if (i.isSimulationChanged()) {
if (source.hasSimulation()) {
// simulation started => observe it
source.getSimulation().addObserver(this);
} else {
// simulation stopped => repaint
drawModel();
repaint();
}
}
if (panelModel != view.getSessionModel().getSelectedEditPanelModel()) {
return; // only draw if visible
}
if (i.isActiveEditPanelModelChanged()) {
drawGrid();
drawModel();
drawGhosts();
repaint();
} else if (i.isResultsChanged()) {
drawModel();
repaint();
}
}
/**
* Method for event when something in settings has changed. Given source
* object is observed settings object, given SettingsInfo specifies which
* settings exactly have changed.
*
* @param source Settings object.
* @param i Info object for setting changes.
*/
@Override
public void settingsChanged(Settings source, SettingsInfo i) {
boolean isVisible = panelModel == view.getSessionModel()
.getSelectedEditPanelModel();
boolean drawGrid = false;
boolean drawModel = false;
boolean drawGhosts = false;
boolean drawToolTips = false;
/* Handle Valisation Change */
if (i.getChangedSetting().equals(Settings.REALTIME_VALIDATION)) {
drawGhosts = true;
updateRealTimeValidation(panelModel.getCircuit());
}
/* On toggling grid on or off, repaint it. */
if (i.getChangedSetting().equals(Settings.SHOW_GRID) && isVisible) {
drawGrid = true;
}
/* If rendering mode changed, repaint edit panel. */
if (i.getChangedSetting().equals(Settings.LOW_RENDERING_QUALITY_MODE)
&& isVisible) {
lowQualityMode = source
.getBoolean(Settings.LOW_RENDERING_QUALITY_MODE);
drawGrid = true;
drawModel = true;
drawGhosts = true;
drawToolTips = true;
}
/* If tooltips changed, reset them and paint them again. */
if (i.getChangedSetting().equals(Settings.SHOW_TOOLTIPS)) {
clearBackground(imageLayers.get(LAYER_TOOLTIPS).createGraphics());
drawToolTips = true;
if (view.getSessionModel().getSettings()
.getBoolean(Settings.SHOW_TOOLTIPS)) {
if (panelModel.getActiveBrick() != null) {
drawToolTip(panelModel.getActiveBrick());
}
}
}
/* Only draw ghost elements if they changed. */
if (drawGhosts) {
drawGhosts();
}
/* Only draw grid layer if it has changed due to zooming or similar. */
if (drawGrid) {
drawGrid();
}
/* Only repaint elements on model layer if they sth. has changed. */
if (drawModel) {
drawModel();
}
/* Only repaint if anything has changed. */
if (drawGhosts || drawGrid || drawModel || drawToolTips) {
repaint();
}
}
@Override
public void sessionModelChanged(SessionModel source, SessionModelInfo i) {
if (i.hasCurrentProjectChanged()
&& panelModel == source.getSelectedEditPanelModel()) {
drawGrid();
drawModel();
drawGhosts();
repaint();
}
if (i.isToolChanged()) {
drawSelection(new Point(), new Point()); // clear selection
}
}
/**
* Get the {@link EditPanelModel} of this {@link EditPanel}.
*
* @return the model
*/
public EditPanelModel getPanelModel() {
return panelModel;
}
private void resizeLayers() {
long time = 0;
if (Profiler.ON) {
time = System.currentTimeMillis();
}
initializeImageLayers();
if (panelModel != view.getSessionModel().getSelectedEditPanelModel()) {
return; // only draw if visible
}
drawGrid();
drawModel();
drawGhosts();
repaint();
if (Profiler.ON) {
Profiler.rendering(Profiler.RESIZE_LAYERS,
System.currentTimeMillis() - time);
}
}
/**
* Called when component should be repainted. Uses parent implementation and
* draws additionally all defined {@code imageLayers}. ImageLayers are used
* for redrawing only certain layers on certain update events.
*
* @param g
*/
@Override
protected void paintComponent(Graphics g) {
long time = 0;
if (Profiler.ON) {
time = System.currentTimeMillis();
}
Graphics2D graphics = (Graphics2D) g;
super.paintComponent(graphics);
render(graphics);
graphics.dispose();
if (Profiler.ON) {
Profiler.rendering(Profiler.PAINT_COMPONENT,
System.currentTimeMillis() - time);
}
}
private void render(Graphics2D graphics) {
for (String key : imageLayerOrder) {
graphics.drawImage(imageLayers.get(key), 0, 0, null);
}
}
/**
* Initiates (re-)drawing ghost layer.
*/
private void drawGhosts() {
long time = 0;
if (Profiler.ON) {
time = System.currentTimeMillis();
}
drawGhosts(imageLayers.get(LAYER_GHOSTS).createGraphics());
if (Profiler.ON) {
Profiler.rendering(Profiler.DRAW_GHOSTS, System.currentTimeMillis()
- time);
}
}
/**
* Draws ghosts from associated panel model on given Graphics2D object.
*
* @param graphics Graphics2D object from a layer.
*/
private void drawGhosts(Graphics2D graphics) {
clearBackground(graphics);
drawElements(graphics, panelModel.getGhosts());
graphics.dispose();
}
/**
* Initiates (re-)drawing grid.
*/
private void drawGrid() {
long time = 0;
if (Profiler.ON) {
time = System.currentTimeMillis();
}
drawGrid(imageLayers.get(LAYER_GRID).createGraphics());
if (Profiler.ON) {
Profiler.rendering(Profiler.DRAW_GRID, System.currentTimeMillis()
- time);
}
}
/**
* Draws a grid with grid information from panel model on given graphics
* object.
*
* @param graphics
*/
private void drawGrid(Graphics2D graphics) {
clearBackground(graphics);
setRenderingHints(graphics);
double zoomLevel = panelModel.getTransformation().getScaleX();
if (view.getSessionModel().getSettings().getBoolean(Settings.SHOW_GRID)
&& zoomLevel >= ZOOM_BREAKPOINT_SIMPLE_DRAWER) {
AffineTransform at = panelModel.getTransformation();
int width = getWidth();
int height = getHeight();
int gridSize = view.getSessionModel().getSettings()
.getInt(Settings.GRID_SIZE);
Color gridColor = view.getSessionModel().getSettings()
.get(Settings.GRID_COLOR, Color.class);
DrawUtil.drawGrid(graphics, gridSize, width, height, at, gridColor);
graphics.dispose();
}
}
/**
* Initiates redrawing model layer (that layer, that contains all
* elements).
*/
private void drawModel() {
long time = 0;
if (Profiler.ON) {
time = System.currentTimeMillis();
}
drawModel(imageLayers.get(LAYER_MODEL).createGraphics());
if (Profiler.ON) {
Profiler.rendering(Profiler.DRAW_MODEL, System.currentTimeMillis()
- time);
}
}
/**
* Draws elements from model on given graphics object.
*
* @param graphics Graphics2D object to accept on.
*/
private void drawModel(Graphics2D graphics) {
clearBackground(graphics);
drawElements(graphics, panelModel.getCircuit().getElements());
graphics.dispose();
}
/**
* Initiates drawing a selection feedback rectangle with given points.
*
* @param selectionStart First point from which selection starts.
* @param selectionEnd Second point on which selection ends.
*/
private void drawSelection(Point selectionStart, Point selectionEnd) {
long time = 0;
if (Profiler.ON) {
time = System.currentTimeMillis();
}
drawSelection(selectionStart, selectionEnd,
imageLayers.get(LAYER_SELECTION).createGraphics());
if (Profiler.ON) {
Profiler.rendering(Profiler.DRAW_SELECTION,
System.currentTimeMillis() - time);
}
}
/**
* Draws a selection rectangle on given Graphics2D object.
*
* @param selectionStart Start point of selection.
* @param selectionEnd End point of selection.
* @param graphics Graphics2D object (e.g. from {@link
* EditPanel#imageLayers}
*/
private void drawSelection(Point selectionStart, Point selectionEnd,
Graphics2D graphics) {
clearBackground(graphics);
graphics.transform(panelModel.getTransformation());
setRenderingHints(graphics);
Color borderColor = view.getSessionModel().getSettings()
.get(Settings.SELECTION_BORDER_COLOR, Color.class);
graphics.setColor(borderColor);
graphics.setStroke(SELECTION_STROKE);
int x = selectionStart.x > selectionEnd.x ? selectionEnd.x
: selectionStart.x;
int width = Math.abs(selectionStart.x - selectionEnd.x);
int height = Math.abs(selectionStart.y - selectionEnd.y);
int y = selectionStart.y > selectionEnd.y ? selectionEnd.y
: selectionStart.y;
graphics.drawRect(x, y, width, height);
/*
* Fill rectangle (WATCH OUT: multi layer images must support
* Transparency.TRANSLUCENT !)
*/
/*
* Color fillColor = new Color(SELECTION_COLOR.getRed(),
* SELECTION_COLOR.getGreen(), SELECTION_COLOR.getBlue(), 100);
*/
Color fillColor = view.getSessionModel().getSettings()
.get(Settings.SELECTION_FILL_COLOR, Color.class);
graphics.setColor(fillColor);
graphics.fillRect(x + 1, y + 1, width - 1, height - 1);
graphics.dispose();
}
/**
* Clears background of given Graphics2D object by filling its background
* white and with full transparency. For this to work, given Graphics2D
* object from layer (BufferedImage) must support transparent colors! {@link
* Transparency#TRANSLUCENT}
*
* @param graphics
*/
private void clearBackground(Graphics2D graphics) {
/*
* Define transparent background and clear whole image with background
* color, so that image is empty.
*/
graphics.setBackground(new Color(255, 255, 255, 0));
graphics.clearRect(0, 0, this.getWidth(), this.getHeight());
}
private class EditPanelResizeListener extends ComponentAdapter {
@Override
public void componentResized(ComponentEvent e) {
if (e.getSource() instanceof EditPanel) {
resizeLayers();
}
}
}
/**
* Draws given elements on given graphics object by looking up each element
* state, resolving via decision table its layout and then performing a
* drawing routine on it. This method may NOT clear background or existing
* drawings on graphics object. For clearing the graphics object first, use
* {@see EditPanel#clearBackground}.
*
* @param graphics
* @param elements
*/
private void drawElements(Graphics2D graphics, Set<Element> elements) {
graphics.transform(panelModel.getTransformation());
setRenderingHints(graphics);
Point start = AbstractTool.calculateInversePoint(new Point(0, 0),
panelModel.getTransformation());
Point end = AbstractTool.calculateInversePoint(new Point(getWidth(),
getHeight()), panelModel.getTransformation());
final Rectangle visualRange = new Rectangle(start.x, start.y, end.x
- start.x, end.y - start.y);
final double zoomLevel = panelModel.getTransformation().getScaleX();
/* Inject graphics object to used drawer. */
if (zoomLevel < ZOOM_BREAKPOINT_SLIMMED_DRAWER) {
slimmedDrawer.setGraphics(graphics);
} else if (zoomLevel < ZOOM_BREAKPOINT_SIMPLE_DRAWER) {
simpleDrawer.setGraphics(graphics);
} else {
drawer.setGraphics(graphics);
}
/* Draw Wires first, then Bricks but only in high quality mode */
if (lowQualityMode) {
for (Element element : elements) {
chooseDrawerAndDrawElement(element, zoomLevel, visualRange);
}
} else {
for (final Element element : elements) {
new BrickWireDistinguishDispatcher() {
{
element.dispatch(this);
}
@Override
protected void visitWire(Wire w) {
}
@Override
protected void visitBrick(Brick b) {
chooseDrawerAndDrawElement(element, zoomLevel,
visualRange);
}
};
}
for (final Element element : elements) {
new BrickWireDistinguishDispatcher() {
{
element.dispatch(this);
}
@Override
protected void visitWire(Wire w) {
chooseDrawerAndDrawElement(element, zoomLevel,
visualRange);
}
@Override
protected void visitBrick(Brick b) {
}
};
}
}
}
private void chooseDrawerAndDrawElement(Element element, double zoomLevel,
Rectangle visualRange) {
if (element.intersects(visualRange)) {
if (zoomLevel < ZOOM_BREAKPOINT_SLIMMED_DRAWER) {
/* Draw element slimmed. */
drawElement(element, slimmedDrawer);
} else if (zoomLevel < ZOOM_BREAKPOINT_SIMPLE_DRAWER) {
/* Simple drawer without fancy stuff. */
drawElement(element, simpleDrawer);
} else {
/*
* Complex drawing with decision table and layout / state
* resolver.
*/
drawElementFancy(element);
}
}
}
private static void drawElement(Element element, Drawer drawer) {
element.dispatch(drawer);
}
private void drawElementFancy(Element element) {
ElementState state = elementStateResolver.resolve(element);
Layout layout = new Layout();
LAYOUT_RESOLVER.setComponentStack(panelModel.getComponentStack());
LAYOUT_RESOLVER.setSimulation(view.getSessionModel()
.getCurrentSimulation());
LAYOUT_RESOLVER.setLayout(layout);
element.dispatch(LAYOUT_RESOLVER);
DrawAction action = DecisionTable.resolve(state);
drawer.setLayout(layout);
drawer.setAction(action);
element.dispatch(drawer);
}
/**
* Draws a tool tip for the given (active) brick by using its information as
* tool tip text and {@see DrawUtil#drawToolTip} to draw the actual tool
* tip.
*
* @param activeBrick
*/
private void drawToolTip(Brick activeBrick) {
long time = 0;
if (Profiler.ON) {
time = System.currentTimeMillis();
}
/* Construct tool tip text with a StringBuilder object. */
String name = activeBrick.getName();
int delay = activeBrick.getDelay();
Brick.Orientation orientation = activeBrick.getOrientation();
Point position = activeBrick.getPosition();
StringBuilder text = new StringBuilder();
text.append(tr("brick.name"));
text.append(": ");
text.append(name);
text.append("\n");
text.append(tr("brick.type"));
text.append(": ");
if (activeBrick instanceof Component) {
text.append(((Component) activeBrick).getType().getName());
} else {
text.append(activeBrick.getClass().getSimpleName());
}
if (activeBrick instanceof Gate || activeBrick instanceof Component) {
text.append("\n");
text.append(tr("brick.delay"));
text.append(": ");
text.append(delay);
}
text.append("\n");
text.append(tr("brick.position"));
text.append(": ");
text.append(position.x);
text.append(".");
text.append(position.y);
text.append("\n");
text.append(tr("brick.orientation"));
text.append(": ");
text.append(tr("orientation." + orientation));
List<Result> checkResults = view.getSessionModel().getCurrentProject().getCheckResults();
if (checkResults != null && !checkResults.isEmpty()) {
StringBuilder errorText = new StringBuilder();
boolean hasError = false;
for (Result r : view.getSessionModel().getCurrentProject()
.getCheckResults()) {
if (r.getFlawElements().contains(activeBrick)) {
hasError = true;
errorText.append("* ");
errorText.append(r.getMessage());
errorText.append("\n");
}
}
if (hasError) {
text.append("\n");
text.append(tr("brick.error"));
text.append(":\n");
text.append(errorText);
}
}
/* Calculate start position for tooltip. */
Point startPoint = new Point();
panelModel.getTransformation().transform(
new Point(activeBrick.getBoundingRect().x
+ activeBrick.getBoundingRect().width, activeBrick.getBoundingRect().y
+ (int) Math.round(0.5 * activeBrick.getBoundingRect().height)), startPoint);
/* Draw with util method actual fancy tool tip on tooltip layer. */
Font font = new Font("Helvetica", Font.PLAIN, 12); // TODO hardcoded
int shiftingX = 20;
int shiftingY = 20;
int width = 200;
int marginTop = 5;
int marginLeft = 4;
int marginRight = 4;
int marginBottom = 0;
Color tooltipBorderColor = new Color(150, 150, 220);
Color tooltipFillColor = new Color(200, 200, 255, 200);
Color tooltipTextColor = Color.black;
DrawUtil.drawToolTip(
imageLayers.get(LAYER_TOOLTIPS).createGraphics(),
startPoint,
width,
text.toString(),
tooltipBorderColor,
tooltipFillColor,
tooltipTextColor,
font,
shiftingX,
shiftingY,
marginTop,
marginLeft,
marginRight,
marginBottom
);
if (Profiler.ON) {
Profiler.rendering(Profiler.DRAW_TOOLTIP,
System.currentTimeMillis() - time);
}
}
/**
* Specifies rendering hints for a given graphics object depending on
* current performance settings.
*
* @param graphics
*/
private void setRenderingHints(Graphics2D graphics) {
if (lowQualityMode) {
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_OFF);
graphics.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION,
RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED);
graphics.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING,
RenderingHints.VALUE_COLOR_RENDER_SPEED);
graphics.setRenderingHint(RenderingHints.KEY_DITHERING,
RenderingHints.VALUE_DITHER_DISABLE);
graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
graphics.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_SPEED);
graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
} else {
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
}
}
}