Javaによる簡易ドローエディタの作成

今回はJavaによる簡易ドローエディタについて。
まず、機能としては

    • 基本図形(四角・楕円・直線)の描写
    • 基本色(赤・緑・青)の描写
    • 右クリックによるUndo(厳密に言えば図形削除)

の三つである。


なお、プログラムリストは以下のとおり。

     1  import javax.swing.*;
     2  import java.awt.*;
     3  import java.awt.event.*;
     4  import java.util.*;
     5
     6  abstract class Figure {
     7    protected int x,y,width,height;
     8    protected Color color;
     9    public Figure(int x,int y,int w,int h,Color c) {
    10      this.x = x; this.y = y;
    11      width = w; height = h;
    12      color = c;
    13    }
    14    public void setSize(int w,int h) {
    15      width = w; height = h;
    16    }
    17    public void setLocation(int x,int y) {
    18      this.x = x; this.y = y;
    19    }
    20    abstract public void reshape(int x1,int y1,int x2,int y2);
    21    abstract public void paint(Graphics g);
    22  }
    23
    24  class LineFigure extends Figure {
    25    public LineFigure(int x,int y,int w,int h,Color c) {
    26      super(x,y,w,h,c);
    27    }
    28    public void reshape(int x1,int y1,int x2,int y2) {
    29      setLocation(x1,y1);
    30      setSize(x2,y2);
    31    }
    32    public void paint(Graphics g) {
    33      g.setColor(color);
    34      g.drawLine(x,y,width,height);
    35    }
    36  }
    37
    38  class CircleFigure extends Figure {
    39    public CircleFigure(int x,int y,int w,int h,Color c) {
    40      super(x,y,w,h,c);
    41    }
    42    public void reshape(int x1,int y1,int x2,int y2) {
    43      int newx = Math.min(x1,x2);
    44      int newy = Math.min(y1,y2);
    45      int neww = Math.abs(x1 - x2);
    46      int newh = Math.abs(y1 - y2);
    47      setLocation(newx,newy);
    48      setSize(neww,newh);
    49    }
    50    public void paint(Graphics g) {
    51      g.setColor(color);
    52      g.drawOval(x,y,width,height);
    53    }
    54  }
    55
    56  class RectangleFigure extends Figure {
    57    public RectangleFigure(int x,int y,int w,int h,Color c) {
    58      super(x,y,w,h,c);
    59    }
    60    public void reshape(int x1,int y1,int x2,int y2) {
    61      int newx = Math.min(x1,x2);
    62      int newy = Math.min(y1,y2);
    63      int neww = Math.abs(x1 - x2);
    64      int newh = Math.abs(y1 - y2);
    65      setLocation(newx,newy);
    66      setSize(neww,newh);
    67    }
    68    public void paint(Graphics g) {
    69      g.setColor(color);
    70      g.drawRect(x,y,width,height);
    71    }
    72  }
    73
    74  class DrawApplication {
    75      protected Vector<Figure> figures;
    76      protected Figure drawingFigure;
    77      protected String figurelabel;
    78      protected Color currentColor;
    79      protected DrawPanel drawPanel;
    80      public DrawApplication() {
    81          figures = new Vector<Figure>();
    82          drawingFigure = null;
    83          currentColor = Color.red;
    84          figurelabel = "rect";
    85      }
    86      public void setDrawPanel(DrawPanel c) {
    87          drawPanel = c;
    88      }
    89      public int getNumberOfFigures() {
    90          return figures.size();
    91      }
    92      public Figure getFigure(int index) {
    93          return (Figure)figures.elementAt(index);
    94      }
    95      public void createFigure(int x,int y) {
    96          Figure f = null;;
    97          if(figurelabel == "rect") f = new RectangleFigure(x,y,0,0,currentColor);
    98          else if(figurelabel == "circ") f = new CircleFigure(x,y,0,0,currentColor);
    99          else if(figurelabel == "line") f = new LineFigure(x,y,x,y,currentColor);
   100
   101          figures.addElement(f);
   102          drawingFigure = f;
   103          drawPanel.repaint();
   104      }
   105      public void reshapeFigure(int x1,int y1,int x2,int y2) {
   106          if (drawingFigure != null) {
   107              drawingFigure.reshape(x1,y1,x2,y2);
   108              drawPanel.repaint();
   109          }
   110      }
   111      public void changecolor(Color c){
   112          currentColor = c;
   113      }
   114      public void changefigure(String s){
   115          figurelabel = s;
   116      }
   117      public void undo(){
   118          figures.remove(figures.size()-1);
   119          drawPanel.repaint();
   120      }
   121  }
   122
   123
   124  class DrawPanel extends JPanel {
   125      protected DrawApplication drawApplication;
   126      public DrawPanel(DrawApplication app) {
   127      setBackground(Color.white);
   128      drawApplication = app;
   129      }
   130      public void paintComponent(Graphics g) {
   131          super.paintComponent(g);
   132          //[すべてのFigureをpaintする]
   133          for(int i=0; i < drawApplication.getNumberOfFigures();i++){
   134              Figure f = drawApplication.getFigure(i);
   135              f.paint(g);
   136          }
   137      }
   138  }
   139
   140  class DrawMouseListener implements MouseListener,MouseMotionListener {
   141      protected DrawApplication drawApplication;
   142      protected int dragStartX,dragStartY;
   143      public DrawMouseListener(DrawApplication a) {
   144      drawApplication = a;
   145      }
   146      public void mouseClicked(MouseEvent e) {
   147  }
   148      public void mousePressed(MouseEvent e) {
   149      dragStartX = e.getX(); dragStartY = e.getY();
   150      if(SwingUtilities.isRightMouseButton(e) == true)
   151          drawApplication.undo();
   152      else if(SwingUtilities.isLeftMouseButton(e) == true)
   153          drawApplication.createFigure(dragStartX,dragStartY);
   154      }
   155      public void mouseReleased(MouseEvent e) {
   156      drawApplication.reshapeFigure(dragStartX,dragStartY,e.getX(),e.getY());
   157      }
   158      public void mouseEntered(MouseEvent e) { }
   159      public void mouseExited(MouseEvent e) { }
   160      public void mouseDragged(MouseEvent e) {
   161      drawApplication.reshapeFigure(dragStartX,dragStartY,e.getX(),e.getY());
   162      }
   163      public void mouseMoved(MouseEvent e) { }
   164  }
   165
   166  class Select implements ActionListener {
   167      DrawApplication a;
   168
   169      Select (DrawApplication ap){
   170          a = ap;
   171      }
   172      public void actionPerformed(ActionEvent e){ //設定の変更
   173          String es = e.getActionCommand();
   174          if(es.equals("red"))  a.changecolor(Color.red);
   175          if(es.equals("green"))  a.changecolor(Color.green);
   176          if(es.equals("blue"))  a.changecolor(Color.blue);
   177          if(es.equals("rect"))  a.changefigure("rect");
   178          if(es.equals("circ"))  a.changefigure("circ");
   179          if(es.equals("line"))  a.changefigure("line");
   180
   181      }
   182  }
   183
   184
   185  class DrawMain {
   186      public static void main(String argv[]) {
   187      JFrame f = new JFrame("Draw");
   188      JPanel pc = new JPanel();
   189      JPanel pf = new JPanel();
   190      pc.setLayout(new GridLayout(1,3));
   191      pf.setLayout(new GridLayout(1,3));
   192      JButton r = new JButton("c.red");
   193      JButton g = new JButton("c.green");
   194      JButton b = new JButton("c.blue");
   195
   196      JButton rect = new JButton("□");
   197      JButton circ = new JButton("○");
   198      JButton line = new JButton("─");
   199
   200      r.setActionCommand("red");
   201      g.setActionCommand("green");
   202      b.setActionCommand("blue");
   203
   204      rect.setActionCommand("rect");
   205      circ.setActionCommand("circ");
   206      line.setActionCommand("line");
   207
   208      DrawApplication a = new DrawApplication();
   209      DrawPanel dp = new DrawPanel(a);
   210      a.setDrawPanel(dp);
   211      DrawMouseListener ml = new DrawMouseListener(a);
   212      dp.addMouseListener(ml);
   213      dp.addMouseMotionListener(ml);
   214
   215      pc.add(r);
   216      pc.add(g);
   217      pc.add(b);
   218
   219      pf.add(rect);
   220      pf.add(circ);
   221      pf.add(line);
   222
   223      b.addActionListener(new Select(a));
   224      g.addActionListener(new Select(a));
   225      r.addActionListener(new Select(a));
   226
   227      rect.addActionListener(new Select(a));
   228      circ.addActionListener(new Select(a));
   229      line.addActionListener(new Select(a));
   230
   231      f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
   232      f.getContentPane().add(dp,BorderLayout.CENTER);
   233      f.getContentPane().add(pc,BorderLayout.SOUTH);
   234      f.getContentPane().add(pf,BorderLayout.NORTH);
   235
   236      f.setSize(400,300);
   237      f.setVisible(true);
   238      }
   239  }
