ACE−DRAWプロトタイプ2

戻る



 今回も前回にちょっとだけコードを書き加えただけなので第4回ではなく第3.5回です。変更点は

です。

 前回までのプログラムで定義だけして活用していなかった変数 fileClean をチェックするようにして、アニメデータが変更されているのに新しいファイルを開こうとしたりウインドウを閉じようとすると


のようなダイアログが出て保存を促します。いままではメインのフレーム(アニメのフレームではなくSwingのJFrame)のデフォルト終了処理に EXIT_ON_CLOSE を指定していましたが、今回からは DO_NOTHING_ON_CLOSE にしてウインドウリスナの windowClosing メソッドで終了処理をします。

 また、MSX版と同様に一番最後のフレームで「>」ボタンを押すと


のようなダイアログが出て新規フレームを作成できるようにしました。

以下、プログラムソースです。
// Java版AceDrawのためのステップ
// 「AceDraw プロト2」AceDrawProto2.java
// Copyright(c)2007 A.Hiramatsu

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.io.*;
import java.util.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.filechooser.*;

public class AceDrawProto2 {
  private static final int imgWidth = 320;
  private static final int imgByteWidth = imgWidth / 8;
  private static final int imgHeight = 240;
  private static final int animeFrmSize = 9728;
  private static final int animeFrmImgBytes = imgByteWidth * imgHeight;
  private static final int animeFrmHeadSize = 128;

  private static class AnimeFrame {
    boolean mark;
    byte[] img;

    private AnimeFrame() {
      this.mark = false;
      this.img = new byte[animeFrmImgBytes];
      for(int i=0; i<animeFrmImgBytes; i++) {
        this.img[i] = (byte)255;
      }
    }

    private void setMark(boolean b) {
      this.mark = b;
    }

    private boolean getMark() {
      return this.mark;
    }

    private void setImg(byte[] src) {
      int n = src.length;

      if(n > animeFrmImgBytes) n = animeFrmImgBytes;

      for(int i=0; i<n; i++) {
        this.img[i] = src[i];
      }
    }

    private byte[] getImg() {
      return this.img;
    }

    private byte[] cloneImg() {
      byte[] rImg = new byte[animeFrmImgBytes];

      for(int i=0; i<animeFrmImgBytes; i++) {
        rImg[i] = this.img[i];
      }
      return rImg;
    }
  };

  private ArrayList<AnimeFrame> aniFrms = new ArrayList<AnimeFrame>();

  private final int paletts[][] = {
    // CPN      CPn      CpN      Cpn      cPN      cPn      cpN      cpn         
    {0x000000,0x000000,0x000000,0x000000,0xffffff,0xffffff,0xffffff,0xffffff}, // cur
    {0xe00000,0xe00000,0x000000,0x000000,0xffb0b0,0xffb0b0,0xffffff,0xffffff}, // cur,prv
    {0x0000e0,0x000000,0x0000e0,0x000000,0xb0c0ff,0xffffff,0xb0c0ff,0xffffff}, // cur,nxt
    {0xd000d0,0xe00000,0x0000e0,0x000000,0xffb0ff,0xffb0b0,0xb0c0ff,0xffffff}  // all
  };

  private byte[] fileHeadBuf = new byte[animeFrmSize];
  private byte[] frameHeadBuf = new byte[animeFrmHeadSize];
  private byte[] frameImgBuf = new byte[animeFrmImgBytes];

  private byte[] drawingImg;
  private byte[] prvFrmImg;
  private byte[] nxtFrmImg;
  private int curFrmNo;

  private byte[] undoImg;
  private byte[] redoImg;

  private JLabel grpInfoLbl;
  private JToggleButton freeBtn;
  private JToggleButton snglBtn;
  private JToggleButton polyBtn;
  private JButton undoBtn;
  private JPopupMenu plMenu;
  private boolean plMenuVisible;

  private JFrame mainFrm;
  private JFileChooser fChoose = new JFileChooser(".");

  private JLabel frmInfoLbl;
  private JButton frmBMBtn;
  private JButton frmBBtn;
  private JButton frmFBtn;
  private JButton frmFMBtn;
  private JToggleButton frmPBtn;
  private JToggleButton frmNBtn;

  private JMenu fileMenu;
  private JMenu grpMenu;
  private JMenu frmMenu;
  private JMenu playMenu;

  private BufferedImage mainImg = new BufferedImage(imgWidth,imgHeight,
                                            BufferedImage.TYPE_INT_RGB);

  private boolean drawing = false;
  private boolean dragging = false;
  private boolean erasing = false;
  private boolean tempdraw = false;
  private int drawLatX,drawLatY;
  private int drawCurX,drawCurY;
  private enum DrawMode {none,free,sngl,poly};
  private DrawMode drawmode;
  private ArrayList<Point> plPoints; 

  private boolean canundo,canredo;

  private boolean fileClean;
  private String fileName = "";

  private JPanel grpPnl = new JPanel() {
    public void paintComponent(Graphics g) {
      super.paintComponent(g);
      g.drawImage(mainImg,0,0,this);
    }
  };

  private void updateView() {
    int bc,bp,bn;
    int pal,ix;
    int srcp = 0;

    pal = 0;
    if(frmPBtn.isSelected() && (curFrmNo > 0)) pal = pal + 1;
    if(frmNBtn.isSelected() && (curFrmNo < aniFrms.size()-1)) pal = pal + 2;

    for(int y=0; y<imgHeight; y++) {
      for(int x=0; x<imgByteWidth; x++) {
        bc = drawingImg[srcp];
        bp = prvFrmImg[srcp];
        bn = nxtFrmImg[srcp++];
        for(int i=0; i<8; i++) {
          ix = ((bc >> (7-i)) & 1)*4 + ((bp >> (7-i)) & 1)*2 + ((bn >> (7-i)) & 1);
          mainImg.setRGB(x*8+i,y,paletts[pal][ix]);
        }
      }
    }

    grpPnl.repaint();
  }

