新規作成、開く、名前をつけて保存、上書き保存、終了をトレイトで

この記事は、Play or Scala Advent Calendar 2012 の24日目の記事です。
http://qiita.com/advent-calendar/2012/play-or-scala

概要

GUIアプリケーションを作るときに、ファイルメニューに「新規作成」「開く」「上書き保存」
「名前をつけて保存」というメニューを用意することがよくあります。
なんとか共通化できないかと思っていたのですが、
トレイトを使うといい感じに整理できたので紹介します。

前提・環境

ここでは、同時に複数のファイルが扱えないSDI(Single Document Interface)を想定しています。
メモ帳やペイントなどがそれにあたります。
また、GUIは javax.swingパッケージを使っています。
(scala.swingパッケージではありません)。

トレイトを使わない場合

単に「開く」と言ってもファイル選択するだけではなく、すでに現在のドキュメントが
編集されていたら、先に保存するかどうか問い合わせなければなりません。
保存する場合でも、編集中のドキュメントがファイルと関連付けられていないときには
保存するファイル名を入力させる必要があります。
擬似的なコードを書くと以下のようになります。

    /** [開く]のときの処理 */
    def actionPerformed(e:ActionEvent) {
      // 編集されていたら、
      if (modified) {
        // 保存しますか?と問い合わせる。
        // キャンセルならreturn
        if (Yesなら) {
          // ファイルと関連付けられてないとき、
          if (file == null) {
            // 保存用ファイル選択ダイアログを表示。
          }
          save(file)      // ★ココがアプリ固有の処理
          modified = false
        }
      }
      // オープン用ファイル選択ダイアログを表示。
      // 開くボタンを押したら
      open(file)          // ★ココがアプリ固有の処理
      modified = false
      // GUIを更新。
    }

これらの処理のうち、大半は紋切り型のコードです。
アプリ固有の部分は、★の箇所のopen()とsave()だけです。

なんとか共通化できないかと思っていたのですが、
トレイトを使うといい感じに整理できたので紹介します。
まぁ、共通部分をstaticなメソッドにしたりすればある程度共通化できるのですが、
どうもしっくりきませんでした。

トレイトを使った場合

以下がそのトレイトになります。
GUIアプリケーションには、ビューアとエディタの2種類が考えられるので、
SingleDocumentViewerとSingleDocumentEditorの2つのトレイトに分けました。

trait SingleDocumentViewer {
	self: JFrame =>  // 自分型の指定

	var file:File = null
	def open(file:File)
	def updateFrame()

	val fileOpenAction:Action = new FileOpenAction()
	val fileExitAction:Action = new FileExitAction()

	class FileOpenAction extends AbstractAction {
		// 中略
	}

	class FileExitAction extends AbstractAction {
		// 中略
	}
}


trait SingleDocumentEditor extends SingleDocumentViewer {
	self: JFrame =>  // 自分型の指定
	var modified = false

	def init()
	def save(file:File)

	val fileNewAction = new FileNewAction()
	override val fileOpenAction = new FileOpenAction()
	val fileSaveAction = new FileSaveAction()
	val fileSaveAsAction = new FileSaveAsAction()
	override val fileExitAction = new FileExitAction()

	def inquireSave(parentComponent:JFrame):Boolean = {
		// 中略
	}

	class FileNewAction extends AbstractAction {
		// 中略
	}

	class FileOpenAction extends AbstractAction {
		// 中略
	}

	class FileSaveAction extends AbstractAction {
		// 中略
	}

	class FileSaveAsAction extends AbstractAction {
		// 中略
	}

	class FileExitAction extends AbstractAction {
		// 中略
	}
}


まだ荒削りで、考慮すべき点は多いですが、おおむねイイ感じにできています。
ソースはこちら。
http://www.hcn.zaq.ne.jp/no-ji/memo-scala/Advent2012.zip
サンプルとして、テキストパッドと画像ビューアを用意しました。
短いソースですが、ファイルメニューに関しては充実しています。

まとめ

GUIアプリでのトレイトの使い方ですが、
他にも、クリップボード3兄弟、最近使ったファイル、多言語対応、アプリの起動&終了、
などにも適用できるのではないかと考えてます。

ちなみに

ちなみに、今日は私の誕生日です。(*^_^*)