FIgureクラス

描写した図形の情報の更新・保存をする抽象クラス。

更新・保存する情報は
x座標、y座標、幅、高さ、色
の5つ

以下、主な公開メソッド

setSize
描写した図形の幅・高さを更新・保存するメソッド

setLocation
描写した図形のx座標、y座標を更新・保存するメソッド

reshape
それぞれの図形において、マウスの操作に応じて再描写するメソッド

paint
描写した図形をパネル上に表示させるメソッド

CircleFigureクラス

線を描写するクラス
抽象クラスFigureを継承する

Figureクラスのreshapeメソッド中で
図形の情報を更新し、paintメソッド中で
色の指定と図形の描写を行う。

このクラス独自の公開メソッドはない。


CircleFigureクラス

楕円を描写するクラス
抽象クラスFigureを継承する

Figureクラスのreshapeメソッド中で
図形の情報を更新し、paintメソッド中で
色の指定と図形の描写を行う。

このクラス独自の公開メソッドはない。


RectangleFigureクラス

四角形を描写するクラス
抽象クラスFigureを継承する

Figureクラスのreshapeメソッド中で
図形の情報を更新し、paintメソッド中で
色の指定と図形の描写を行う。

このクラス独自の公開メソッドはない。


DrawApplicationクラス

図形を描写する際の様々な機能を管理・実行するクラス。

