/*
* Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de)
*
* 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 com.bc.ceres.swing.demo;
import com.bc.ceres.glayer.swing.AdjustableViewScrollPane;
import com.bc.ceres.swing.actions.CopyAction;
import com.bc.ceres.swing.actions.CutAction;
import com.bc.ceres.swing.actions.DeleteAction;
import com.bc.ceres.swing.actions.PasteAction;
import com.bc.ceres.swing.actions.RedoAction;
import com.bc.ceres.swing.actions.SelectAllAction;
import com.bc.ceres.swing.actions.UndoAction;
import com.bc.ceres.swing.figure.AbstractInteractorListener;
import com.bc.ceres.swing.figure.FigureCollection;
import com.bc.ceres.swing.figure.FigureFactory;
import com.bc.ceres.swing.figure.Interactor;
import com.bc.ceres.swing.figure.interactions.InsertEllipseFigureInteractor;
import com.bc.ceres.swing.figure.interactions.InsertLineFigureInteractor;
import com.bc.ceres.swing.figure.interactions.InsertPolygonFigureInteractor;
import com.bc.ceres.swing.figure.interactions.InsertPolylineFigureInteractor;
import com.bc.ceres.swing.figure.interactions.InsertRectangleFigureInteractor;
import com.bc.ceres.swing.figure.interactions.PanInteractor;
import com.bc.ceres.swing.figure.interactions.SelectionInteractor;
import com.bc.ceres.swing.figure.interactions.ZoomInteractor;
import com.bc.ceres.swing.figure.support.DefaultFigureCollection;
import com.bc.ceres.swing.figure.support.DefaultFigureStyle;
import com.bc.ceres.swing.figure.support.FigureEditorPanel;
import com.bc.ceres.swing.selection.SelectionChangeEvent;
import com.bc.ceres.swing.selection.SelectionChangeListener;
import com.bc.ceres.swing.selection.support.DefaultSelectionManager;
import com.bc.ceres.swing.undo.support.DefaultUndoContext;
import javax.swing.AbstractAction;
import javax.swing.AbstractButton;
import javax.swing.Action;
import javax.swing.ButtonGroup;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JOptionPane;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.datatransfer.FlavorEvent;
import java.awt.datatransfer.FlavorListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Locale;
import java.util.prefs.Preferences;
public abstract class FigureEditorApp {
private static final Interactor SELECTION_INTERACTOR = new SelectionInteractor();
private static final Interactor ZOOM_INTERACTOR = new ZoomInteractor();
private static final Interactor PAN_INTERACTOR = new PanInteractor();
private static final Interactor NEW_LINE_INTERACTOR = new InsertLineFigureInteractor();
private static final Interactor NEW_RECT_INTERACTOR = new InsertRectangleFigureInteractor();
private static final Interactor NEW_ELLI_INTERACTOR = new InsertEllipseFigureInteractor();
private static final Interactor NEW_POLYLINE_INTERACTOR = new InsertPolylineFigureInteractor();
private static final Interactor NEW_POLYGON_INTERACTOR = new InsertPolygonFigureInteractor();
private JFrame frame;
private UndoAction undoAction;
private RedoAction redoAction;
private DeleteAction deleteAction;
private SelectAllAction selectAllAction;
private CutAction cutAction;
private CopyAction copyAction;
private PasteAction pasteAction;
private FigureEditorPanel figureEditorPanel;
static {
Locale.setDefault(Locale.ENGLISH);
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception ignored) {
// ok
}
}
protected FigureEditorApp() {
}
protected abstract FigureFactory getFigureFactory();
protected abstract void loadFigureCollection(File file, FigureCollection figureCollection) throws IOException;
protected abstract void storeFigureCollection(FigureCollection figureCollection, File file) throws IOException;
private void init() {
DefaultSelectionManager selectionManager = new DefaultSelectionManager();
DefaultUndoContext undoContext = new DefaultUndoContext(this);
figureEditorPanel = new FigureEditorPanel(undoContext, new DefaultFigureCollection(), getFigureFactory());
selectionManager.setSelectionContext(figureEditorPanel.getFigureEditor().getSelectionContext());
undoAction = new UndoAction(undoContext) {
@Override
public void execute() {
super.execute();
redoAction.updateState();
}
};
redoAction = new RedoAction(undoContext) {
@Override
public void execute() {
super.execute();
undoAction.updateState();
}
};
cutAction = new CutAction(selectionManager);
copyAction = new CopyAction(selectionManager);
pasteAction = new PasteAction(selectionManager);
selectAllAction = new SelectAllAction(selectionManager);
deleteAction = new DeleteAction(selectionManager);
AbstractButton selectButton = createInteractorButton(figureEditorPanel, "S", SELECTION_INTERACTOR);
AbstractButton zoomButton = createInteractorButton(figureEditorPanel, "Z", ZOOM_INTERACTOR);
AbstractButton panButton = createInteractorButton(figureEditorPanel, "P", PAN_INTERACTOR);
AbstractButton newLineButton = createInteractorButton(figureEditorPanel, "L", NEW_LINE_INTERACTOR);
AbstractButton newRectButton = createInteractorButton(figureEditorPanel, "R", NEW_RECT_INTERACTOR);
AbstractButton newElliButton = createInteractorButton(figureEditorPanel, "E", NEW_ELLI_INTERACTOR);
AbstractButton newPLButton = createInteractorButton(figureEditorPanel, "PL", NEW_POLYLINE_INTERACTOR);
AbstractButton newPGButton = createInteractorButton(figureEditorPanel, "PG", NEW_POLYGON_INTERACTOR);
JToolBar toolBar = new JToolBar();
toolBar.add(selectButton);
toolBar.add(zoomButton);
toolBar.add(panButton);
toolBar.add(newLineButton);
toolBar.add(newRectButton);
toolBar.add(newElliButton);
toolBar.add(newPLButton);
toolBar.add(newPGButton);
ButtonGroup group = new ButtonGroup();
group.add(selectButton);
group.add(zoomButton);
group.add(panButton);
group.add(newLineButton);
group.add(newRectButton);
group.add(newElliButton);
group.add(newPLButton);
group.add(newPGButton);
figureEditorPanel.getFigureEditor().setInteractor(SELECTION_INTERACTOR);
figureEditorPanel.setPreferredSize(new Dimension(1024, 1024));
FigureCollection drawing = figureEditorPanel.getFigureEditor().getFigureCollection();
FigureFactory figureFactory = figureEditorPanel.getFigureEditor().getFigureFactory();
drawing.addFigure(figureFactory.createPolygonFigure(new Rectangle(20, 30, 200, 100), DefaultFigureStyle.createPolygonStyle(Color.BLUE, Color.GREEN)));
drawing.addFigure(figureFactory.createPolygonFigure(new Rectangle(90, 10, 100, 200), DefaultFigureStyle.createPolygonStyle(Color.MAGENTA, Color.ORANGE)));
Path2D linePath = rectPath(true, 110, 60, 70, 140);
drawing.addFigure(figureFactory.createLineFigure(linePath, DefaultFigureStyle.createLineStyle(Color.GRAY)));
linePath = new Path2D.Double();
linePath.moveTo(110, 60);
linePath.lineTo(110 + 70, 60);
linePath.lineTo(110 + 70, 60 + 140);
drawing.addFigure(figureFactory.createLineFigure(linePath, DefaultFigureStyle.createLineStyle(Color.BLACK)));
linePath = new Path2D.Double();
linePath.moveTo(200, 100);
linePath.lineTo(300, 200);
drawing.addFigure(figureFactory.createLineFigure(linePath, DefaultFigureStyle.createLineStyle(Color.MAGENTA, new BasicStroke(5.0f))));
drawing.addFigure(figureFactory.createPolygonFigure(new Ellipse2D.Double(50, 100, 80, 80), DefaultFigureStyle.createPolygonStyle(Color.YELLOW, Color.RED)));
drawing.addFigure(figureFactory.createPolygonFigure(new Ellipse2D.Double(220, 120, 150, 300), DefaultFigureStyle.createPolygonStyle(Color.GREEN, Color.BLUE)));
Area area = new Area(new Rectangle(0, 0, 100, 100));
area.subtract(new Area(new Rectangle(25, 25, 50, 50)));
area.add(new Area(new Rectangle(75, 75, 50, 50)));
area.subtract(new Area(new Rectangle(87, 87, 25, 25)));
area.subtract(new Area(new Rectangle(-26, -26, 50, 50)));
// drawing.addFigure(figureFactory.createPolygonalFigure(area, DefaultFigureStyle.createShapeStyle(Color.RED, Color.ORANGE)));
Path2D path = new Path2D.Double(Path2D.WIND_EVEN_ODD);
path.append(rectPath(true, 0, 0, 100, 100), false);
path.append(rectPath(false, 12, 12, 25, 25), false);
path.append(rectPath(false, 65, 65, 25, 25), false);
DefaultFigureStyle shapeStyle = DefaultFigureStyle.createPolygonStyle(new Color(0, 0, 255, 127), Color.ORANGE);
drawing.addFigure(figureFactory.createPolygonFigure(path, shapeStyle));
for (int i = 0; i < 50; i++) {
DefaultFigureStyle pointStyle = new DefaultFigureStyle();
pointStyle.setStrokeColor(new Color(0, 0, 64));
pointStyle.setStrokeOpacity(0.9);
pointStyle.setStrokeWidth(1.0);
int type = i % 4;
if (type == 0) {
pointStyle.setSymbolName("pin");
pointStyle.setFillColor(new Color(128, 128, 255));
pointStyle.setFillOpacity(0.7);
} else if (type == 1) {
pointStyle.setSymbolName("circle");
pointStyle.setFillColor(new Color(128, 128, 0));
pointStyle.setFillOpacity(0.7);
} else if (type == 2) {
pointStyle.setSymbolName("star");
} else {
pointStyle.setSymbolImagePath("/com/bc/ceres/swing/update/icons/list-add.png");
pointStyle.setSymbolRefX(8.0);
pointStyle.setSymbolRefY(8.0);
}
drawing.addFigure(figureFactory.createPointFigure(new Point2D.Double(i * 10, i * 10), pointStyle));
}
/*
Area a2 = new Area();
a2.add(new Area(new Rectangle(0, 0, 100, 100)));
a2.subtract(new Area(new Rectangle(12, 12, 25, 25)));
a2.subtract(new Area(new Rectangle(65, 65, 25, 25)));
a2.add(new Area(new Rectangle(200, 200, 100, 100)));
a2.subtract(new Area(new Rectangle(200 + 12, 200 + 12, 25, 25)));
a2.subtract(new Area(new Rectangle(200 + 65, 200 + 65, 25, 25)));
drawing.addFigure(figureFactory.createPolygonalFigure(a2, DefaultFigureStyle.createShapeStyle(new Color(255, 255, 0, 127), Color.ORANGE)));
*/
JMenuBar menuBar = new JMenuBar();
menuBar.add(createFileMenu());
menuBar.add(createEditMenu());
JComponent contentPane = new AdjustableViewScrollPane(figureEditorPanel);
frame = new JFrame(getClass().getSimpleName());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setJMenuBar(menuBar);
frame.getContentPane().add(toolBar, BorderLayout.NORTH);
frame.getContentPane().add(contentPane, BorderLayout.CENTER);
frame.setSize(400, 400);
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
e.printStackTrace();
String message = MessageFormat.format("" +
"An internal error occurred!\n" +
"Type: {0}\n" +
"Message: {1}", e.getClass(), e.getMessage());
JOptionPane.showMessageDialog(frame, message,
"Internal Error",
JOptionPane.ERROR_MESSAGE);
System.exit(1);
}
});
figureEditorPanel.getFigureEditor().getSelectionContext().addSelectionChangeListener(new SelectionChangeListener() {
@Override
public void selectionChanged(SelectionChangeEvent event) {
System.out.println("selection changed: " + event.getSelection());
}
@Override
public void selectionContextChanged(SelectionChangeEvent event) {
System.out.println("selection context changed: " + event.getSelection());
}
});
undoContext.addUndoableEditListener(new UndoableEditListener() {
@Override
public void undoableEditHappened(UndoableEditEvent event) {
System.out.println("edit happened: " + event.getEdit());
}
});
selectionManager.getClipboard().addFlavorListener(new FlavorListener() {
@Override
public void flavorsChanged(FlavorEvent event) {
System.out.println("flavors changed: " + event);
}
});
}
private static Path2D rectPath(boolean clockwise, int x, int y, int w, int h) {
Path2D.Double linePath = new Path2D.Double();
linePath.moveTo(x, y);
if (clockwise) {
linePath.lineTo(x, y + h);
linePath.lineTo(x + w, y + h);
linePath.lineTo(x + w, y);
} else {
linePath.lineTo(x + w, y);
linePath.lineTo(x + w, y + h);
linePath.lineTo(x, y + h);
}
linePath.lineTo(x, y);
linePath.closePath();
return linePath;
}
public static void run(FigureEditorApp drawingApp) {
drawingApp.init();
drawingApp.run();
}
public JFrame getFrame() {
return frame;
}
private void run() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
frame.setVisible(true);
}
});
}
private JMenu createFileMenu() {
JMenu menu = new JMenu("File");
menu.add(new OpenAction());
menu.add(new SaveAsAction());
menu.addSeparator();
menu.add(new ExitAction());
return menu;
}
private JMenu createEditMenu() {
JMenu menu = new JMenu("Edit");
menu.add(undoAction);
menu.add(redoAction);
menu.addSeparator();
menu.add(cutAction);
menu.add(copyAction);
menu.add(pasteAction);
menu.addSeparator();
menu.add(selectAllAction);
menu.addSeparator();
menu.add(deleteAction);
return menu;
}
private static AbstractButton createInteractorButton(final FigureEditorPanel figureEditorPanel, String name, final Interactor interactor) {
final AbstractButton selectButton = new JToggleButton(name);
selectButton.setSelected(false);
selectButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
boolean b = selectButton.isSelected();
System.out.println("b = " + b);
if (b) {
figureEditorPanel.getFigureEditor().setInteractor(interactor);
}
}
});
interactor.addListener(new AbstractInteractorListener() {
@Override
public void interactorActivated(Interactor interactor) {
selectButton.setSelected(true);
}
@Override
public void interactorDeactivated(Interactor interactor) {
selectButton.setSelected(false);
}
});
return selectButton;
}
private class OpenAction extends AbstractAction {
private OpenAction() {
putValue(Action.NAME, "Open...");
putValue(Action.ACTION_COMMAND_KEY, getClass().getName());
}
@Override
public void actionPerformed(ActionEvent event) {
File lastDir = new File(Preferences.userNodeForPackage(FigureEditorApp.class).get("lastDir", "."));
JFileChooser chooser = new JFileChooser(lastDir);
chooser.setAcceptAllFileFilterUsed(true);
int i = chooser.showOpenDialog(frame);
if (i == JFileChooser.APPROVE_OPTION && chooser.getSelectedFile() != null) {
Preferences.userNodeForPackage(FigureEditorApp.class).put("lastDir", chooser.getCurrentDirectory().getPath());
figureEditorPanel.getFigureEditor().getFigureSelection().removeAllFigures();
figureEditorPanel.getFigureEditor().getFigureCollection().removeAllFigures();
try {
loadFigureCollection(chooser.getSelectedFile(), figureEditorPanel.getFigureEditor().getFigureCollection());
} catch (IOException e) {
JOptionPane.showMessageDialog(getFrame(), "Error: " + e.getMessage());
}
}
}
}
private class SaveAsAction extends AbstractAction {
private SaveAsAction() {
putValue(Action.NAME, "Save As...");
putValue(Action.ACTION_COMMAND_KEY, getClass().getName());
}
@Override
public void actionPerformed(ActionEvent event) {
File lastDir = new File(Preferences.userNodeForPackage(FigureEditorApp.class).get("lastDir", "."));
JFileChooser chooser = new JFileChooser(lastDir);
chooser.setAcceptAllFileFilterUsed(true);
int i = chooser.showSaveDialog(frame);
if (i == JFileChooser.APPROVE_OPTION && chooser.getSelectedFile() != null) {
Preferences.userNodeForPackage(FigureEditorApp.class).put("lastDir", chooser.getCurrentDirectory().getPath());
try {
storeFigureCollection(figureEditorPanel.getFigureEditor().getFigureCollection(), chooser.getSelectedFile());
} catch (IOException e) {
JOptionPane.showMessageDialog(getFrame(), "Error: " + e.getMessage());
}
}
}
}
private class ExitAction extends AbstractAction {
private ExitAction() {
putValue(Action.NAME, "Exit");
putValue(Action.ACTION_COMMAND_KEY, getClass().getName());
}
@Override
public void actionPerformed(ActionEvent e) {
frame.dispose();
}
}
}