/**
*
*/
package cz.cuni.mff.peckam.java.origamist.gui.common;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.LayoutManager;
import java.awt.event.ActionEvent;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.ComboBoxModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JToolBar;
import javax.swing.SwingUtilities;
import javax.swing.origamist.BackgroundImageSupport;
import javax.swing.origamist.BackgroundImageSupport.BackgroundRepeat;
import javax.swing.origamist.JLocalizedLabel;
import javax.swing.origamist.JPanelWithOverlay;
import javax.swing.origamist.JToolBarWithBgImage;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;
import com.sun.j3d.exp.swing.JCanvas3D;
import cz.cuni.mff.peckam.java.origamist.exceptions.InvalidOperationException;
import cz.cuni.mff.peckam.java.origamist.exceptions.PaperStructureException;
import cz.cuni.mff.peckam.java.origamist.gui.viewer.DisplayMode;
import cz.cuni.mff.peckam.java.origamist.gui.viewer.StepRendererWithControls;
import cz.cuni.mff.peckam.java.origamist.model.Origami;
import cz.cuni.mff.peckam.java.origamist.model.Step;
import cz.cuni.mff.peckam.java.origamist.utils.LinkedListWithAdditionalBounds;
import cz.cuni.mff.peckam.java.origamist.utils.ListWithAdditionalBounds;
import cz.cuni.mff.peckam.java.origamist.utils.LocalizedString;
import cz.cuni.mff.peckam.java.origamist.utils.ParametrizedCallable;
import cz.cuni.mff.peckam.java.origamist.utils.ParametrizedLocalizedString;
/**
* A component for rendering a set of StepRenderers.
*
* @author Martin Pecka
*/
public class DiagramRenderer extends JPanelWithOverlay
{
private static final long serialVersionUID = -7158217935566060260L;
/** The origami which is this diagram from. */
protected Origami origami;
/** The first step to be rendered. */
protected Step firstStep;
/** The mode the renderer actually displays the origami in. */
protected DisplayMode mode;
/** The listener that will be fired after the locale of the GUI changed. */
protected PropertyChangeListener localeListener;
/** The actually displayed step renderers. */
protected final List<StepRendererWithControls> stepRenderers = new LinkedList<StepRendererWithControls>();
/** The pool of step renderers that can be reused. */
protected final List<StepRendererWithControls> stepRendererPool = new ArrayList<StepRendererWithControls>();
/** The pool of renderers for empty cells. */
protected final ListWithAdditionalBounds<JComponent, EmptyCellRenderer> emptyCellRendererPool = new LinkedListWithAdditionalBounds<JComponent, EmptyCellRenderer>();
protected final Set<JComponent> usedEmptyCellRenderers = new HashSet<JComponent>();
/** The basic zoom of all StepRenderers. */
protected double zoom = 100d;
/** The listener to be attached to {@link StepRendererWithControls} to listen when the fullscreen is requested. */
protected PropertyChangeListener stepFullscreenListener;
// COMPONENTS
/** The label to be displayed over the renderer while it is loading. */
protected final JLabel overlayLabel;
/** The panel holding all the StepRenderers. */
protected final JPanel diagramPane;
/** The toolbar for diagram manipulations. */
protected final JToolBarWithBgImage toolbar;
/** The toolbar of the currently displayed step if the display mode is DIAGRAM. */
protected JToolBar stepToolbar;
/** The step renderer that is the owner of the stepToolbar. */
protected StepRendererWithControls stepToolBarsRenderer;
/** The panel holding all toolbars this renderer should show. */
protected final JPanel toolbarPane;
/** The layout for diagramPane in page mode. */
protected LayoutManager pageModeLayout = null;
/** The layout for diagramPane in diagram mode. */
protected LayoutManager diagramModeLayout = null;
/** Previous button in PAGE mode. */
protected final JButton prevButtonPage;
/** Previous button in DIAGRAM mode. */
protected final JButton prevButtonDiagram;
/** Next button in PAGE mode. */
protected final JButton nextButtonPage;
/** Next button in DIAGRAM mode. */
protected final JButton nextButtonDiagram;
/** First button in PAGE mode. */
protected final JButton firstButtonPage;
/** First button in DIAGRAM mode. */
protected final JButton firstButtonDiagram;
/** Last button in PAGE mode. */
protected final JButton lastButtonPage;
/** Last button in DIAGRAM mode. */
protected final JButton lastButtonDiagram;
/** The quick-jump combobox for navigating through pages. */
protected final JComboBox pageSelect;
/** The model of pageSelect for PAGE mode. */
protected ComboBoxModel pageSelectPageModel = new DefaultComboBoxModel();
/** The model of pageSelect for DIAGRAM mode. */
protected ComboBoxModel pageSelectDiagramModel = new DefaultComboBoxModel();
/** The string saying "Step x of y" to be displayed in <code>pageSelect</code> */
protected ParametrizedLocalizedString stepXofY;
/** The string saying "Page x of y" to be displayed in <code>pageSelect</code> */
protected ParametrizedLocalizedString pageXofY;
/**
* @param o
* @param firstStep
*/
public DiagramRenderer(Origami o, Step firstStep)
{
this();
setOrigami(o, firstStep, false);
}
/**
*
*/
public DiagramRenderer()
{
overlayLabel = new JLocalizedLabel("viewer", "DiagramRenderer.loading");
overlayLabel.setFont(overlayLabel.getFont().deriveFont(Font.BOLD, 40));
overlayLabel.setOpaque(false);
getOverlay().setBackground(new Color(0, 0, 0, 64));
// don't allow the content to respond to key events while the overlay is displayed
getOverlay().addKeyListener(new KeyListener() {
@Override
public void keyTyped(KeyEvent e)
{
e.consume();
}
@Override
public void keyPressed(KeyEvent e)
{
e.consume();
}
@Override
public void keyReleased(KeyEvent e)
{
e.consume();
}
});
// don't allow the content to respond to mouse events while the overlay is displayed
getOverlay().addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e)
{
e.consume();
}
@Override
public void mousePressed(MouseEvent e)
{
e.consume();
}
@Override
public void mouseReleased(MouseEvent e)
{
e.consume();
}
@Override
public void mouseMoved(MouseEvent e)
{
e.consume();
}
});
diagramPane = new JPanel();
diagramModeLayout = new FormLayout("fill:default:grow", "fill:default:grow");
toolbar = new JToolBarWithBgImage("viewer");
toolbar.setFloatable(false);
toolbar.setBackground(new Color(231, 231, 184, 230));
toolbar.setBackgroundImage(new BackgroundImageSupport(getClass()
.getResource("/resources/images/tooltip-bg.png"), toolbar, 0, 0, BackgroundRepeat.REPEAT_X));
firstButtonPage = toolbar.createToolbarButton(new FirstAction(), "DiagramRenderer.first.page",
"first-page-24.png");
firstButtonDiagram = toolbar.createToolbarButton(new FirstAction(), "DiagramRenderer.first.diagram",
"first-page-24.png");
firstButtonDiagram.setVisible(false);
prevButtonPage = toolbar.createToolbarButton(new PrevAction(), "DiagramRenderer.prev.page", "prev-page-24.png");
prevButtonDiagram = toolbar.createToolbarButton(new PrevAction(), "DiagramRenderer.prev.diagram",
"prev-page-24.png");
prevButtonDiagram.setVisible(false);
pageSelect = new JComboBox();
pageSelect.setEditable(false);
pageSelect.setBackground(new Color(244, 244, 224));
pageSelect.setAction(new PageSelectAction());
nextButtonPage = toolbar.createToolbarButton(new NextAction(), "DiagramRenderer.next.page", "next-page-24.png");
nextButtonDiagram = toolbar.createToolbarButton(new NextAction(), "DiagramRenderer.next.diagram",
"next-page-24.png");
nextButtonDiagram.setVisible(false);
lastButtonPage = toolbar.createToolbarButton(new LastAction(), "DiagramRenderer.last.page", "last-page-24.png");
lastButtonDiagram = toolbar.createToolbarButton(new LastAction(), "DiagramRenderer.last.diagram",
"last-page-24.png");
lastButtonDiagram.setVisible(false);
toolbarPane = new JPanel();
toolbarPane.setBorder(BorderFactory.createEmptyBorder());
buildLayout();
PropertyChangeListener l = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt)
{
if (DisplayMode.DIAGRAM.equals(mode)) {
firstButtonDiagram.setEnabled(origami != null
&& origami.getModel().getSteps().getStep().indexOf(DiagramRenderer.this.firstStep) > 0);
prevButtonDiagram.setEnabled(firstButtonDiagram.isEnabled());
lastButtonDiagram
.setEnabled(origami != null
&& origami.getModel().getSteps().getStep().indexOf(DiagramRenderer.this.firstStep) < origami
.getModel().getSteps().getStep().size() - 1);
nextButtonDiagram.setEnabled(lastButtonDiagram.isEnabled());
} else {
if (origami != null) {
int numSteps = origami.getModel().getSteps().getStep().size();
int stepsPerPage = origami.getPaper().getCols() * origami.getPaper().getRows();
int numPages = (int) Math.ceil((double) numSteps / stepsPerPage);
int stepIndex = origami.getModel().getSteps().getStep().indexOf(DiagramRenderer.this.firstStep) + 1;
int pageIndex = (stepIndex - 1) / stepsPerPage + 1;
firstButtonPage.setEnabled(pageIndex > 1);
prevButtonPage.setEnabled(firstButtonPage.isEnabled());
lastButtonPage.setEnabled(pageIndex < numPages);
nextButtonPage.setEnabled(lastButtonPage.isEnabled());
} else {
firstButtonPage.setEnabled(false);
prevButtonPage.setEnabled(false);
lastButtonPage.setEnabled(false);
nextButtonPage.setEnabled(false);
}
}
}
};
addPropertyChangeListener("mode", l);
addPropertyChangeListener("firstStep", l);
stepFullscreenListener = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt)
{
if (evt.getSource() instanceof StepRenderer) {
setDisplayMode(DisplayMode.DIAGRAM);
setStep(((StepRenderer) evt.getSource()).getStep());
} else if (evt.getSource() instanceof StepRendererWithControls) {
setDisplayMode(DisplayMode.DIAGRAM);
setStep(((StepRendererWithControls) evt.getSource()).getStep());
}
}
};
setDisplayMode(DisplayMode.PAGE);
getContent().setOpaque(false);
}
/**
* Add the components to their containers.
*/
protected void buildLayout()
{
getOverlay().setLayout(
new FormLayout("left:pref:grow, center:pref, right:pref:grow",
"top:pref:grow, center:pref, bottom:pref:grow"));
getOverlay().add(overlayLabel, new CellConstraints(2, 2));
getContent().setLayout(new BorderLayout(0, 1));
getContent().add(diagramPane, BorderLayout.CENTER);
getContent().add(toolbarPane, BorderLayout.PAGE_END);
toolbar.add(firstButtonPage);
toolbar.add(firstButtonDiagram);
toolbar.add(prevButtonPage);
toolbar.add(prevButtonDiagram);
toolbar.add(pageSelect);
toolbar.add(nextButtonPage);
toolbar.add(nextButtonDiagram);
toolbar.add(lastButtonPage);
toolbar.add(lastButtonDiagram);
toolbarPane.setLayout(new FlowLayout(FlowLayout.LEADING, 5, 0));
toolbarPane.add(toolbar);
}
/**
* Instructs the renderer to display the given origami from now on.
*
* Automatically chooses the first step to be displayed.
*
* @param o The origami to display.
*/
public synchronized void setOrigami(Origami o)
{
setOrigami(o, o.getModel().getSteps().getStep().get(0));
}
/**
* Instructs the renderer to display the given origami from the given step.
*
* @param o The origami to display.
* @param firstStep The step to be displayed as the first one.
*/
public synchronized void setOrigami(Origami o, Step firstStep)
{
setOrigami(o, firstStep, true);
}
/**
* Instructs the renderer to display the given origami from the given step.
*
* @param o The origami to display.
* @param firstStep The step to be displayed as the first one.
* @param updateSteps Whether to update the displayed steps, or delay it for later.
*/
protected synchronized void setOrigami(Origami o, Step firstStep, final boolean updateSteps)
{
this.origami = o;
setStep(firstStep);
int numSteps = o.getModel().getSteps().getStep().size();
int numPages = o.getNumberOfPages();
int stepIndex = o.getModel().getSteps().getStep().indexOf(firstStep) + 1;
int pageIndex = o.getPage(firstStep);
Object[] steps = new Object[numSteps + 1];
stepXofY = new ParametrizedLocalizedString("viewer", "DiagramRenderer.stepXofY", stepIndex, numSteps);
steps[0] = stepXofY;
for (int i = 1; i <= numSteps; i++)
steps[i] = i;
Object[] pages = new Object[numPages + 1];
pageXofY = new ParametrizedLocalizedString("viewer", "DiagramRenderer.pageXofY", pageIndex, numPages);
pages[0] = pageXofY;
for (int i = 1; i <= numPages; i++)
pages[i] = i;
pageSelectDiagramModel = new DefaultComboBoxModel(steps);
pageSelectPageModel = new DefaultComboBoxModel(pages);
int gridWidth = o.getPaper().getCols();
StringBuilder colsBuilder = new StringBuilder();
int[][] colGroups = new int[1][gridWidth];
for (int i = 0; i < gridWidth; i++) {
if (i > 0)
colsBuilder.append(",");
colsBuilder.append("fill:default:grow");
colGroups[0][i] = i + 1;
}
int gridHeight = o.getPaper().getRows();
StringBuilder rowsBuilder = new StringBuilder();
int[][] rowGroups = new int[1][gridHeight];
for (int i = 0; i < gridHeight; i++) {
if (i > 0)
rowsBuilder.append(",");
rowsBuilder.append("fill:default:grow");
rowGroups[0][i] = i + 1;
}
FormLayout layout = new FormLayout(colsBuilder.toString(), rowsBuilder.toString());
layout.setColumnGroups(colGroups);
layout.setRowGroups(rowGroups);
pageModeLayout = layout;
int poolSize = o.getPaper().getCols() * o.getPaper().getRows();
if (stepRendererPool.size() > poolSize) {
stepRendererPool.subList(poolSize, stepRendererPool.size()).clear();
} // if the pool should get larger, we don't care here as the new pool entries will be created lazily
if (emptyCellRendererPool.size() > poolSize) {
emptyCellRendererPool.subList(poolSize, emptyCellRendererPool.size()).clear();
} // if the pool should get larger, we don't care here as the new pool entries will be created lazily
usedEmptyCellRenderers.clear();
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run()
{
if (mode == DisplayMode.DIAGRAM)
pageSelect.setModel(pageSelectDiagramModel);
else
pageSelect.setModel(pageSelectPageModel);
setBackground(origami.getPaper().getColor().getBackground());
for (StepRendererWithControls r : stepRendererPool) {
if (r != null)
r.setOrigami(origami);
}
for (EmptyCellRenderer comp : emptyCellRendererPool.getAltView())
comp.setOrigami(origami);
if (updateSteps)
updateDisplayedSteps();
}
});
}
/**
* Set the given step as the first (or the only) step to be displayed in this renderer.
*
* @param firstStep The first (or the only) step to be displayed in this renderer.
*/
public synchronized void setStep(Step firstStep)
{
Step oldStep = this.firstStep;
this.firstStep = firstStep;
firePropertyChange("firstStep", oldStep, firstStep);
if (stepXofY != null && pageXofY != null) {
int numSteps = origami.getModel().getSteps().getStep().size();
int stepsPerPage = origami.getPaper().getCols() * origami.getPaper().getRows();
int numPages = (int) Math.ceil((double) numSteps / stepsPerPage);
int stepIndex = origami.getModel().getSteps().getStep().indexOf(firstStep) + 1;
int pageIndex = (stepIndex - 1) / stepsPerPage + 1;
stepXofY.setParameters(stepIndex, numSteps);
pageXofY.setParameters(pageIndex, numPages);
}
}
/**
* Set the display mode to the one given. If the new mode is different from the actual one, a redraw will be forced.
*
* @param mode The new mode to switch the renderer to.
*/
public synchronized void setDisplayMode(final DisplayMode mode)
{
if (mode.equals(this.mode))
return;
DisplayMode oldMode = this.mode;
this.mode = mode;
firePropertyChange("mode", oldMode, mode);
switch (mode) {
case DIAGRAM:
pageSelect.setModel(pageSelectDiagramModel);
prevButtonDiagram.setVisible(true);
prevButtonPage.setVisible(false);
nextButtonDiagram.setVisible(true);
nextButtonPage.setVisible(false);
firstButtonDiagram.setVisible(true);
firstButtonPage.setVisible(false);
lastButtonDiagram.setVisible(true);
lastButtonPage.setVisible(false);
break;
case PAGE:
pageSelect.setModel(pageSelectPageModel);
if (origami != null) {
// when switching back to PAGE mode, don't automatically display the previously displayed step
// as the
// first step in the page, but make the page begin with a step that corresponds to the first
// step of
// this page
int stepsPerPage = origami.getPaper().getCols() * origami.getPaper().getRows();
int stepIndex = origami.getModel().getSteps().getStep().indexOf(firstStep) + 1;
int pageIndex = (stepIndex - 1) / stepsPerPage + 1;
int newStepIndex = (pageIndex - 1) * stepsPerPage;
Step step = origami.getModel().getSteps().getStep().get(newStepIndex);
if (step != null)
setStep(step);
}
prevButtonDiagram.setVisible(false);
prevButtonPage.setVisible(true);
nextButtonDiagram.setVisible(false);
nextButtonPage.setVisible(true);
firstButtonDiagram.setVisible(false);
firstButtonPage.setVisible(true);
lastButtonDiagram.setVisible(false);
lastButtonPage.setVisible(true);
break;
}
updateDisplayedSteps();
}
/**
* @return The mode the renderer actually displays the origami in.
*/
public DisplayMode getDisplayMode()
{
return mode;
}
/**
* This flag will be reset on each {@link #updateDisplayedSteps()} call and will be set to true by the first run
* error handler. This allows only one error message to be issued for multiple errors.
*/
protected boolean errorShown = false;
/**
* Updates the displayed steps (eg. manages the number of them and repaints).
*/
protected void updateDisplayedSteps()
{
errorShown = false;
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run()
{
showOverlay();
diagramPane.removeAll();
stepRenderers.clear();
usedEmptyCellRenderers.clear();
if (origami == null) {
hideOverlay();
return;
}
new Thread() {
@Override
public void run()
{
ParametrizedCallable<Void, Exception> errorHandler = new ParametrizedCallable<Void, Exception>() {
@Override
public Void call(Exception e)
{
if (errorShown)
return null;
if (e instanceof InvalidOperationException) {
errorShown = true;
final InvalidOperationException ioe = (InvalidOperationException) e;
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run()
{
JOptionPane.showMessageDialog(DiagramRenderer.this, ioe
.getUserFriendlyMessage(), new LocalizedString("application",
"invalid.operation.title").toString(), JOptionPane.ERROR_MESSAGE);
}
});
} else if (e instanceof PaperStructureException) {
errorShown = true;
JOptionPane.showMessageDialog(DiagramRenderer.this, e.getMessage(),
new LocalizedString("application", "invalid.operation.title").toString(),
JOptionPane.ERROR_MESSAGE);
}
return null;
}
};
final Thread _this = this;
if (mode == DisplayMode.DIAGRAM) {
final StepRendererWithControls r = getStepRenderer(0);
r.setDisplayMode(mode);
r.setStep(firstStep, null, errorHandler);
r.setZoom(zoom);
stepRenderers.add(r);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run()
{
if (diagramPane.getLayout() != diagramModeLayout)
diagramPane.setLayout(diagramModeLayout);
diagramPane.add(r, new CellConstraints(1, 1));
synchronized (_this) {
_this.notify();
}
}
});
} else {
int page = origami.getPage(firstStep);
final Integer[] stepsPlacement = origami.getStepsPlacement(page);
final int numSteps = stepsPlacement.length;
int gridSize = origami.getPaper().getCols() * origami.getPaper().getRows();
final JComponent[] panels = new JComponent[gridSize];
Arrays.fill(panels, null);
// this component will signalize that the cell it lies in is covered by a renderer, but it
// isn't its upper left corner
final JComponent nonEmptyCell = new JPanel();
final int gridWidth = origami.getPaper().getCols();
final int gridHeight = origami.getPaper().getRows();
Step step = firstStep;
for (int i = 0; i < numSteps; i++) {
StepRendererWithControls r = getStepRenderer(i);
r.setDisplayMode(getDisplayMode());
r.setStep(step, null, errorHandler);
r.setZoom(zoom);
int gridOrigin = stepsPlacement[i];
int gridX = gridOrigin % gridWidth;
int gridY = gridOrigin / gridWidth;
int width = step.getColspan() != null ? step.getColspan() : 1;
int height = step.getRowspan() != null ? step.getRowspan() : 1;
for (int j = gridX; j < gridX + width; j++) {
for (int k = gridY; k < gridY + height; k++) {
panels[j + gridWidth * k] = nonEmptyCell;
}
}
panels[gridOrigin] = r;
stepRenderers.add(r);
step = step.getNext();
}
for (int i = 0; i < panels.length; i++) {
if (panels[i] == null) {
panels[i] = getFreeEmptyCellRenderer();
}
}
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run()
{
if (diagramPane.getLayout() != pageModeLayout)
diagramPane.setLayout(pageModeLayout);
Step step = firstStep;
for (int i = 0; i < numSteps; i++) {
int gridOrigin = stepsPlacement[i];
int x = gridOrigin % gridWidth;
int y = gridOrigin / gridWidth;
int width = step.getColspan() != null ? step.getColspan() : 1;
int height = step.getRowspan() != null ? step.getRowspan() : 1;
diagramPane.add(panels[gridOrigin], new CellConstraints(x + 1, y + 1, width,
height));
step = step.getNext();
}
for (int i = 0; i < panels.length; i++) {
if (panels[i] instanceof EmptyCellRenderer) {
int x = i % gridWidth;
int y = i / gridHeight;
diagramPane.add(panels[i], new CellConstraints(x + 1, y + 1));
}
}
synchronized (_this) {
_this.notify();
}
}
});
}
synchronized (this) {
try {
this.wait(10000);
} catch (InterruptedException e) {}
}
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run()
{
if (stepToolbar != null && stepToolBarsRenderer != null)
stepToolBarsRenderer.getOverlay().add(stepToolbar, new CellConstraints(1, 1));
else if (stepToolbar != null)
toolbarPane.remove(stepToolbar);
if (mode.equals(DisplayMode.DIAGRAM) && stepRenderers.size() > 0) {
// in DIAGRAM view we want to add the step's toolbar to the toolbar of this renderer
stepToolbar = stepRenderers.get(0).getToolbar();
stepToolBarsRenderer = stepRenderers.get(0);
// also detaches the toolbar from the StepRendererWithControls
toolbarPane.add(stepToolbar);
stepToolbar.setVisible(true);
}
// this is important!
getContent().revalidate();
hideOverlay();
repaint();
new Thread(new Runnable() {
@Override
public void run()
{
stepsUpdated();
}
}).start();
}
});
}
}.start();
}
});
}
/**
* Called when steps are resized. Won't be called in EDT.
*/
protected void stepsUpdated()
{
}
/**
* Return the step renderer that will draw the step that will be displayed at the given index in this renderer's
* grid. The returned renderer will have correct origami already set.
*
* @param index The index in this renderer's grid.
* @return The step renderer.
*/
protected StepRendererWithControls getStepRenderer(int index)
{
// ensure the pool is accordingly large
while (index + 1 > stepRendererPool.size()) {
stepRendererPool.add(null);
}
if (stepRendererPool.get(index) == null) {
stepRendererPool.set(index, createStepRenderer());
}
return stepRendererPool.get(index);
}
/**
* @return A newly created and setup step renderer.
*/
protected StepRendererWithControls createStepRenderer()
{
final StepRendererWithControls r = new StepRendererWithControls();
r.setOrigami(origami);
r.addPropertyChangeListener("fullscreenModeRequested", stepFullscreenListener);
r.addPropertyChangeListener("zoom", new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt)
{
if (getDisplayMode() == DisplayMode.DIAGRAM) {
zoom = (Double) evt.getNewValue();
}
}
});
r.getRenderer().getCanvas().setResizeMode(JCanvas3D.RESIZE_DELAYED);
r.getRenderer().getCanvas().addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e)
{
revalidate();
repaint();
}
});
return r;
}
/**
* @return A renderer for empty cells, that isn't a child of a Swing container, so it can be added to a container.
*/
protected JComponent getFreeEmptyCellRenderer()
{
for (JComponent comp : emptyCellRendererPool) {
if (comp.getParent() == null && !usedEmptyCellRenderers.contains(comp)) {
usedEmptyCellRenderers.add(comp);
return comp;
}
}
JComponent newRenderer = createEmptyCellRenderer();
if (!(newRenderer instanceof EmptyCellRenderer))
throw new ClassCastException("Empty cell renderer must implement EmptyCellRenderer interface.");
emptyCellRendererPool.add(newRenderer);
usedEmptyCellRenderers.add(newRenderer);
return newRenderer;
}
/**
* @return Create a new renderer for empty cells.
*/
protected JComponent createEmptyCellRenderer()
{
DefaultEmptyCellRenderer result = new DefaultEmptyCellRenderer();
result.setOrigami(origami);
return result;
}
/**
* @return the zoom
*/
public double getZoom()
{
return zoom;
}
/**
* @param zoom the zoom to set
*/
public void setZoom(double zoom)
{
final double zoomDelta = zoom - this.zoom;
this.zoom = zoom;
for (StepRendererWithControls r : stepRenderers) {
r.setZoom(r.getZoom() + zoomDelta);
}
revalidate();
}
/**
* Increase zoom by 10%.
*/
public void incZoom()
{
setZoom(getZoom() + 10d);
}
/**
* Decrease zoom by 10%.
*/
public void decZoom()
{
setZoom(getZoom() - 10d);
}
/**
* Switch to the selected step/page (depends on the current display mode).
*
* @author Martin Pecka
*/
class PageSelectAction extends AbstractAction
{
/** */
private static final long serialVersionUID = -3468061316473710494L;
@Override
public void actionPerformed(ActionEvent e)
{
final Object selectedObject = pageSelect.getSelectedItem();
if (selectedObject instanceof Integer) {
final Integer index = (Integer) selectedObject;
final Step step;
if (getDisplayMode().equals(DisplayMode.DIAGRAM)) {
step = origami.getModel().getSteps().getStep().get(index - 1);
} else {
step = origami.getFirstStep(index);
}
if (step != null) {
setStep(step);
updateDisplayedSteps();
}
}
pageSelect.setSelectedIndex(0);
}
}
/**
* Sets the first step/page as the step/page to be displayed.
*
* @author Martin Pecka
*/
class FirstAction extends AbstractAction
{
/** */
private static final long serialVersionUID = -4456533603941034141L;
@Override
public void actionPerformed(ActionEvent e)
{
if (getDisplayMode().equals(DisplayMode.DIAGRAM) && !firstButtonDiagram.isEnabled())
return;
else if (getDisplayMode().equals(DisplayMode.PAGE) && !firstButtonPage.isEnabled())
return;
setStep(origami.getModel().getSteps().getStep().get(0));
updateDisplayedSteps();
}
}
/**
* Sets the last step/page as the step/page to be displayed.
*
* @author Martin Pecka
*/
class LastAction extends AbstractAction
{
/** */
private static final long serialVersionUID = -4456533603941034141L;
@Override
public void actionPerformed(ActionEvent e)
{
if (getDisplayMode().equals(DisplayMode.DIAGRAM)) {
if (!lastButtonDiagram.isEnabled())
return;
setStep(origami.getModel().getSteps().getStep().get(origami.getModel().getSteps().getStep().size() - 1));
} else {
if (!lastButtonPage.isEnabled())
return;
int numPages = origami.getNumberOfPages();
Step step = origami.getFirstStep(numPages);
setStep(step);
}
updateDisplayedSteps();
}
}
/**
* Sets the previous step/page as the step/page to be displayed.
*
* @author Martin Pecka
*/
class PrevAction extends AbstractAction
{
/** */
private static final long serialVersionUID = -7796794346643659044L;
@Override
public void actionPerformed(ActionEvent e)
{
if (getDisplayMode().equals(DisplayMode.DIAGRAM)) {
if (!prevButtonDiagram.isEnabled())
return;
int index = origami.getModel().getSteps().getStep().indexOf(firstStep);
if (index > 0) {
setStep(origami.getModel().getSteps().getStep().get(index - 1));
updateDisplayedSteps();
}
} else {
if (!prevButtonPage.isEnabled())
return;
int page = origami.getPage(firstStep);
page--;
if (page > 0) {
Step step = origami.getFirstStep(page);
setStep(step);
updateDisplayedSteps();
}
}
}
}
/**
* Sets the next step/page as the step/page to be displayed.
*
* @author Martin Pecka
*/
class NextAction extends AbstractAction
{
/** */
private static final long serialVersionUID = 6137420423152101455L;
@Override
public void actionPerformed(ActionEvent e)
{
if (getDisplayMode().equals(DisplayMode.DIAGRAM)) {
if (!nextButtonDiagram.isEnabled())
return;
int index = origami.getModel().getSteps().getStep().indexOf(firstStep);
if (index < origami.getModel().getSteps().getStep().size() - 1) {
setStep(origami.getModel().getSteps().getStep().get(index + 1));
updateDisplayedSteps();
}
} else {
if (!nextButtonPage.isEnabled())
return;
int page = origami.getPage(firstStep);
int numPages = origami.getNumberOfPages();
if (page < numPages) {
page++;
Step step = origami.getFirstStep(page);
setStep(step);
updateDisplayedSteps();
}
}
}
}
/**
* A renderer of empty cells.
*
* @author Martin Pecka
*/
public interface EmptyCellRenderer
{
/**
* Set the origami this renderer should render empty cell for.
*
* @param o
*/
void setOrigami(Origami o);
}
/**
* Empty cell renderer implemented by a JPanel.
*
* @author Martin Pecka
*/
protected class DefaultEmptyCellRenderer extends JPanel implements EmptyCellRenderer
{
/** */
private static final long serialVersionUID = -7852465401961605356L;
@Override
public void setOrigami(Origami o)
{
if (o != null)
setBackground(o.getPaper().getBackgroundColor());
}
}
}