  private void updateFrmInfo() {
    char c = ' ';
    if(aniFrms.get(curFrmNo).getMark()) c = '*';
    frmInfoLbl.setText(c + Integer.toString(curFrmNo+1) +
                       " / "+Integer.toString(aniFrms.size()));
  }

  private byte[] newImg() {
    byte[] img = new byte[animeFrmImgBytes];
    for(int i=0; i < animeFrmImgBytes; i++) {
      img[i] = (byte)255;
    }
    return img;
  }

  private void updateFrm() {
    byte[] img =aniFrms.get(curFrmNo).getImg();

    for(int i=0; i < animeFrmImgBytes; i++) {
      if(drawingImg[i] != img[i]) {
        aniFrms.get(curFrmNo).setImg(drawingImg);
        fileClean = false;
        break;
      }
    }
    canundo = false;
    canredo = false;
  }

  private void enaMenu() {
    fileMenu.setEnabled(true);
    grpMenu.setEnabled(true);
    frmMenu.setEnabled(true);
    playMenu.setEnabled(true);
  }

  private void disMenu() {
    fileMenu.setEnabled(false);
    grpMenu.setEnabled(false);
    frmMenu.setEnabled(false);
    playMenu.setEnabled(false);
  }

  private void enaFrmFBBtns() {
    frmBMBtn.setEnabled(true);
    frmBBtn.setEnabled(true);
    frmFBtn.setEnabled(true);
    frmFMBtn.setEnabled(true);
  }

  private void disFrmFBBtns() {
    frmBMBtn.setEnabled(false);
    frmBBtn.setEnabled(false);
    frmFBtn.setEnabled(false);
    frmFMBtn.setEnabled(false);
  }

  private void enaDrawBtns() {
    freeBtn.setEnabled(true);
    snglBtn.setEnabled(true);
    polyBtn.setEnabled(true);
    undoBtn.setEnabled(true);
  }

  private void disDrawBtns() {
    freeBtn.setEnabled(false);
    snglBtn.setEnabled(false);
    polyBtn.setEnabled(false);
    undoBtn.setEnabled(false);
  }

  private void imgSetPixel(int x,int y,int op) {
    int ofs,bitptn;

    if((x>=0) && (x<imgWidth) &&
       (y>=0) && (y<imgHeight)) {

      ofs = x / 8 + y * imgByteWidth;
      bitptn = 0x80 >> (x & 0x07);

      switch(op) {
      case 0: drawingImg[ofs] &= (byte)(~bitptn); break;
      case 1: drawingImg[ofs] |= (byte)bitptn; break;
      case 2: drawingImg[ofs] ^= (byte)bitptn; break;
      default:
      }
    }
  }

  private void imgDrawLine(int x1,int y1,int x2,int y2,int op) {
    int w,h,dx,dy,accum;

    w = x2 - x1;
    dx = 1;
    if(w < 0) {
      w = -w;
      dx = -dx;
    }

    h = y2 - y1;
    dy = 1;
    if(h < 0) {
      h = -h;
      dy = -dy;
    }

    if((w > 0) || (h > 0)) {
      accum = 0;
      if(w > h) {
        do {
          imgSetPixel(x1,y1,op);
          x1 += dx;
          accum += h;
          if(accum >= w) {
            accum -= w;
            y1 += dy;
          }
        } while(x1 != x2);
      } else {
        do {
         imgSetPixel(x1,y1,op);
          y1 += dy;
          accum += w;
          if(accum >= h) {
            accum -= h;
            x1 += dx;
          }
        } while(y1 != y2);
      }
    }
  }

  private void imgDrawHLine(int x1,int x2,int y,int op) {
    int ofs,endofs,bp1,bp2;

    if((y >= 0) && (y < imgHeight)) {
      if(! ((x1 < 0) && (x2 < 0)) ||
           ((x1 >= imgWidth) && (x2 >= imgWidth)) ) {

        if(x1 > x2) {
          int t = x1;
          x1 = x2;
          x2 = t;
        }
        if(x1 < 0) x1 = 0;
        if(x2 >=imgWidth) x2 = imgWidth - 1;

        ofs = x1 / 8 + y * imgByteWidth;
        bp1 = (0x100 >> (x1 & 0x07)) - 1;
        endofs = x2 / 8 + y * imgByteWidth;
        bp2 = ~((0x80 >> (x2 & 0x07)) - 1);

        if(ofs == endofs) {
          switch(op) {
          case 0: drawingImg[ofs] &= (byte)(~(bp1 & bp2)); break;
          case 1: drawingImg[ofs] |= (byte)(bp1 & bp2); break;
          case 2: drawingImg[ofs] ^= (byte)(bp1 & bp2); break;
          default:
          }
        } else {
          switch(op) {
          case 0: drawingImg[ofs] &= (byte)(~bp1); break;
          case 1: drawingImg[ofs] |= (byte)bp1; break;
          case 2: drawingImg[ofs] ^= (byte)bp1; break;
          default:
          }

          for(int i=ofs+1; i<endofs; i++) {
            switch(op) {
            case 0: drawingImg[i] =(byte)0; break;
            case 1: drawingImg[i] =(byte)0xff; break;
            case 2: drawingImg[i] = (byte)(~drawingImg[i]); break;
            default:
            }
          }

          switch(op) {
          case 0: drawingImg[endofs] &= (byte)(~bp2); break;
          case 1: drawingImg[endofs] |= (byte)bp2; break;
          case 2: drawingImg[endofs] ^= (byte)bp2; break;
          default:
          }
        }
      }
    }
  }

