続々Scala2.8でSwing

Scala@東北 第1回BootCamp&HackathonScalaのSwingで、調べたことなどを。

まずは、MenuItemのコンストラクタでActionクラスを引数にするパターンで引っかかりました。

new MenuItem(Action("新規"){
accelerator = ..... //などど書きたかったがエラー。accelerator_は、Actionクラスに定義されているメソッド。
.......
})

なお、一つ前のエントリーのコードを見てもらうとわかりますが、実際に書き上げたコードでは、明示的にnewしたActionクラスのインスタンスを渡す形になっていますので、これは途中での試行錯誤です。

Action("String"){.....}という書き方はコンパニオンクラスのオブジェクトのapplyメソットを呼び出しています。
Actionオブジェクトのapplyの実装を見ると以下のようになっています。

def apply(title: String)(body: =>Unit) = new Action(title) {
   def apply() { body }
}

ScalaのSwingでは、コンポーネントをnewするときに、無名サブクラスを使って実装したりしてます。
こういうやつですね。

contents = new BorderPanel {
      import BorderPanel.Position._
      add(scrollPane, Center)
    }

で、上述の書き方も{}の中は無名サブクラスになっているのかと思っていたのですが、applyメソッドの中身部分を記述することになるので、メソッドを持っていなかったという事ですね。


で、これに関連しての疑問。なぜ、無名サブクラスのapplyの中に書いたコードが呼ばれているのか?
こういうやつですね。

            val newFileAction = new Action("新規(N)") {
            accelerator = Some(KeyStroke.getKeyStroke(JKeyEvent.VK_N,mask))
            mnemonic = JKeyEvent.VK_N
            def apply() {
            textArea.text =""  //<-これ
            }
        }

コードを覗いて調べてみました。
ちなみに、すぐにActionのpeerの実装を見てみればわかったと思うのですが、遠回りしてしまいました。
後、Swingそれなりにわかっている方なら、すぐにピンと来るのかもしれません。
先に引っかかった件で、既に答えがわかってもいいような気がしますが、にぶいんですかね。
まずAction.scalaを見てみましたが、Actionクラスはabstractクラスになっていて単体では特に呼び出されていないようにみえます。
なので、Actionを引数で呼んでいるMenuItemをみてみます。ちなみにここは、一旦むむむっとなって、ymnkさんにヒントをもらってMenuItemをみてみたのでした。

//こんなふうにMenuItemから呼ばれています。newFileActionはActionのインスタンスです。
   contents += new MenuItem(newFileAction)

実際のコードは以下のとおり。

//Menu.scalaより
class MenuItem(title0: String) extends AbstractButton {
  override lazy val peer: JMenuItem = new JMenuItem(title0)
  def this(a: Action) = {
    this("")
    action = a
  }
}

action = aで、action_=に渡していますがここには定義がないので、継承しているAbstractButtonをみてみます。
AbstractButtonには定義はありませんでした。

abstract class AbstractButton extends Component with Action.Trigger.Wrapper with Publisher {

そこで、withしている Action.Trigger.Wrapperをみてみます。
これはどこにあるのかというと、Action.scalaにあります。
振り出しのファイルに戻ってしまいました!
action_=は、以下のように定義されています。

 def action_=(a: Action) { _action = a; peer.setAction(a.peer) }

a.peerのところで、Actionのpeerを呼んでいますね。
ちなみにpeer.setActionのpeerはAction.Trigger.Wrapperで定義されていて、javax.swing.JComponentになります。
まわりまわってActionクラスのpeerに到達しました。
定義は以下の通り。

lazy val peer: javax.swing.Action = new javax.swing.AbstractAction(title0) {
  def actionPerformed(a: java.awt.event.ActionEvent) = apply()
}

なるほど。actionPerformedに実装するイベントは、Actionのapplyで実装されますよというわけですね。


小ネタです。
プレースホルダーが型推論されなかった書き方。

  import scala.io._
    val src = Source.fromFile(chooser.selectedFile,"UTF-8")
//これは駄目でした。Stringだと判別されなかった模様。
//getLinesの返り値はIterator[String]
  src.getLines.foreach{textArea.append(_+"\n")}
//こうしないとコンパイルできませんでした。 
  src.getLines.foreach{l =>textArea.append(l+"\n")}

それから、Scala2.8でgetLinesの改行の取り扱いが変わっている模様。