主な公開フィールドとして
・Figure型のVevtor配列のインスタンスFigure
描写した図形を保存する
・Figure型のインスタンスdrawingFigure
描写する図形の情報を管理する
・String型のインスタンスfigurelabel
描写する図形の種類を判別する
・Color型のインスタンスcurrentColor
描写する図形の色を判別する
・DrawPanel型のインスタンスdrawPanel
描写するパネルを判別する

以下、主な公開メソッドである。

setDrawPanel
DrawPanelクラスのインスタンスを設定するメソッド

getNumberOfFigures
Vector配列Figuresの要素数を返すメソッド。返り値はint型。

getFigure
Vector配列Figuresのi番目に格納されている図形を返すメソッド
引数はint型、返り値はFigure型

createFigure
新たな図形を生成するメソッド
ここで選択した図形を生成する。

reshapeFigure
生成した図形の再描写を行うメソッド
マウス操作からの情報をリアルタイムで更新する。

changecolor
選択した色の情報を更新するメソッド

changefigure
選択した図形の情報を更新するメソッド

undo
Vector配列Figuresの一番最後の要素を削除するメソッド

DrawPanelクラス

図形を描写するパネルを管理するクラス。
背景色の設定と図形の表示をする。

公開フィールドとして
・DrawApplication型のインスタンス
Figuresにある図形を参照する。

以下、主な公開メソッドである。

paintComponent
すべてのFigureをpaintするメソッド
つまり描写した図形をパネル上に表示する。

DrawMouseListenerクラス

マウスの操作情報を管理するクラス
クリックやポインタの座標などの情報を他のクラスへ渡す。