  private void imgFillRect(int x1,int y1,int x2,int y2,int op) {
    if(! ((y1 < 0) && (y2 < 0)) ||
          ((y1 >= imgHeight) && (y2 >= imgHeight)) ) {
      if(y1 > y2) {
        int t = y1;
        y1 = y2;
        y2 = t;
      }
      if(y1 < 0) y1 = 0;
      if(y2 >= imgHeight) y2 = imgHeight - 1;

      for(int y=y1; y<=y2; y++) {
        imgDrawHLine(x1,x2,y,op);
      }
    }
  }

  private void setUndo() {
    for(int i=0; i<animeFrmImgBytes; i++) {		// 画面イメージを
       undoImg[i] = drawingImg[i];			// undoバッファに保存
    }
    canundo=true;					// 「undo可能だがまだ一度もundoしてない

」
    canredo=false;					// 状態にする
  }

  private void drawTempLine(int x,int y) {
    if((drawLatX != x) || (drawLatY != y)) {	// 長さが0でなければ
      imgDrawLine(drawLatX,drawLatY,x,y,2);	// 仮描画する
      drawCurX=x;				// 仮描画位置更新
      drawCurY=y;
      tempdraw=true;				// 仮描画済み
    }
  }

  private void cancelLine() {
    if(drawmode.equals(DrawMode.sngl) ||	// 仮描画するのは単直線かポリ直線のみ
       drawmode.equals(DrawMode.poly)) {

      if(tempdraw) {				// 仮描画線を引いていたら
        if((drawLatX != drawCurX) ||
           (drawLatY != drawCurY)) {

          imgDrawLine(drawLatX,drawLatY,	// 仮描画線を消す
                      drawCurX,drawCurY,2);
        }
      }
    }
    tempdraw=false;
  }

  MouseListener grpMLsn = new MouseAdapter() {		// マウスボタンイベント
    public void mousePressed(MouseEvent e) {
      int x = e.getX();
      int y = e.getY();

      if(e.getButton()==MouseEvent.BUTTON1) {		// 左ボタン押された
        if(!erasing) {					// 消しゴム使用中でなければ

          if(drawmode.equals(DrawMode.free)) {		// 自由曲線モード
            setUndo();
            drawing=true;
            dragging=false;
            drawLatX=x;					// 現在位置を最後の位置とする
            drawLatY=y;

          } else if(drawmode.equals(DrawMode.sngl)) {	// 単直線モード
            setUndo();
            drawing=true;
            drawLatX=x;					// 現在位置を最後の位置とする
            drawLatY=y;
            drawCurX=x;					// 仮描画用位置
            drawCurY=y;

          } else if(drawmode.equals(DrawMode.poly)) {	// ポリ直線モード
            if(drawing) {				// すでに描画中なら
              if((drawLatX != x) || (drawLatY != y)) {
                plPoints.add(new Point(x,y));		// 現在の点を追加
              }
            } else {
              plPoints.clear();
              plPoints.add(new Point(x,y));
              setUndo();
              disMenu();
              disFrmFBBtns();
              disDrawBtns();
            }
            drawing=true;				// 描画中にする
            drawLatX=x;					// 最後の位置を更新
            drawLatY=y;
            drawCurX=x;					// 仮描画用位置
            drawCurY=y;
          } else {
          }
        }

      } else if(e.getButton()==MouseEvent.BUTTON3) {	// 右ボタン押された
        if(plMenuVisible) {
          plMenu.setVisible(false);
          plMenuVisible = false;
        } else {
          if(drawing) {					// 描画中で
            if(drawmode.equals(DrawMode.poly)) {	// なおかつポリ直線モードなら
              plMenu.show(grpPnl,x,y);
              plMenuVisible = true;
            }
          } else {					// 描画中でなければ
            setUndo();
            imgDrawHLine(x-1,x+1,y-2,1);
            imgFillRect(x-2,y-1,x+2,y+1,1);
            imgDrawHLine(x-1,x+1,y+2,1);
            updateView();
            erasing = true;				// 消しゴム使用中にする
          }
        }
      }
    }

    public void mouseReleased(MouseEvent e) {
      int x = e.getX();
      int y = e.getY();

      if(e.getButton()==MouseEvent.BUTTON1) {		// 左ボタン離された

        if(drawmode.equals(DrawMode.free)) {		// 自由曲線モード
          if(drawing && !dragging) {
            imgSetPixel(drawLatX,drawLatY,0);		// ドラッグしてなければ1点だけ打つ
            updateView();
          }
          drawing=false;
          dragging=false;

        } else if(drawmode.equals(DrawMode.sngl)) {	// 単直線モード
          if(drawing) {
            cancelLine();				// 仮描画した線を消す
            if((drawLatX != x) || (drawLatY != y)) {	// 線の長さが0でなければ
              imgDrawLine(drawLatX,drawLatY,
                          drawCurX,drawCurY,0);		// 正式に線を引く
            }
            updateView();
          }
          drawing=false;
        } else {
        }

      } else if(e.getButton()==MouseEvent.BUTTON3) {	// 右ボタン離された
        erasing=false;					// 消しゴム使用中を解除
      } else {
      }
    }

    public void mouseExited(MouseEvent e) {		// マウスが描画領域を出た
      grpInfoLbl.setText("");
    }
  };

