package open.dolphin.impl.scheam;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.event.*;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.Enumeration;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import open.dolphin.client.ClientContext;
import open.dolphin.client.SchemaEditor;
import open.dolphin.impl.scheam.holder.DrawingHolder;
import open.dolphin.impl.scheam.schemahelper.SchemaUtils;
import open.dolphin.impl.scheam.schemahelper.ShapeIconMaker;
import open.dolphin.infomodel.ExtRefModel;
import open.dolphin.infomodel.SchemaModel;
/**
* SchemaEditorImpl ここではコンポネントを作る
* SchemaEditorProperties: 描画のための変数を扱う
* StateMgr: ボタンで state を切り替える
* State: 実際の描画をする部分
* @author kazm, pns
*/
public class SchemaEditorImpl implements SchemaEditor {
public static final String TITLE = java.util.ResourceBundle.getBundle("open/dolphin/impl/scheam/resources/SchemaEditorImpl").getString("title.schemaEditor");
// ここに絵を描く(JComponent の子で paintComponent を Override している)
private SchemaCanvas canvas;
// 重なっている DrowingHolder を順番に保持する
private ArrayList<DrawingHolder> drawingList;
// schema の infomodel
private SchemaModel model;
// Matisse で作った画像編集フレーム(JFrame)
private SchemaCanvasView canvasView;
// Matisse で作ったツールパレット(JFrame)
private SchemaToolView toolView;
// KartePane との通信用
private PropertyChangeSupport boundSupport;
private SchemaEditorProperties properties;
private StateMgr stateMgr;
private UndoMgr undoMgr;
private boolean editable;
//ボタン
private final JButton[] cPaletteBtn = new JButton[12];
private JButton cancelBtn;
private JButton clearBtn;
private JButton colorBtn;
private JToggleButton eraserBtn;
private JToggleButton lineBtn;
private final JToggleButton[] lineWidthBtn = new JToggleButton[4];
private static final float[] lineWidthValue = { 1.5f, 2.5f, 3.5f, 4.5f }; // なぜか 3.0f でアンチエリアスがかからない
private JButton okBtn;
private JToggleButton ovalBtn;
private JToggleButton ovalFillBtn;
private JToggleButton pencilBtn;
private JToggleButton polyBtn;
private JToggleButton polyFillBtn;
private JToggleButton rectBtn;
private JToggleButton rectFillBtn;
private JComboBox roleCombo;
private JToggleButton selectBtn;
private JToggleButton textBtn;
private JTextField titleFld;
private JButton undoBtn;
private JButton redoBtn;
private JButton rotateRightBtn;
private JButton rotateLeftBtn;
private JButton expandBtn;
private JToggleButton clippingBtn;
private JToggleButton dotsSparseBtn;
private JToggleButton dotsMediumBtn;
private JToggleButton dotsDenseBtn;
private JToggleButton netSparseBtn;
private JToggleButton netMediumBtn;
private JToggleButton netDenseBtn;
private final ButtonGroup toolBg = new ButtonGroup();
private final ButtonGroup lineWidthBg = new ButtonGroup();
// アルファ値
private JSlider alphaSlider;
private JTextField alphaField;
// Line Width スライダー
private JSlider widthSlider;
private JTextField widthField;
// カーソル
//private Cursor defaultCursor;
private Cursor crossHairCursor;
private Cursor moveCursor;
private Cursor eraserCursor;
private Cursor textCursor;
public SchemaEditorImpl() {}
@Override
public void start() {
initComponents(editable);
}
// getter and setters
@Override
public void setSchema(SchemaModel model) {
this.model = model;
}
@Override
public void setEditable(boolean b) {
this.editable = b;
}
public SchemaCanvas getCanvas() {
return canvas;
}
public ArrayList<DrawingHolder> getDrawingList() {
return drawingList;
}
public SchemaEditorProperties getProperties() {
return properties;
}
public SchemaCanvasView getCanvasView() {
return canvasView;
}
public SchemaToolView getToolView() {
return toolView;
}
public StateMgr getStateMgr() {
return stateMgr;
}
public UndoMgr getUndoMgr() {
return undoMgr;
}
/**
* KartePane に返す BufferedImage を作る
* @return
*/
private BufferedImage createImage() {
BufferedImage baseImage = canvas.getBaseImage();
int width = baseImage.getWidth();
int height = baseImage.getHeight();
BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
Graphics2D g2 = result.createGraphics();
// まず全部白く塗る
Rectangle2D bounds = new Rectangle2D.Double(0, 0, width, height);
g2.setPaint(Color.WHITE);
g2.fill(bounds);
// これを入れないと,図形の縁がギザギザになる
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.drawImage(baseImage, null, 0, 0);
for (DrawingHolder d : drawingList) d.draw(g2);
return result;
}
/**
* KartePane に SchemaModel を返す
* 「カルテに展開」ボタン: createImage で作った BufferedImage を持ってくる
* 「破棄」ボタン : null を持ってくる
* @param image
*/
private void firePropertyChange(BufferedImage image) {
// カルテに展開
if (image != null) {
ImageIcon icon = new ImageIcon(image);
model.setIcon(icon);
String text = canvasView.getTitleFld().getText().trim();
if (text.equals("")) {
text = SchemaEditorProperties.DEFAULT_TITLE;
}
model.getExtRefModel().setTitle(text);
model.getExtRefModel().setMedicalRole((String) canvasView.getRoleCombo().getSelectedItem());
boundSupport.firePropertyChange("imageProp", null, model);
// キャンセル
} else {
boundSupport.firePropertyChange("imageProp", model, null);
}
}
/**
* このリスナは KartePane の propertyChanged を呼び出す
* @param l
*/
@Override
public void addPropertyChangeListener(PropertyChangeListener l) {
if (boundSupport == null) {
boundSupport = new PropertyChangeSupport(this);
}
boundSupport.addPropertyChangeListener(l);
}
@Override
public void removePropertyChangeListener(PropertyChangeListener l) {
if (boundSupport == null) {
boundSupport = new PropertyChangeSupport(this);
}
boundSupport.removePropertyChangeListener(l);
}
private void initComponents(boolean editable) {
properties = new SchemaEditorProperties();
drawingList = new ArrayList<>(5);
undoMgr = new UndoMgr(this);
// SchemaCanvas の設定
canvas = new SchemaCanvas(this);
canvas.setBorder(BorderFactory.createEmptyBorder());
// 持ってきた SchemaModel の Image を BufferedImage に変換してセット
BufferedImage srcImage = SchemaUtils.imageToBufferedImage(model.getIcon());
//s.oh^ 2013/03/29 シェーマで大きすぎる画像の対応(有効にするために以下のコメントを外す)
//if(srcImage.getWidth() > MAX_WIDTH || srcImage.getHeight() > MAX_HEIGHT) {
// srcImage = SchemaUtils.imageToBufferedImage(adjustImageSize(model.getIcon(), new Dimension(MAX_WIDTH, MAX_HEIGHT)));
//}
//s.oh$
canvas.setBaseImage(srcImage);
// canvas と tool の View (JFrame) を作る
canvasView = new SchemaCanvasView();
toolView = new SchemaToolView();
// お互いに連絡するために登録する
canvasView.setSchemaToolView(toolView);
toolView.setSchemaCanvasView(canvasView);
// canvasView に canvas を登録
JPanel canvasPanel = canvasView.getCanvasPanel();
canvasPanel.setBorder(BorderFactory.createEmptyBorder());
canvasPanel.setBackground(canvasView.getBackground());
canvasPanel.add(canvas);
stateMgr = new StateMgr(this);
// カーソル作成
// defaultCursor = new Cursor(Cursor.DEFAULT_CURSOR);
moveCursor = new Cursor(Cursor.MOVE_CURSOR);
textCursor = new Cursor(Cursor.TEXT_CURSOR);
crossHairCursor = new Cursor(Cursor.CROSSHAIR_CURSOR);
eraserCursor = ShapeIconMaker.createIconCursor(SchemaEditorProperties.ICON_ERASER);
// ボタン設定
// ツールボタン
pencilBtn = toolView.getPencilBtn();
eraserBtn = toolView.getEraserBtn();
ovalBtn = toolView.getOvalBtn();
ovalFillBtn = toolView.getOvalFillBtn();
rectBtn = toolView.getRectBtn();
rectFillBtn = toolView.getRectFillBtn();
polyBtn = toolView.getPolyBtn();
polyFillBtn = toolView.getPolyFillBtn();
textBtn = toolView.getTextBtn();
lineBtn = toolView.getLineBtn();
selectBtn = toolView.getSelectBtn();
clippingBtn = toolView.getClippingBtn();
netSparseBtn = toolView.getNetSparseBtn();
netMediumBtn = toolView.getNetMediumBtn();
netDenseBtn = toolView.getNetDenseBtn();
dotsSparseBtn = toolView.getDotsSparseBtn();
dotsMediumBtn = toolView.getDotsMediumBtn();
dotsDenseBtn = toolView.getDotsDenseBtn();
JToggleButton[] toolButtons = {
pencilBtn, eraserBtn, ovalBtn, ovalFillBtn, rectBtn, rectFillBtn, polyBtn, polyFillBtn, textBtn, lineBtn,
selectBtn, clippingBtn, dotsSparseBtn, dotsMediumBtn, dotsDenseBtn, netSparseBtn, netMediumBtn, netDenseBtn
};
for (JToggleButton b : toolButtons) {
toolBg.add(b);
b.setEnabled(editable);
}
// 線の太さボタン
for (int i=0; i<lineWidthValue.length; i++) {
lineWidthBtn[i] = toolView.getLineWidthBtn(i);
lineWidthBg.add(lineWidthBtn[i]);
lineWidthBtn[i].setEnabled(editable);
}
// カラーボタンと,カラーパレット
colorBtn = toolView.getColorBtn(); colorBtn.setEnabled(editable);
ImageIcon cPaletteIcon[] = new ImageIcon[12];
for (int i=0; i<12; i++) {
cPaletteBtn[i] = toolView.getCPaletteBtn(i);
cPaletteIcon[i] = ShapeIconMaker.createRectFillIcon(
cPaletteBtn[i].getForeground(), SchemaEditorProperties.CPALETTE_SIZE);
cPaletteBtn[i].setIcon(cPaletteIcon[i]);
cPaletteBtn[i].setEnabled(editable);
}
// その他のボタン
undoBtn = toolView.getUndoBtn(); undoBtn.setEnabled(false);
redoBtn = toolView.getRedoBtn(); redoBtn.setEnabled(false);
clearBtn = toolView.getClearBtn(); clearBtn.setEnabled(editable);
rotateRightBtn = toolView.getRotateRightBtn(); rotateLeftBtn = toolView.getRotateLeftBtn();
expandBtn = toolView.getExpandBtn();
cancelBtn = canvasView.getCancelBtn();
okBtn = canvasView.getOkBtn(); okBtn.setSelected(editable);
// アルファ値スライダー
alphaSlider = toolView.getAlphaSlider();
alphaField = toolView.getAlphaField();
alphaField.setEditable(false);
alphaSlider.addChangeListener((ChangeEvent e) -> {
float val = (float)alphaSlider.getValue() / 100;
properties.setAlpha(val);
alphaField.setText(String.format("%.2f", val));
});
toolView.getAlphaLabel().addMouseListener(new MouseAdapter(){
@Override
public void mouseClicked(MouseEvent e) {
properties.setAlpha(0.5f);
alphaSlider.setValue(50);
alphaField.setText("0.50");
}
});
// Line Width スライダー
widthSlider = toolView.getWidthSlider();
widthField = toolView.getWidthField();
widthSlider.addChangeListener((ChangeEvent e) -> {
float val = (float)widthSlider.getValue() / 10;
properties.setLineWidth(val);
setLineWidthGUI();
});
// ショートカット登録
ShortcutKey.register(canvasView, undoBtn, KeyEvent.VK_Z, InputEvent.META_DOWN_MASK, "undo");
ShortcutKey.register(canvasView, redoBtn, KeyEvent.VK_Z, InputEvent.META_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK, "redo");
ShortcutKey.register(canvasView, cancelBtn, KeyEvent.VK_ESCAPE, 0, "escape");
ShortcutKey.register(canvasView, okBtn, KeyEvent.VK_ENTER, 0, "enter");
ShortcutKey.register(canvasView, lineWidthBtn[0], KeyEvent.VK_1, 0, "line1");
ShortcutKey.register(canvasView, lineWidthBtn[1], KeyEvent.VK_2, 0, "line2");
ShortcutKey.register(canvasView, lineWidthBtn[2], KeyEvent.VK_3, 0, "line3");
ShortcutKey.register(canvasView, lineWidthBtn[3], KeyEvent.VK_4, 0, "line4");
ShortcutKey.register(canvasView, pencilBtn, KeyEvent.VK_B, 0, "pencil");
ShortcutKey.register(canvasView, clippingBtn, KeyEvent.VK_C, 0, "clipping");
ShortcutKey.register(canvasView, eraserBtn, KeyEvent.VK_E, 0, "eraser");
ShortcutKey.register(canvasView, textBtn, KeyEvent.VK_T, 0, "text");
ShortcutKey.register(canvasView, expandBtn, KeyEvent.VK_Z, 0, "expand");
ShortcutKey.register(canvasView, lineBtn, KeyEvent.VK_U, 0, "line");
ShortcutKey.register(canvasView, ovalFillBtn, KeyEvent.VK_O, 0, "oval");
ShortcutKey.register(canvasView, rectFillBtn, KeyEvent.VK_I, 0, "rect");
ShortcutKey.register(canvasView, polyFillBtn, KeyEvent.VK_P, 0, "poly");
ShortcutKey.register(canvasView, selectBtn, KeyEvent.VK_S, 0, "select");
ShortcutKey.register(canvasView, rotateRightBtn, KeyEvent.VK_R, 0, "rotateRight");
ShortcutKey.register(canvasView, rotateLeftBtn, KeyEvent.VK_R, InputEvent.SHIFT_DOWN_MASK, "rotateLeft");
// マウスリスナ StateMgr で切り替えた State を呼び出す
AtokAvoidableMouseListener atokAvoider = new AtokAvoidableMouseListener(stateMgr);
canvas.addMouseListener(atokAvoider);
canvas.addMouseMotionListener(atokAvoider);
// ボタンアクション登録 stateMgr.文字列() が呼ばれる
ButtonAction buttonAction = new ButtonAction(this);
// クリックした後,マウスで絵を描いたりするボタン
buttonAction.register(selectBtn, "startSelect", moveCursor);
buttonAction.register(lineBtn, "startLine", crossHairCursor);
buttonAction.register(rectBtn, "startRect", crossHairCursor);
buttonAction.register(ovalBtn, "startEllipse", crossHairCursor);
buttonAction.register(polyBtn, "startPolygon", crossHairCursor);
buttonAction.register(rectFillBtn, "startRectFill", crossHairCursor);
buttonAction.register(ovalFillBtn, "startEllipseFill", crossHairCursor);
buttonAction.register(polyFillBtn, "startPolygonFill", crossHairCursor);
buttonAction.register(textBtn, "startText", textCursor);
buttonAction.register(pencilBtn, "startPencil", crossHairCursor);
buttonAction.register(eraserBtn, "startEraser", eraserCursor);
buttonAction.register(clippingBtn, "startClipping", crossHairCursor);
buttonAction.register(netSparseBtn, "startNetSparse", crossHairCursor);
buttonAction.register(netMediumBtn, "startNetMedium", crossHairCursor);
buttonAction.register(netDenseBtn, "startNetDense", crossHairCursor);
buttonAction.register(dotsSparseBtn, "startDotsSparse", crossHairCursor);
buttonAction.register(dotsMediumBtn, "startDotsMedium", crossHairCursor);
buttonAction.register(dotsDenseBtn, "startDotsDense", crossHairCursor);
// クリックだけで作業完了するボタン(カーソル変更は不要)
buttonAction.register(undoBtn, "undo", null);
buttonAction.register(redoBtn, "redo", null);
buttonAction.register(clearBtn, "clear", null);
buttonAction.register(rotateRightBtn, "rotateRight", null);
buttonAction.register(rotateLeftBtn, "rotateLeft", null);
buttonAction.register(expandBtn, "expand", null);
// 線の太さにアクションを設定
for (int i=0; i<lineWidthValue.length; i++) {
final int final_i = i;
lineWidthBtn[i].addActionListener((ActionEvent e) -> {
float lw = lineWidthValue[final_i];
if ((e.getModifiers() & InputEvent.SHIFT_MASK) != 0) lw *= 2;
properties.setLineWidth(lw);
widthSlider.setValue((int)(lw*10));
widthField.setText(String.format("%.2f", lw));
});
}
// カラーパレットボタンにアクションを設定
for (int i=0; i<12; i++) {
final int final_i = i;
cPaletteBtn[i].addActionListener((ActionEvent e) -> {
Color c = cPaletteBtn[final_i].getForeground();
properties.setFillColor(c);
colorBtn.setIcon(ShapeIconMaker.createRectFillIcon(c, SchemaEditorProperties.SHAPEICON_SIZE));
});
}
// 選択された色を表示するボタン 押すと colorChooser が起動
colorBtn.addActionListener((ActionEvent e) -> {
chooseColor();
});
okBtn.setEnabled(editable);
okBtn.addActionListener((ActionEvent e) -> {
close();
firePropertyChange(createImage());
});
cancelBtn.addActionListener((ActionEvent e) -> {
close();
firePropertyChange(null);
});
titleFld = canvasView.getTitleFld();
titleFld.setText(SchemaEditorProperties.DEFAULT_TITLE);
titleFld.setEnabled(editable);
roleCombo = canvasView.getRoleCombo();
roleCombo.setSelectedItem(SchemaEditorProperties.DEFAULT_ROLE);
roleCombo.setEnabled(editable);
// これらのボタンがフォーカスを取ってしまうとショートカットが効かなくなる
okBtn.setFocusable(false);
cancelBtn.setFocusable(false);
roleCombo.setFocusable(false);
ExtRefModel extRef = model.getExtRefModel();
if (extRef != null) {
String text = extRef.getTitle();
if (text != null && (!text.equals(""))) {
titleFld.setText(text);
}
text = extRef.getMedicalRole();
if (text != null && (!text.equals(""))) {
roleCombo.setSelectedItem(text);
}
}
// プロパティーファイルの値によりボタンの初期値をセット
properties.load();
// 線の太さ
setLineWidthGUI();
// 色
colorBtn.setIcon(ShapeIconMaker.createRectFillIcon(properties.getFillColor(), SchemaEditorProperties.SHAPEICON_SIZE));
// ツールボタン
int btnNo = Math.min(properties.getSelectedTButtonNumber(), toolButtons.length-1);
toolButtons[btnNo].doClick();
// アルファスライダー
alphaSlider.setValue((int) (properties.getAlpha() * 100));
alphaField.setText(String.format("%.2f", properties.getAlpha()));
// baseImage から,view の必要 width, height を計算(自動ではうまくいかない)
properties.computeViewBounds(canvasView, toolView, srcImage);
if (editable) toolView.setVisible(true); // editable でない場合はツールパネルを出さない
canvasView.setVisible(true);
}
private void setLineWidthGUI() {
lineWidthBg.clearSelection();
float lw = properties.getLineWidth();
if (lw == 1.5f) lineWidthBtn[0].doClick();
else if (lw == 2.5f) lineWidthBtn[1].doClick();
else if (lw == 3.5f) lineWidthBtn[2].doClick();
else if (lw == 4.5f) lineWidthBtn[3].doClick();
widthSlider.setValue((int)(lw*10));
widthField.setText(String.format("%.2f", lw));
}
private void close() {
// 選択されているツールボタンの番号を調べる
int btnNo = 0;
for (Enumeration e = toolBg.getElements(); e.hasMoreElements();) {
if(((JToggleButton) e.nextElement()).isSelected()) break;
btnNo++;
}
properties.setSelectedTButtonNumber(btnNo);
properties.setSchemaViewRect(canvasView, toolView);
properties.save(); // プロパティーファイルに書き込み
canvasView.setVisible(false);
canvasView.dispose();
toolView.setVisible(false);
toolView.dispose();
}
/**
* カラー表示しているボタンを押したら ColorChooser を出す
*/
private void chooseColor() {
Color newColor = JColorChooser.showDialog(canvasView, ClientContext.getMyBundle(SchemaEditorImpl.class).getString("title.colorChooser"), properties.getFillColor());
if (newColor != null) {
properties.setFillColor(newColor);
ImageIcon icon = ShapeIconMaker.createRectFillIcon(properties.getFillColor(), SchemaEditorProperties.SHAPEICON_SIZE);
toolView.getColorBtn().setIcon(icon);
}
}
/**
* baseImage が変わった場合,SchemaCanvas を描画し直す
* @param baseImage
*/
public void recomputeViewBounds(BufferedImage baseImage) {
properties.recomputeViewBounds(canvasView, toolView, baseImage);
canvasView.repaint(); // 明示的に描いておかないと,正方形の画像だったら repaint されない
}
private ImageIcon adjustImageSize(ImageIcon icon, Dimension dim) {
if ((icon.getIconHeight() > dim.height) ||
(icon.getIconWidth() > dim.width)) {
Image img = icon.getImage();
float hRatio = (float) icon.getIconHeight() / dim.height;
float wRatio = (float) icon.getIconWidth() / dim.width;
int h,w;
if (hRatio > wRatio) {
h = dim.height;
w = (int) (icon.getIconWidth() / hRatio);
} else {
w = dim.width;
h = (int) (icon.getIconHeight() / wRatio);
}
img = img.getScaledInstance(w, h, Image.SCALE_SMOOTH);
return new ImageIcon(img);
} else {
return icon;
}
}
}