aboutsummaryrefslogtreecommitdiff
path: root/src/clojure/contrib/swing_utils.clj
blob: 013d7d292e398cae3be9775b6acaddb8c143aa88 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
;;  Copyright (c) Stephen C. Gilardi. All rights reserved.  The use and
;;  distribution terms for this software are covered by the Eclipse Public
;;  License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which can
;;  be found in the file epl-v10.html at the root of this distribution.  By
;;  using this software in any fashion, you are agreeing to be bound by the
;;  terms of this license.  You must not remove this notice, or any other,
;;  from this software.
;;
;;  clojure.contrib.swing-utils
;;
;;  Useful functions for interfacing Clojure to Swing
;;
;;  scgilardi (gmail)
;;  Created 31 May 2009

(ns clojure.contrib.swing-utils
  (:import (java.awt.event ActionListener KeyAdapter)
           (javax.swing AbstractAction Action 
                        JMenu JMenuBar JMenuItem
                        SwingUtilities))
  (:use [clojure.contrib.def :only (defvar)]))

(defn add-action-listener
  "Adds an ActionLister to component. When the action fires, f will be
  invoked with the event as its first argument followed by args.
  Returns the listener."
  [component f & args]
  (let [listener (proxy [ActionListener] []
                   (actionPerformed [event] (apply f event args)))]
    (.addActionListener component listener)
    listener))

(defn add-key-typed-listener
  "Adds a KeyListener to component that only responds to KeyTyped events.
  When a key is typed, f is invoked with the KeyEvent as its first argument
  followed by args. Returns the listener."
  [component f & args]
  (let [listener (proxy [KeyAdapter] []
                   (keyTyped [event] (apply f event args)))]
    (.addKeyListener component listener)
    listener))

;; ----------------------------------------------------------------------
;; Meikel Brandmeyer

(defn do-swing*
  "Runs thunk in the Swing event thread according to schedule:
    - :later => schedule the execution and return immediately
    - :now   => wait until the execution completes."
  [schedule thunk]
  (cond
   (= schedule :later) (SwingUtilities/invokeLater thunk)
   (= schedule :now) (if (SwingUtilities/isEventDispatchThread)
                       (thunk)
                       (SwingUtilities/invokeAndWait thunk)))
  nil)

(defmacro do-swing
  "Executes body in the Swing event thread asynchronously. Returns
  immediately after scheduling the execution."
  [& body]
  `(do-swing* :later (fn [] ~@body)))

(defmacro do-swing-and-wait
  "Executes body in the Swing event thread synchronously. Returns
  after the execution is complete."
  [& body]
  `(do-swing* :now (fn [] ~@body)))

(defvar action-translation-table
  (atom {:name        Action/NAME
         :accelerator Action/ACCELERATOR_KEY
         :command-key Action/ACTION_COMMAND_KEY
         :long-desc   Action/LONG_DESCRIPTION
         :short-desc  Action/SHORT_DESCRIPTION
         :mnemonic    Action/MNEMONIC_KEY
         :icon        Action/SMALL_ICON})
  "Translation table for the make-action constructor.")

(defn make-action
  "Create an Action proxy from the given action spec. The standard keys
  recognised are: :name, :accelerator, :command-key, :long-desc,
  :short-desc, :mnemonic and :icon - corresponding to the similar named
  Action properties.  The :handler value is used in the actionPerformed
  method of the proxy to pass on the event."
  [spec]
  (let [t-table @action-translation-table
        handler (:handler spec)
        spec    (dissoc spec :handler)
        spec    (map (fn [[k v]] [(t-table k) v]) spec)
        action  (proxy [AbstractAction] []
                  (actionPerformed [evt] (handler evt)))]
    (doseq [[k v] spec]
      (.putValue action k v))
    action))

(defvar menu-constructor-dispatch
  (atom #{:action :handler :items})
  "An atom containing the dispatch set for the add-menu-item method.")

(defmulti add-menu-item
  "Adds a menu item to the parent according to the item description.
   The item description is a map of the following structure.

 Either:
   - one single :action specifying a javax.swing.Action to be associated
     with the item.
   - a specification suitable for make-action
   - a set of :name, :mnemonic and :items keys, specifying a submenu with
     the given sequence of item entries.
   - an empty map specifying a separator."
  {:arglists '([parent item])}
  (fn add-menu-item-dispatch [_ item]
    (some @menu-constructor-dispatch (keys item))))

(defmethod add-menu-item :action
  add-menu-item-action
  [parent {:keys [action]}]
  (let [item (JMenuItem. action)]
    (.add parent item)))

(defmethod add-menu-item :handler
  add-menu-item-handler
  [parent spec]
  (add-menu-item parent {:action (make-action spec)}))

(defmethod add-menu-item :items
  add-menu-item-submenu
  [parent {:keys [items mnemonic name]}]
  (let [menu (JMenu. name)]
    (when mnemonic
      (.setMnemonic menu mnemonic))
    (doseq [item items]
      (add-menu-item menu item))
    (.add parent menu)))

(defmethod add-menu-item nil ; nil meaning separator
  add-menu-item-separator
  [parent _]
  (.addSeparator parent))

(defn make-menubar
  "Create a menubar containing the given sequence of menu items. The menu
  items are described by a map as is detailed in the docstring of the
  add-menu-item function."
  [menubar-items]
  (let [menubar (JMenuBar.)]
    (doseq [item menubar-items]
      (add-menu-item menubar item))
    menubar))

;; ----------------------------------------------------------------------