  MouseMotionListener grpMmLsn = new MouseMotionAdapter() {	// マウス移動イベント
    public void mouseDragged(MouseEvent e) {
      int x = e.getX();
      int y = e.getY();

      grpInfoLbl.setText( "(" + Integer.toString(x) +
                       "," + Integer.toString(y) + ")" );	// 座標表示

      if(drawing) {

        if(drawmode.equals(DrawMode.free)) {		// 自由曲線モード
          if((drawLatX != x) || (drawLatY != y)) {	// カーソルが動いていたら

            imgDrawLine(drawLatX,drawLatY,x,y,0);	// 線を引く
            drawLatX=x;					// 最後の位置を更新
            drawLatY=y;
            dragging=true;
            updateView();
          }
        } else if(drawmode.equals(DrawMode.sngl) ||	// 単直線モード または
                  (drawmode.equals(DrawMode.poly) &&	// ポリ直線モードで描画中なら
                                         drawing)) {
          cancelLine();					// 仮描画線を消す
          drawTempLine(x,y);				// 新しい仮描画線を書く
          updateView();
        } else {
        }

      } else if(erasing) {				// 消しゴム使用中
        imgDrawHLine(x-1,x+1,y-2,1);
        imgFillRect(x-2,y-1,x+2,y+1,1);
        imgDrawHLine(x-1,x+1,y+2,1);
        updateView();
      }
    }

    public void mouseMoved(MouseEvent e) {
      int x = e.getX();
      int y = e.getY();

      grpInfoLbl.setText( "(" + Integer.toString(x) +
                       "," + Integer.toString(y) + ")" );	// 座標表示

      if(drawmode.equals(DrawMode.poly)) {		// ポリ直線モードで
        if(drawing) {					// 描画中なら
          cancelLine();					// 仮描画線を消す
          drawTempLine(x,y);				// 新しい仮描画線を描く
          updateView();
        }
      }
    }
  };

  MouseListener plEndLsn = new MouseAdapter() {
    public void mousePressed(MouseEvent e) {
      int x1,y1,x2,y2;

      if(e.getButton()==MouseEvent.BUTTON1) {
        cancelLine();
        if(plPoints.size() > 2) {
          x1 = plPoints.get(0).x;
          y1 = plPoints.get(0).y;
          for(int i=1; i<plPoints.size(); i++) {
            x2 = plPoints.get(i).x;
            y2 = plPoints.get(i).y;
            imgDrawLine(x1,y1,x2,y2,0);
            x1 = x2;
            y1 = y2;
          }
          plPoints.clear();
        }
        updateView();
        drawing = false;
        enaMenu();
        enaFrmFBBtns();
        enaDrawBtns();
      }
    }
  };

  MouseListener plBackLsn = new MouseAdapter() {
    public void mousePressed(MouseEvent e) {
      int x1,y1,x2,y2;

      if(e.getButton()==MouseEvent.BUTTON1) {
        cancelLine();
        if(plPoints.size() > 1) {
          x1 = plPoints.get(plPoints.size()-2).x;
          y1 = plPoints.get(plPoints.size()-2).y;
          x2 = plPoints.get(plPoints.size()-1).x;
          y2 = plPoints.get(plPoints.size()-1).y;
          plPoints.remove(plPoints.size()-1);
          imgDrawLine(x1,y1,x2,y2,2);
          drawLatX = x1;
          drawLatY = y1;
          updateView();
        }
      }
    }
  };


  MouseListener undoBtnLsn = new MouseAdapter() {	// undoボタンイベント
    public void mousePressed(MouseEvent e) {
      cancelLine();

      if(canredo) {						// redo可(一度undoしている)
        if(canundo) {						// undo可なら
          for(int i=0; i<animeFrmImgBytes; i++) {		// undoバッファの内容を
            drawingImg[i] = undoImg[i];				// 画面に復帰
          }
        } else {
          for(int i=0; i<animeFrmImgBytes; i++) {		// redoバッファの内容を
            drawingImg[i] = redoImg[i];				// 画面に復帰
          }
        }
        canundo = !canundo;					// undo可をトグル
      } else {
        if(canundo) {						// 描画後最初のundo
          for(int i=0; i<animeFrmImgBytes; i++) {		// 現在の画面イメージを
            redoImg[i] = drawingImg[i];				// redoバッファに保存
            drawingImg[i] = undoImg[i];				// undoバッファの内容を
          }							// 画面に復帰
          canundo=false;
          canredo=true;						// redo可にする
        }
      }
      updateView();
      drawing=false;
      dragging=false;
      erasing=false;
    } 
  };

  MouseListener freeBtnLsn = new MouseAdapter() {	// freeボタン
    public void mousePressed(MouseEvent e) {
      drawmode=DrawMode.free;
      drawing=false;
      dragging=false;
      erasing=false;
    } 
  };

  MouseListener snglBtnLsn = new MouseAdapter() {	// 単直線ボタン
    public void mousePressed(MouseEvent e) {
      drawmode=DrawMode.sngl;
      drawing=false;
      dragging=false;
      erasing=false;
    } 
  };

  MouseListener polyBtnLsn = new MouseAdapter() {	// ポリ直線ボタン
    public void mousePressed(MouseEvent e) {
      drawmode=DrawMode.poly;
      drawing=false;
      dragging=false;
      erasing=false;
    } 
  };


  MouseListener frmFBtnLsn = new MouseAdapter() {
    public void mousePressed(MouseEvent e) {
      if(curFrmNo < aniFrms.size()-1) {
        updateFrm();
        curFrmNo++;
        prvFrmImg = drawingImg;
        drawingImg = aniFrms.get(curFrmNo).cloneImg();
        if(curFrmNo < aniFrms.size()-1) {
          nxtFrmImg = aniFrms.get(curFrmNo+1).getImg();
        }
        updateView();
        updateFrmInfo();
      } else {
        int r = JOptionPane.showConfirmDialog(mainFrm,"新規フレームを作成しますか?",
                                              "AceDraw",JOptionPane.YES_NO_OPTION);
        if(r == JOptionPane.YES_OPTION) {
          updateFrm();
          addNewFrm();
        }
      }
    }
  };

