Kikuchy's Second Memory

つくる楽しさをもっと伝えたい。プログラムを書いていて、わからなかったこと・気付いた事を書き留めています。

Clojure で GUI アプリを書いてみる (システムトレイ・タスクトレイ編)

f:id:kikuchy:20130520005339p:plain
f:id:kikuchy:20130520005350p:plain


ちょっと前からハマっている Clojure という言語。
やっぱり『関数が第一級オブジェクト』というのはメソッドをちょろっと書くときに抵抗が無くて良いですよね。

さて、自分用に天気予報を教えてくれるアプリが欲しかったのですが、 勉強も兼ねて作ってみようかと思いました。Mac の Dashboard Widget にすると Dashboard の起動に時間がかかるし(電卓とキッチンタイマーだけしか置いてないのですが、起動に10秒くらいかかる。重症)。それなら初めからシステムトレイに在中するやつで良いんじゃないかと。

ということで、写真のように、システムトレイに在中してくれるものにしたいと思います。
アイコンで3時間後の天気予報を表示してくれるようなやつを計画中。

その第一歩として、まずはシステムトレイにアイコンを表示するところから始めることにしました。
さっそくですが、作り始めてみます。



実行環境


まずは Leiningen で Clojure のプロジェクトを作ります。今回のプジェクト名は tasktraytest としました。

$ lein new tasktraytest
$ cd tasktraytest

プロジェクトの project.clj を以下のように修正。

(defproject tasktraytest "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.5.1"]]
;; この :main を追加
;; これを書いておくと、lein run コマンドで tasktraytest.core/-main が実行される
  :main tasktraytest.core)

念のため依存関係の解決をしておきます。 Clojure を使った事が無ければ時間がかかるかも。

$ lein deps

src/tasktraytest/core.clj を以下のように変更。

(ns tasktraytest.core)
(import '(java.awt SystemTray Image TrayIcon PopupMenu MenuItem)
  '(java.awt.event ActionListener)
  '(javax.imageio ImageIO))

;; クリック時に出てくるポップアップメニューを生成
(defn system-tray-popupmenu []
  (let [popup (PopupMenu.)
    item-exit (MenuItem. "exit")]
    (.addActionListener item-exit (reify ActionListener
      (actionPerformed [_ evt]
        (System/exit 0))))
    (.add popup item-exit)
    popup))

;; アイコン用画像を読み込み
(defn read-image [filepath]
  (ImageIO/read (.getResourceAsStream (.getContextClassLoader (Thread/currentThread)) filepath)))

;; アイコンの実体を生成
(defn system-tray-icon [#^Image image #^String tip #^PopupMenu popup]
  (TrayIcon. image tip popup))

;; アイコンをシステムトレイに設定する
(defn add-icon-system-tray [#^TrayIcon icon]
    (.add (SystemTray/getSystemTray) icon))

;; lein run で実行する関数
(defn -main []
  (add-icon-system-tray (system-tray-icon (read-image "trayicon.png") "Tray Example" (system-tray-popupmenu))))

src の中に "trayicon.png" というファイル名で、適当な png ファイルを置きます。

これで準備完了です。 Leiningen から実行しましょう。

$ lein run

これでシステムトレイにアイコンが出てくるはず。
以上! なんて簡単なんだ!


ちょっとだけ解説。
Java の AWT を使ってタスクトレイにアイコンを追加するコードを Clojure で書き直しただけです。
アイコン用画像の読み込み・アイコンの生成・システムトレイへの設置の三つを分離したのは、今後アイコン画像を切り替える予定があるからです。
system-tray-icon が TrayIcon のインスタンスを返すので、後でこのインスタンスを使って画像を差し替えます。
新しいもの好きなので、できれば Swing を使いたかったのですが、 Swing にはタスクトレイにアクセスする方法が無いみたいで…仕方なく AWT を使いました。
でも、 AWT はシステム依存な部分が多い分、 UI がネイティブで(ユーザーには)使いやすいらしいですね。

そのうち、アイコン画像の差し替え・天気予報の API に接続・タイマーの使用、といったことをしていきたいと思います。



参考