package open.dolphin.impl.scheam;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import javax.swing.JButton;
import open.dolphin.impl.scheam.holder.DrawingHolder;
/**
*
* @author pns
*/
public class UndoMgr {
/** shape の描画・削除操作 */
public static final int DRAW = 1;
/** canvas の回転操作 */
public static final int ROTATE = 2;
/** DrawingHolder の移動操作 */
public static final int MOVE = 3;
/** DrawingHolder の拡大縮小 */
public static final int EXPAND = 4;
/** DrawingHolder の切り抜き */
public static final int CLIP = 5;
private final SchemaEditorImpl context;
private final Deque<Model> undoQueue;
private final Deque<Model> redoQueue;
public UndoMgr(SchemaEditorImpl context) {
this.context = context;
undoQueue = new LinkedList<>();
redoQueue = new LinkedList<>();
}
/**
* Draw 系の操作をした場合はここを呼ぶ
*/
public void storeDraw() {
Model model = new Model(DRAW);
undoQueue.offerLast(model);
// undo のあと新たに編集したら,それまでの redo は忘れる
// 忘れないようにコーディングするのは難しくてできなかった
redoQueue.clear();
controlBtn();
}
/**
* Rotate 系の操作をした場合はここを呼ぶ
* @param theta
*/
public void storeRotate(double theta) {
Model model = new Model(ROTATE);
model.setBaseImage();
model.setTheta(theta);
undoQueue.offerLast(model);
redoQueue.clear();
controlBtn();
}
/**
* Move 系の操作をした場合はここを呼ぶ
* @param holder
* @param x
* @param y
*/
public void storeMove(DrawingHolder holder, double x, double y) {
Model model = new Model(MOVE);
model.setHolder(holder);
model.setMove(x, y);
undoQueue.offerLast(model);
redoQueue.clear();
controlBtn();
}
/**
* 拡大縮小系の操作をした場合はここを呼ぶ
* @param ratio
*/
public void storeExpand(double ratio) {
Model model = new Model(EXPAND);
model.setBaseImage();
model.setScale(ratio);
undoQueue.offerLast(model);
redoQueue.clear();
controlBtn();
}
/**
* 切り抜きの操作をした場合はここを呼ぶ
* @param x
* @param y
*/
public void storeClipping(double x, double y) {
Model model = new Model(CLIP);
model.setBaseImage();
model.setMove(x, y);
undoQueue.offerLast(model);
redoQueue.clear();
controlBtn();
}
public void undo() {
if (! undoQueue.isEmpty()) restore(undoQueue, redoQueue);
controlBtn();
}
public void redo() {
if (! redoQueue.isEmpty()) restore(redoQueue, undoQueue);
controlBtn();
}
/**
* Undo/Redo ボタンの enable/disable 制御
*/
private void controlBtn() {
JButton undoButton = context.getToolView().getUndoBtn();
if (undoQueue.isEmpty()) undoButton.setEnabled(false);
else undoButton.setEnabled(true);
JButton redoButton = context.getToolView().getRedoBtn();
if (redoQueue.isEmpty()) redoButton.setEnabled(false);
else redoButton.setEnabled(true);
}
/**
* recover queue から model をとりだして復活させる
* present model は store queue に入れる
* @param recover
* @param store
*/
private void restore(Deque<Model> recover, Deque<Model> store) {
Model model = recover.pollLast();
Model present;
switch(model.getCode()) {
case DRAW:
present = new Model(DRAW);
store.offerLast(present);
restoreDraw(model);
break;
case ROTATE:
present = new Model(ROTATE);
// present には戻したのをまた戻す情報をセットするので,theta は符号を変える
present.setTheta( - model.getTheta());
present.setBaseImage();
store.offerLast(present);
restoreRotate(model);
break;
case MOVE:
present = new Model(MOVE);
present.setMove( - model.getMove().x, - model.getMove().y);
present.setHolder(model.getHolder());
store.offerLast(present);
restoreMove(model);
break;
case EXPAND:
present = new Model(EXPAND);
present.setScale( 1/model.getScale());
present.setBaseImage();
store.offerLast(present);
restoreExpand(model);
break;
case CLIP:
present = new Model(CLIP);
present.setMove( - model.getMove().x, - model.getMove().y);
present.setBaseImage();
store.offerLast(present);
restoreClipping(model);
break;
}
}
/**
* Draw 系の操作のリストア
* @param model
*/
private void restoreDraw(Model model) {
List<DrawingHolder> list = context.getDrawingList();
list.clear();
for (DrawingHolder h : model.getDrawingList()) list.add(h);
}
/**
* Rotate 系の操作のリストア
* @param model
*/
private void restoreRotate(Model model) {
List<DrawingHolder> list = context.getDrawingList();
// 戻すので theta は符号を変える
double theta = - model.getTheta();
BufferedImage baseImage = model.getBaseImage();
//DrawingHolder の回転
for (DrawingHolder h : list) {
if (theta > 0) {
// 原点中心に 90度右回転 → 右方向に幅の分移動
h.rotate(Math.PI/2);
h.translate(baseImage.getWidth(), 0);
} else {
// 原点中心に 90度左回転 → 下方向に高さ分移動
h.rotate(-Math.PI/2);
h.translate(0,baseImage.getHeight());
}
}
context.getCanvas().setBaseImage(baseImage);
context.recomputeViewBounds(baseImage);
}
/**
* Move 系の操作のリストア
* @param model
*/
private void restoreMove(Model model) {
List<DrawingHolder> list = context.getDrawingList();
int hash = model.getHolder().hashCode();
for (DrawingHolder h : list) {
// Holder を hashCode で比較して,一致したものを undo する
if (h.hashCode() == hash) {
h.translate( - model.getMove().x, - model.getMove().y);
break;
}
}
}
private void restoreExpand(Model model) {
List<DrawingHolder> list = context.getDrawingList();
BufferedImage baseImage = model.getBaseImage();
double ratio = 1/model.getScale();
for (DrawingHolder h : list) h.expand(ratio, ratio);
context.getCanvas().setBaseImage(baseImage);
context.recomputeViewBounds(baseImage);
}
private void restoreClipping(Model model) {
List<DrawingHolder> list = context.getDrawingList();
BufferedImage baseImage = model.getBaseImage();
for (DrawingHolder h : list) h.translate( -model.getMove().x, -model.getMove().y);
context.getCanvas().setBaseImage(baseImage);
context.recomputeViewBounds(baseImage);
}
/**
* undo 情報を入れておくための model
* drawingList だけは必ず取っておく。その他は必要に応じてセット
*/
private class Model {
private int code;
private final List<DrawingHolder> drawingList;
private BufferedImage baseImage;
private double theta;
private double x;
private double y;
private DrawingHolder holder;
private double scale;
public Model(int code) {
this.code = code;
drawingList = new ArrayList<>();
for (DrawingHolder h : context.getDrawingList()) drawingList.add(h);
}
public void setCode(int code) {
this.code = code;
}
public int getCode() {
return code;
}
public List<DrawingHolder> getDrawingList() {
return drawingList;
}
public void setBaseImage() {
BufferedImage image = context.getCanvas().getBaseImage();
baseImage = new BufferedImage(image.getWidth(), image.getHeight(), image.getType());
Graphics2D g = baseImage.createGraphics();
g.drawImage(image, null, 0, 0);
g.dispose();
}
public BufferedImage getBaseImage() {
return baseImage;
}
public void setTheta(double theta) {
this.theta = theta;
}
public double getTheta() {
return theta;
}
public void setMove(double x, double y) {
this.x = x; this.y = y;
}
public Point.Double getMove() {
return new Point.Double(x,y);
}
public void setHolder(DrawingHolder holder) {
this.holder = holder;
}
public DrawingHolder getHolder() {
return holder;
}
public void setScale(double scale) {
this.scale = scale;
}
public double getScale() {
return scale;
}
}
}