  MouseListener frmBBtnLsn = new MouseAdapter() {
    public void mousePressed(MouseEvent e) {
      if(curFrmNo > 0) {
        updateFrm();
        curFrmNo--;
        nxtFrmImg = drawingImg;
        drawingImg = aniFrms.get(curFrmNo).cloneImg();
        if(curFrmNo > 0) {
          prvFrmImg = aniFrms.get(curFrmNo-1).getImg();
        }
        updateView();
        updateFrmInfo();
      }
    }
  };

  MouseListener frmFMBtnLsn = new MouseAdapter() {
    public void mousePressed(MouseEvent e) {
      int newcur = curFrmNo;

      if((curFrmNo < aniFrms.size()-1) && (aniFrms.get(curFrmNo).getMark())) {
        newcur++;
      }

      while(newcur < aniFrms.size()-1) {
        if(! aniFrms.get(newcur).getMark()) newcur++;
        else                                break;
      }

      if(curFrmNo != newcur) {
        updateFrm();
        curFrmNo = newcur;
        drawingImg = aniFrms.get(curFrmNo).getImg();
        if(curFrmNo > 0) {
          prvFrmImg = aniFrms.get(curFrmNo-1).getImg();
        }
        if(curFrmNo < aniFrms.size()-1) {
          nxtFrmImg = aniFrms.get(curFrmNo+1).getImg();
        }
        updateView();
        updateFrmInfo();
      }
    }
  };

  MouseListener frmBMBtnLsn = new MouseAdapter() {
    public void mousePressed(MouseEvent e) {
      int newcur = curFrmNo;

      if((curFrmNo > 0) && (aniFrms.get(curFrmNo).getMark())) {
        newcur--;
      }

      while(newcur > 0) {
        if(! aniFrms.get(newcur).getMark()) newcur--;
        else                                break;
      }

      if(curFrmNo != newcur) {
        updateFrm();
        curFrmNo = newcur;
        drawingImg = aniFrms.get(curFrmNo).getImg();
        if(curFrmNo > 0) {
          prvFrmImg = aniFrms.get(curFrmNo-1).getImg();
        }
        if(curFrmNo < aniFrms.size()-1) {
          nxtFrmImg = aniFrms.get(curFrmNo+1).getImg();
        }
        updateView();
        updateFrmInfo();
        canundo = false;
        canredo = false;
      }
    }
  };

  ItemListener frmPBtnLsn = new ItemListener() {
    public void itemStateChanged(ItemEvent e) {
      updateView();
    }
  };

  ItemListener frmNBtnLsn = new ItemListener() {
    public void itemStateChanged(ItemEvent e) {
      updateView();
    }
  };


  private boolean checkClean() {
    if(! fileClean) {
      int r = JOptionPane.showConfirmDialog(mainFrm,"アニメデータが変更されています。保存しますか?",
                                            "AceDraw",JOptionPane.YES_NO_CANCEL_OPTION);
      if(r == JOptionPane.YES_OPTION) {
        if(! doFileSave()) r = JOptionPane.CANCEL_OPTION;
      }
      if(r != JOptionPane.CANCEL_OPTION) {
        fileClean = true;
        return true;
      } else {
        return false;
      }
    } else {
      return true;
    }
  }

  private void doFileNew() {
    if(checkClean()) {
      aniFrms.clear();
      aniFrms.add(new AnimeFrame());
      curFrmNo = 0;
      fileName = "";

      drawingImg = aniFrms.get(curFrmNo).cloneImg();
      updateView();
      updateFrmInfo();
    }
  }

  private void doFileOpen() {
    File f;
    int n,p;
    int rlen = 0;
    int totalFrms = 0;
    long fsize;
    String s;
    boolean canRead = false;
    FileInputStream fi = null;
    AnimeFrame aniFrm;

    if(checkClean()) {
      n = fChoose.showOpenDialog(mainFrm);
      if(n==JFileChooser.APPROVE_OPTION ) {
        f = fChoose.getSelectedFile();
        if(f != null) {
          s = fChoose.getName(f);
          canRead = true;

          if(s.length()>4) {
            if(s.substring(s.length()-4).toUpperCase().equals(".ACD")) {
              try {
                fi = new FileInputStream(f);
              } catch(Throwable fe) {
                JOptionPane.showMessageDialog(mainFrm,"ファイルのオープンに失敗しました",
                                              "AceDraw",JOptionPane.ERROR_MESSAGE);
                canRead = false;
              }

              if(canRead) {
                try {
                  rlen = fi.read(fileHeadBuf);
                } catch(Throwable fe) {
                  JOptionPane.showMessageDialog(mainFrm,"ファイルの入力中にエラーが発生しました",
                                                "AceDraw",JOptionPane.ERROR_MESSAGE);
                  canRead = false;
                }
              }

              if(canRead) {
                fsize = f.length();

                totalFrms = ((int)fileHeadBuf[8] & 0xff) + 
                            (((int)fileHeadBuf[9] & 0xff) << 8) +
                            (((int)fileHeadBuf[10] & 0xff) << 16);

                curFrmNo = 0;
                aniFrms.clear();

                if((rlen < animeFrmSize) ||
                   (fileHeadBuf[0] != 0x41) || (fileHeadBuf[1] != 0x43) || 
                   (fileHeadBuf[2] != 0x44) || (fileHeadBuf[3] != 0x00) ||
                   (totalFrms < 1) || (fileHeadBuf[11] != 0) ||
                   (fsize != (long)animeFrmSize * (totalFrms + 1))           ){

                  JOptionPane.showMessageDialog(mainFrm,"ファイルヘッダが壊れています",
                                                "AceDraw",JOptionPane.ERROR_MESSAGE);
                  canRead = false;
                }

                if(canRead) {
                  fileName = s;
                  for(int i=0; i < totalFrms; i++) {

                    try {
                      fi.read(frameHeadBuf);
                      fi.read(frameImgBuf);
                    } catch(Throwable fe) {
                      JOptionPane.showMessageDialog(mainFrm,"ファイルの入力中にエラーが発生しました",
                                                    "AceDraw",JOptionPane.ERROR_MESSAGE);
                      canRead = false;
                    }
                    if(!canRead) break;

                    aniFrm = new AnimeFrame();
                    if(frameHeadBuf[0]!=0) aniFrm.setMark(true);
                    aniFrm.setImg(frameImgBuf);
                    aniFrms.add(aniFrm);
                  }
                }
              }

            } else {
              JOptionPane.showMessageDialog(mainFrm,"ファイルの拡張子が.ACDではありません",
                                            "AceDraw",JOptionPane.ERROR_MESSAGE);
              canRead = false;
            }
          }
        }
      }

      if(canRead) {
        drawingImg = aniFrms.get(curFrmNo).cloneImg();
        if(curFrmNo < totalFrms-1) {
          nxtFrmImg = aniFrms.get(curFrmNo+1).getImg();
        }
        updateView();
        updateFrmInfo();
      }
    }
  }

