Swing のテキストウィジェットって、OS のコードを使わないで全部自力で書いているらしいので、参考になると思いソースを読んでみました。
まず動作を追いやすいように、テキストウィジェットを表示して、何かが入力されると現在の文字数をコンソールに出力するという役に立たないプログラムを書いてみた。役に立たないなりにブレークポイントを仕込むとイベント処理の仕組みがよく分かる。
import javax.swing.*; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; class Swing { public static void main(String[] args){ JTextArea textarea = new JTextArea(); JScrollPane scrollpane = new JScrollPane(textarea); JFrame frame = new JFrame("Text Test"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.getContentPane().add(scrollpane); frame.setBounds(10, 10, 300, 200); frame.setVisible(true); textarea.getDocument().addDocumentListener(new Handler()); } } class Handler implements DocumentListener { public void insertUpdate(DocumentEvent e) { System.out.println(e.getDocument().getLength()); } public void changedUpdate(DocumentEvent e) { } public void removeUpdate(DocumentEvent e) { } }
こうしといて、例えば insertUpdate の所にブレークポイントを置いてスタックを味わうわけです。プログラムは短いけど、イベントハンドラの為にクラスを作らないといけないのでとても面倒くさい。
ウィジェットのツリー構造についてさっと見る。Gtk の GtkWidget に対応するのが Swing の JComponent。親オブジェクトにアクセスするのにGtk は parent プロパティ Swing は parent メンバを使う。子オブジェクトへの参照は、Gtk はウィジェットによって違うが、Swing は component に入っている。Swing の方式は Morphic と同じで単純だけど、子オブジェクトが無い物や一つしか無い物も component 配列を持ってしまうので、Gtk の方が清潔だと言える。ちなみに Tweak はさらに極端に子オブジェクトは GdkWindow に相当する PrimitiveCostume のみが持つという思想だった。座標系はどうやって管理しているのか良く分からなかった。
更新通知の方法は、Gtk のように全体がカッチリしてなくて、似た実装が重複して存在している。JComponent も PlainDocument モデルの場合 listenerList と言う名前のメンバにリスナが格納される。
Gtk でやったようにそれらしい fireAdjustmentValueChanged というメソッドにブレークポイントを置いてスクロールバーの動きを調べてみた。
- AWT-EventQueue-0 スレッド
- JComponent.processKeyBindings() キーボード処理
- DefaultCaret.changeCaretPosition() // 色々
- SwingUtilities.invokeLater(callRepaintNewCaret); により非同期でキャレットの描画が行われる。
- AWT-EventQueue-0 スレッド二週目
- DefaultCaret.repaintNewCaret() // キャレット再表示
- もしもキャレットが画面の外にあれば adjustVisibility()
- JTextArea(JComponent).scrollRectToVisible()
- JViewport.scrollRectToVisible() // JViewport というのはスクロール領域の事
- JViewport.fireStateChanged() // 更新メカニズム
- JScrollBar.setValues()
- DefaultBoundedRangeModel.setRangeProperties() // DefaultBoundedRangeModel はスクロールバーのモデル GtkAdjustment のような物。
- DefaultBoundedRangeModel.fireStateChanged() // また更新メカニズム
- ...
ちなみに fireStateChanged という名前のメソッドは 14 個あって、どれもリスナに stateChanged を送るという似たコードのコピペ。私は Sun 研究所の綺麗な建物に感動して、きっと Swing のコードも美しいに違いないと期待していたのでちょっとガッカリ。この人たちは14回コピペする前になんか変だと思わなかったのだろうか!
では軽く描画の仕組みも。これには com.sun.java.swing.SwingUtilities2.drawChars() にブレークポイントを仕込めばよい。疲れたので適当に流れを書く。
- AWT-EventQueue-0 スレッドで詳しくは分からないが再描画イベントが呼ばれる
- JTextArea(JComponent).paintComponent() // コンポーネント描画
- BasicTextAreaUI(BasicTextUI).update() // BasicTextUI に委譲
- PlainView.paint() // さらに PlainView 委譲
- Utilities.drawTabbedText() // 実際に描画
- SunGraphics2D.drawChars() // いよいよ実際に描画
多分 BasicTextUI 以下が Gdk に相当する物だと思われる。コンポーネントごとになんとか UI が用意されてあって、実際の描画を行う。テキストのようにややこしい物に関してはさらに View が用意されているが、ボタンなんかは UI が描画まで行っている。