公開フィールドはDrawApplication型のインスタンス
ドラッグ開始時のマウスの座標を保存するint型の変数X,Y

以下、主な公開メソッドである。

mousePressd
マウスが押されたときの動作を決定するメソッド
クリックされた時にまずカーソルの座標を保存。
その次にそれが左右どちらのクリックかを判定。
右クリックの場合はDrawApplicationのundoを実行。
左クリックの場合はDrawApplicationのcreatefigureを実行。

mouseReleased
マウスが押されなくなったときの動作を決定するメソッド
その時点でのマウスの座標から図形を再描写する。

mouseDragged
マウスがドラッグ状態のときの動作を決定するメソッド
ドラッグ中はリアルタイムでマウスの座標から図形を再描写する。

Selectクラス

ボタンによる動作を管理・実行するクラス。
プログラムの効率化をはかるため
色選択と図形選択の実行を同一のクラスにまとめた。

主な公開フィールドはDrawApplicationのインスタンス

以下、主な公開メソッドである。

actionPerformed
ボタンが押されたときの動作を決定するメソッド
ActionEvent変数によって条件分岐させる。

DrawMainクラス

フレーム・パネル・ボタンの設置・管理をするクラス。
mainメソッドがここに入る。

mainメソッド内での主な公開フィールドは
ウィンドウの基板のJFrame
色選択ボタン、図形選択ボタンを設置するJPanel
計6個のボタンのJButton
DrawApplicationのインスタンス
DrawPanelのインスタンス
DrawMouseListenerのインスタンス


ここでの動作は
フレームの設置
パネルの設置(色を下段、図形を上段、描写領域はその間となる)
ボタンの設置(色はR・G・Bの3つ、図形は四角・楕円・線の3つ)

素となるアプリケーションの作成し
そのアプリケーションでの動作を描写パネルに連携させ
マウス操作によってアプリケーションが起動するよう連携させる。
また、ボタン操作と実行動作を連携させる。

そして設置したフレーム・パネル及びボタンを表示させ
フレームのサイズを設定する。

実行例

赤・緑・青それぞれの
四角形・楕円・線を描写

なお、スクリーンショットでは分からないが右クリックで
アンドゥ(現在図形の削除)ができる。


色の選択については、
JColorChooserを用いれば多機能な色の選択ができたが
今回はオリジナルとして3原色を単純なボタン選択で実現した。

また今回は余裕がなかったが、パネル上に塗りつぶし領域を置き
それをクリックすれば塗りつぶされている色で描写ができる
windows標準ペイントソフトのような)機能も擬似的に実現できる気がした。

また、グラデーションから色を選択する機能を用いれば
もっと柔軟性に富んだ色の選択ができると思われる。


図形の選択については
今回は基本図形(四角・楕円・線)の3つを実現。
しかし、どれもGraphicクラスで簡単に実現できるものだったので
あまり、手間はかからなかった。

カーソル座標と描写開始位置を少し変えれば、
ドラッグ開始点を中心とした図形の描写ができると思われる。
また、Lineメソッドを応用させて自由曲線や多角形の作成
などもできると思われる。

いずれにしろGraphicクラスによって
図形の作成は容易であることがわかった。


Undoについては
厳密に言えば、一般の''やり直し''ではなく、図形の削除である。
つまり、一度Undo操作を行えば二度とその図形は戻ってこない。
なのでスタックを用いて削除した図形を保存・復元するRedo機能を実現すれば、
より便利なものになるのではと思った。

上記の他にも図形の選択・移動・削除や
ツールバーの追加・データの保存
右クリックによるメニューの表示

など
色々と実現してみたい機能はたくさんあった。


今回の実験を通して
Javaという言語は、膨大な数のクラスのおかげで
多機能なアプリケーションの実現が可能であるということがわかった。

今回は簡易ドローエディタという入門的なプログラミングだったが
これからもっと他のソフトも作ってみたい。
それと同時に他にどんな機能を持ったクラスやメソッドがあるのか
調べてみたいと思った。