  private String saveFileName() {
    File f = null;
    int n;
    String s = "";

    n = fChoose.showSaveDialog(mainFrm);
    if(n == JFileChooser.APPROVE_OPTION) {
      f = fChoose.getSelectedFile();
      s = f.getAbsolutePath();
    }
    return s;
  }

  private boolean fileSave(String fn) {
    boolean canOut = false;
    File f = null;
    FileOutputStream fos = null;
    int lc,fc;
    int bOfs,eOfs;

    if((fn != null) && (fn.length()!=0)) {
      if(fn.indexOf('.')<0) {
        fn = fn + ".acd";

      } else {
        if(! fn.substring(fn.indexOf('.')).toUpperCase().equals(".ACD")) {
          JOptionPane.showMessageDialog(mainFrm,"ファイルの拡張子が.ACDではありません",
                                        "AceDraw",JOptionPane.ERROR_MESSAGE);
          fn = "";
        }
      }

      if(fn.length() > 0) {
        f = new File(fn);
        if(f.exists()) {
          int r = JOptionPane.showConfirmDialog(mainFrm,"同名ファイルが存在します。上書きしますか?",
                                                "AceDraw",JOptionPane.YES_NO_OPTION);
          if(r == JOptionPane.YES_OPTION) canOut = true;
        } else {
          canOut = true;
        }

        if(canOut) {
          try {
            fos = new FileOutputStream(f);
          } catch(Throwable fe) {
            JOptionPane.showMessageDialog(mainFrm,"ファイルの作成に失敗しました",
                                          "AceDraw",JOptionPane.ERROR_MESSAGE);
          }

          if(canOut) {
            for(int i=0; i<animeFrmSize; i++) fileHeadBuf[i]= 0;

            fileHeadBuf[0] = (byte)'A';		// ファイルID
            fileHeadBuf[1] = (byte)'C';
            fileHeadBuf[2] = (byte)'D';
            fileHeadBuf[3] = 0;

            fileHeadBuf[4] = 0;			// バージョン番号
            fileHeadBuf[5] = 1;

            fileHeadBuf[6] = 0;			// リザーブ
            fileHeadBuf[7] = 0;

            fileHeadBuf[8] = (byte)(aniFrms.size() & 0xff);	// フレーム数
            fileHeadBuf[9] = (byte)((aniFrms.size() >> 8) & 0xff);
            fileHeadBuf[10] = (byte)((aniFrms.size() >> 16) & 0xff);
            fileHeadBuf[11] = (byte)((aniFrms.size() >> 24) & 0xff);

            try {
              fos.write(fileHeadBuf);
            } catch(Throwable fe) {
              JOptionPane.showMessageDialog(mainFrm,"出力中にエラーが発生しました",
                                            "AceDraw",JOptionPane.ERROR_MESSAGE);
              canOut = false;
            }
          }

          for(int fr = 0; fr < aniFrms.size(); fr ++) {
            if(! canOut) break;

            if(aniFrms.get(fr).getMark()) frameHeadBuf[0] = 1;
            else                          frameHeadBuf[0] = 0;

            for(int i=1; i<128; i++) frameHeadBuf[i] = 0;

            frameImgBuf = aniFrms.get(fr).getImg();

            if(canOut) {
              try {
                fos.write(frameHeadBuf);
                fos.write(frameImgBuf);
              } catch(Throwable fe) {
                JOptionPane.showMessageDialog(mainFrm,"出力中にエラーが発生しました",
                                              "AceDraw",JOptionPane.ERROR_MESSAGE);
                canOut = false;
              }
            }
          } 	// for

          try {
            fos.flush();
            fos.close();
            fileClean = true;
          } catch(Throwable fe) {
            JOptionPane.showMessageDialog(mainFrm,"出力ファイルのクローズに失敗しました",
                                          "AceDraw",JOptionPane.ERROR_MESSAGE);
            canOut = false;
            fileClean = false;
          }
        }
      }
    }

    return canOut;
  }

  private boolean doFilesaveAs() {
    boolean r =false;

    String nfn = saveFileName();
    if((nfn != null) && (nfn.length() > 0)) {
      if(fileSave(nfn)) {
        fileName = nfn;
        r = true;
      }
    }
    return r;
  }

  private boolean doFileSave() {
    boolean r = false;

    if(fileName.length() == 0) {
      r = doFilesaveAs();
    } else {
      r = fileSave(fileName);
    }
    return r;
  }

  private ActionListener fileActLsn = new ActionListener() {
    public void actionPerformed(ActionEvent e) {
      updateFrm();
      if(e.getActionCommand().equals("New")) {
        doFileNew();

      } else if(e.getActionCommand().equals("Open")) {
        doFileOpen();

      } else if(e.getActionCommand().equals("Save")) {
        doFileSave();

      } else if(e.getActionCommand().equals("saveAs")) {
        doFilesaveAs();

      } else if(e.getActionCommand().equals("eXit")) {
        if(checkClean()) System.exit(0);

      } else {
      }
    }
  }; 


  private void addNewFrm() {
    prvFrmImg = drawingImg;
    drawingImg = newImg();

    AnimeFrame aniFrm = new AnimeFrame(); 
    if(curFrmNo < aniFrms.size() - 1) {
      aniFrms.add(curFrmNo+1,aniFrm);
    } else {
      aniFrms.add(aniFrm);
    }      
    curFrmNo++;

    updateView();
    updateFrmInfo();
    fileClean = false;
  }

  private void insertFrm() {
    nxtFrmImg = drawingImg;
    drawingImg = newImg();

    AnimeFrame aniFrm = new AnimeFrame(); 
    aniFrms.add(curFrmNo,aniFrm);

    updateView();
    updateFrmInfo();
    fileClean = false;
  }

  private void deleteFrm() {
    if(aniFrms.size() > 1) {
      int r = JOptionPane.showConfirmDialog(mainFrm,"このフレームを削除しますか?",
                                            "AceDraw",JOptionPane.YES_NO_OPTION);

      if(r == JOptionPane.YES_OPTION) {
        aniFrms.remove(curFrmNo);
        if(curFrmNo == aniFrms.size()) {
          curFrmNo--;
          drawingImg = aniFrms.get(curFrmNo).cloneImg();
          if(curFrmNo > 0) {
            prvFrmImg = aniFrms.get(curFrmNo-1).getImg();
          } else {
            prvFrmImg = newImg();
          }
        } else {
          drawingImg = nxtFrmImg;
          if(curFrmNo < aniFrms.size() - 1) {
            nxtFrmImg = aniFrms.get(curFrmNo+1).getImg();
          } else {
            nxtFrmImg = newImg();
          }
        }

        updateView();
        updateFrmInfo();
        fileClean = false;
      }
    }
  }

  private ActionListener frmActLsn = new ActionListener() {
    public void actionPerformed(ActionEvent e) {
      updateFrm();
      if(e.getActionCommand().equals("New")) {
        addNewFrm();

      } else if(e.getActionCommand().equals("Insert")) {
        insertFrm();

      } else if(e.getActionCommand().equals("Delete")) {
        deleteFrm();

      } else {
      }
    }
  }; 


  private void initProg() {
    drawingImg = newImg();
    prvFrmImg = newImg();
    nxtFrmImg = newImg();
    undoImg = newImg();
    redoImg = newImg();
    plPoints = new ArrayList<Point>();
  }

  private void doProg() {
    AnimeFrame aniFrm = new AnimeFrame();
    aniFrms.add(aniFrm);
    curFrmNo = 0;
    canundo = false;
    canredo = false;
    fileClean = true;
    drawmode = DrawMode.none;

    FileNameExtensionFilter fFilt = new FileNameExtensionFilter("ACEdraw data", "acd");
    fChoose.addChoosableFileFilter(fFilt);

    grpPnl.addMouseListener(grpMLsn);
    grpPnl.addMouseMotionListener(grpMmLsn);
    undoBtn.addMouseListener(undoBtnLsn);
    freeBtn.addMouseListener(freeBtnLsn);
    snglBtn.addMouseListener(snglBtnLsn);
    polyBtn.addMouseListener(polyBtnLsn);

    frmBMBtn.addMouseListener(frmBMBtnLsn);
    frmBBtn.addMouseListener(frmBBtnLsn);
    frmFBtn.addMouseListener(frmFBtnLsn);
    frmFMBtn.addMouseListener(frmFMBtnLsn);
    frmPBtn.addItemListener(frmPBtnLsn);
    frmNBtn.addItemListener(frmNBtnLsn);

    mainFrm.addWindowListener(winLsn);
  }

  WindowListener winLsn = new WindowAdapter() {
    public void windowClosing(WindowEvent e) {
      updateFrm();
      if(checkClean()) System.exit(0);
    }
  };

  private void makeFrame() {
    JFrame.setDefaultLookAndFeelDecorated(true);
    JDialog.setDefaultLookAndFeelDecorated(true);

    mainFrm = new JFrame("AceDrawプロト2");
    mainFrm.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);

    JPanel toolBtnPnl = new JPanel(new GridLayout(3,1,2,0));
    freeBtn =new JToggleButton("free");
    snglBtn =new JToggleButton("single");
    polyBtn =new JToggleButton("poly");
    toolBtnPnl.add(freeBtn);
    toolBtnPnl.add(snglBtn);
    toolBtnPnl.add(polyBtn);
    ButtonGroup toolBtns = new ButtonGroup();
    toolBtns.add(freeBtn);
    toolBtns.add(snglBtn);
    toolBtns.add(polyBtn);

    plMenu = new JPopupMenu();
    JMenuItem plItemEnd = new JMenuItem("End");
    plItemEnd.addMouseListener(plEndLsn);
    JMenuItem plItemBack = new JMenuItem("Back");
    plItemBack.addMouseListener(plBackLsn);
    plMenu.add(plItemEnd);
    plMenu.add(plItemBack);
    plMenuVisible = false;

    JPanel toolPnl = new JPanel(new BorderLayout(0,4));
    undoBtn = new JButton("undo draw");
    toolPnl.add(undoBtn,BorderLayout.NORTH);
    toolPnl.add(toolBtnPnl,BorderLayout.SOUTH);

    JPanel drawPnl = new JPanel(new BorderLayout());
    grpInfoLbl = new JLabel();
    grpInfoLbl.setFont(new Font("Monospaced",Font.PLAIN,10));
    drawPnl.add(grpInfoLbl,BorderLayout.NORTH);
    drawPnl.add(toolPnl,BorderLayout.SOUTH);

    JPanel mainPnl = new JPanel(new BorderLayout(4,4));
    JMenuBar mainMenu = new JMenuBar();

    JPanel frmFBPnl = new JPanel(new GridLayout(1,6,2,2));
    frmBMBtn = new JButton("<<");
    frmBBtn = new JButton("<");
    frmFBtn = new JButton(">");
    frmFMBtn  = new JButton(">>");

    frmPBtn = new JToggleButton("P");
    frmNBtn = new JToggleButton("N");

    frmFBPnl.add(frmBMBtn);
    frmFBPnl.add(frmBBtn);
    frmFBPnl.add(frmFBtn);
    frmFBPnl.add(frmFMBtn);
    frmFBPnl.add(frmPBtn);
    frmFBPnl.add(frmNBtn);

    JPanel frmInfoPnl = new JPanel(new BorderLayout(2,2));
    frmInfoLbl = new JLabel("1 / 1");
    JLabel lblFrmFrame = new JLabel("Frame:");
    frmInfoPnl.add(lblFrmFrame,BorderLayout.WEST);
    frmInfoPnl.add(frmInfoLbl,BorderLayout.CENTER);

    JPanel frmCtrlPnl = new JPanel(new BorderLayout(8,4));
    frmCtrlPnl.add(frmInfoPnl,BorderLayout.NORTH);
    frmCtrlPnl.add(frmFBPnl,BorderLayout.CENTER);

    grpPnl.setPreferredSize(new Dimension(imgWidth,imgHeight));

    mainPnl.add(grpPnl,BorderLayout.CENTER);
    mainPnl.add(drawPnl,BorderLayout.EAST);
    mainPnl.add(frmCtrlPnl,BorderLayout.SOUTH);
    mainPnl.setBorder(new EmptyBorder(4,4,4,4));

    fileMenu = new JMenu("File");
    fileMenu.setMnemonic(KeyEvent.VK_F);
    JMenuItem fileNew = new JMenuItem("New",KeyEvent.VK_N);
    fileNew.addActionListener(fileActLsn);
    JMenuItem fileOpen = new JMenuItem("Open",KeyEvent.VK_O);
    fileOpen.addActionListener(fileActLsn);
    JMenuItem fileSave = new JMenuItem("Save",KeyEvent.VK_S);
    fileSave.addActionListener(fileActLsn);
    JMenuItem filesaveAs = new JMenuItem("saveAs",KeyEvent.VK_A);
    filesaveAs.addActionListener(fileActLsn);
    JMenuItem fileeXit = new JMenuItem("eXit",KeyEvent.VK_X);
    fileeXit.addActionListener(fileActLsn);
    fileMenu.add(fileNew);
    fileMenu.add(fileOpen);
    fileMenu.add(fileSave);
    fileMenu.add(filesaveAs);
    fileMenu.add(fileeXit);
    mainMenu.add(fileMenu);

    grpMenu = new JMenu("Graphic");
    grpMenu.setMnemonic(KeyEvent.VK_G);
    JMenuItem grpcuT = new JMenuItem("cuT",KeyEvent.VK_T);
    grpcuT.setEnabled(false);
    JMenuItem grpCopy = new JMenuItem("Copy",KeyEvent.VK_C);
    grpCopy.setEnabled(false);
    JMenuItem grpPaste = new JMenuItem("Paste",KeyEvent.VK_P);
    grpPaste.setEnabled(false);
    grpMenu.add(grpcuT);
    grpMenu.add(grpCopy);
    grpMenu.add(grpPaste);
    mainMenu.add(grpMenu);

    frmMenu = new JMenu("fRame");
    frmMenu.setMnemonic(KeyEvent.VK_R);
    JMenuItem frmNew = new JMenuItem("New",KeyEvent.VK_N);
    frmNew.addActionListener(frmActLsn);
    JMenuItem frmIns = new JMenuItem("Insert",KeyEvent.VK_I);
    frmIns.addActionListener(frmActLsn);
    JMenuItem frmDel = new JMenuItem("Delete",KeyEvent.VK_D);
    frmDel.addActionListener(frmActLsn);
    JMenuItem frmMove = new JMenuItem("Move",KeyEvent.VK_M);
    frmMove.setEnabled(false);
    JMenuItem frmCopy = new JMenuItem("Copy",KeyEvent.VK_C);
    frmCopy.setEnabled(false);
    JMenuItem frmSelF = new JMenuItem("Select frames",KeyEvent.VK_S);
    frmSelF.setEnabled(false);
    frmMenu.add(frmNew);
    frmMenu.add(frmIns);
    frmMenu.add(frmDel);
    frmMenu.add(frmMove);
    frmMenu.add(frmCopy);
    frmMenu.add(frmSelF);
    mainMenu.add(frmMenu);

    playMenu = new JMenu("Play");
    playMenu.setMnemonic(KeyEvent.VK_P);
    JMenuItem playSt = new JMenuItem("Start",KeyEvent.VK_S);
    playSt.setEnabled(false);
    playMenu.add(playSt);
    mainMenu.add(playMenu);

    mainFrm.getContentPane().add(mainPnl);
    mainFrm.setJMenuBar(mainMenu);
    mainFrm.pack();
    mainFrm.setVisible(true);
    updateView();
  }

  public static void main(final String[] args) {
    AceDrawProto2 myProg = new AceDrawProto2();
    myProg.initProg();
    myProg.makeFrame();
    myProg.doProg();
  }
}