Принудительный аргумент в макросе Clojure для захвата пространства имен - PullRequest
2 голосов
/ 01 июля 2011

Я работаю над макросом Clojure, чтобы помочь в создании JPanels на основе GridBagLayout. Я могу получить классы Java в карте значений по умолчанию внутри макроса для определения пространства имен, но не для тех, которые передаются в качестве аргументов. Какое волшебное сочетание обратных цитат, цитат, тильд или чего-то еще мне нужно?

(import [java.awt GridBagConstraints GridBagLayout Insets]
        [javax.swing JButton JPanel])

(defmacro make-constraints [gridx gridy & constraints]
  (let [defaults
        {:gridwidth 1 :gridheight 1 :weightx 0 :weighty 0
         :anchor 'GridBagConstraints/WEST :fill 'GridBagConstraints/NONE
         :insets `(Insets. 5 5 5 5) :ipadx 0 :ipady 0}

        values
        (assoc (merge defaults (apply hash-map constraints))
          :gridx gridx :gridy gridy)]
    `(GridBagConstraints. ~@(map (fn [value]
                                   (if
                                    (or
                                     (number? value)
                                     (string? value)
                                     (char? value)
                                     (true? value)
                                     (false? value)
                                     (nil? value))
                                    value
                                    `~value))
                                 (map values
                                      [:gridx :gridy :gridwidth :gridheight
                                       :weightx :weighty :anchor :fill
                                       :insets :ipadx :ipady])))))

Когда я использую Insets, определенный на карте значений по умолчанию, он квалифицируется (не как «захваченный символ») как (java.awt.Insets ...):

user=> (macroexpand-1 '(make-constraints 0 0 :weightx 1))
(java.awt.GridBagConstraints.
 0 0 1 1 1 0
 GridBagConstraints/WEST GridBagConstraints/NONE
 (java.awt.Insets. 5 5 5 5) 0 0)

но когда я передаю это как аргумент, это не так:

user=> (macroexpand-1 '(make-constraints 1 1 :insets (Insets. 2 2 2 2)))
(java.awt.GridBagConstraints.
 1 1 1 1 0 0
 GridBagConstraints/WEST GridBagConstraints/NONE
 (Insets. 2 2 2 2) 0 0)

Я не просто пытаюсь быть сторонником. Я получаю ошибки компилятора, что он не может найти правильный GridBagConstraints конструктор.

Ответы [ 2 ]

3 голосов
/ 01 июля 2011

Я не знаю GridBagLayout, но следующее должно в основном работать подобно вашему макросу. Если у вас есть компонент с :height больше 1, вам нужно добавить nil в столбцы ниже, чтобы счетчик столбцов был синхронизирован. Скажем, ваш arrive-text-field будет иметь высоту 2, чем вам нужно будет добавить строку nil перед строкой depart-label, чтобы счетчики были правильными. Это просто быстрый взлом.

(def default-opts
  {:insets   (Insets. 0 0 0 0)
   :width    1
   :height   1
   :weight-x 0.0
   :weight-y 0.0
   :fill     GridBagConstraints/NONE
   :anchor   GridBagConstraints/WEST
   :ipadx    0
   :ipady    0})

(defn grid-bag-constraints
  [x y global-opts opts]
  (let [{:keys [insets width height weight-x weight-h
                fill anchor ipadx ipady]}
        (merge default-opts global-opts opts)]
    (GridBagConstraints. x y width height weight-x weight-h
                         anchor fill insets ipadx ipady)))

(defn grid-bag-container
  [panel global-opts & rows]
  (doseq [[row-idx row] (map-indexed identity rows)
          [col-idx [target & {:as opts}]] (map-indexed identity row)
          :when target]
    (let [constraints (grid-bag-constraints col-idx row-idx global-opts opts)]
      (.add panel target constraints))))

Использование так же, как и раньше.

1 голос
/ 01 июля 2011

Вот мое решение. Я использую его в приложении Swing, которое я пишу. Это уже сэкономило мне много строк написания кода (для двух разных панелей) и будет таким же быстрым, как рукописный код.

(defmacro grid-bag-container [container & args]
  "Fill and return a java.awt.Container that uses the GridBagLayout.
  The macro defines a set of default constraints for the GridBagConstraints:
    :gridwidth 1
    :gridheight 1
    :weightx 0
    :weighty 0
    :anchor :WEST
    :fill :NONE
    :insets (Insets. 5 5 5 5)
    :ipadx 0
    :ipady 0
  These defaults can be overridden in the call to the macro in two way:
    - If the first argument is a hash-map of constraint names and values
      (e.g.: {:weightx 1}), these will override the defaults for the
      entire container.
    - Each individual item (see below) can override the global defaults
      and container defaults for itself.
  The constraints consist of constraint name (as a keyword with the same
  name as the GridBagConstraints field), and a value, which can also be
  a keyword, in which case the appropriate constant from GridBagConstraints
  will be substituted (e.g.: :NONE == GridBagConstraints.NONE), or the value
  can be an expression (e.g.: 0 or (Insets. 2 2 2 2)).
  Following the optional container default overrides hash-map are one or
  more row specification vectors. Each vector represents one row and
  increments gridy (starting from 0). Each vector contains one or more
  item vectors representing the individual components to be added to the
  container. Each item vector has the component as its first value,
  followed by zero or more constraint overrides as keyword-value pairs.
  (e.g.: [myButton :gridwidth 2 :weightx 1]). The values may be keywords
  and are expanded to GridBagConstraints constants as described above.
  Each item vector gets the next value of gridx (starting with 0) in that
  row.
  For example:
    (grid-bag-container panel
      {:insets (Insets. 1 1 1 1)}
      [[button :gridwidth 2 :weightx 1.0 :fill :HORIZONTAL]]
      [[check-box :gridwidth 2 :weightx 1.0 :anchor :CENTER]]
      [[arrive-label] [arrive-text-field :fill :HORIZONTAL]]
      [[depart-label] [depart-text-field :fill :HORIZONTAL]])
  will expand to the hand-written equivalent:
    (doto panel
      (.add button
        (GridBagConstraints. 0 0 2 1 1.0 0  ; gridx: 0 gridy: 1
                             GridBagConstraints/WEST
                             GridBagConstraints/HORIZONTAL
                             (Insets. 1 1 1 1) 0 0))
      (.add check-box
        (GridBagConstraints. 0 1 2 1 1.0 0  ; gridx: 0 gridy: 1
                             GridBagConstraints/CENTER
                             GridBagConstraints/NONE
                             (Insets. 1 1 1 1) 0 0))
      (.add arrive-label
        (GridBagConstraints. 0 2 1 1 0 0    ; gridx: 0 gridy: 2
                             GridBagConstraints/WEST
                             GridBagConstraints/NONE
                             (Insets. 1 1 1 1) 0 0))
      (.add arrive-text-field
        (GridBagConstraints. 1 2 1 1 0 0    ; gridx: 1 gridy: 2
                             GridBagConstraints/WEST
                             GridBagConstraints/HORIZONTAL
                             (Insets. 1 1 1 1) 0 0))
      (.add depart-label
        (GridBagConstraints. 0 3 1 1 0 0    ; gridx: 0 gridy: 3
                             GridBagConstraints/WEST
                             GridBagConstraints/NONE
                             (Insets. 1 1 1 1) 0 0))
      (.add depart-text-field
        (GridBagConstraints. 1 3 1 1 0 0    ; gridx: 1 gridy: 3
                             GridBagConstraints/WEST
                             GridBagConstraints/HORIZONTAL
                             (Insets. 1 1 1 1) 0 0))
  @param container the java.awt.Container to fill
  @param args the components and GridBagContraints speicifcations
  @returns the filled Container"
  (let [global-defaults
        {:gridwidth 1
         :gridheight 1
         :weightx 0
         :weighty 0
         :anchor :WEST
         :fill :NONE
         :insets `(Insets. 5 5 5 5)
         :ipadx 0
         :ipady 0}

        [defaults rows]
        (if (map? (first args))
          [(into global-defaults (first args)) (rest args)]
          [global-defaults args])]
    `(doto ~container
      ~@(loop [gridy 0 rows rows ret []]
        (if (seq rows)
          (recur (inc gridy) (rest rows)
            (into ret
              (let [row (first rows)]
                (loop [gridx 0 row row ret []]
                  (if (seq row)
                    (recur (inc gridx) (rest row)
                      (conj ret
                        (let [item
                              (first row)

                              component
                              (first item)

                              constraints
                              (assoc (merge defaults
                                            (apply hash-map (rest item)))
                                :gridx gridx :gridy gridy)

                              constraint-values
                              (map (fn [value]
                                (if (keyword? value)
                                  `(. GridBagConstraints
                                      ~(symbol (name value)))
                                  `~value))
                                (map constraints
                                  [:gridx :gridy :gridwidth :gridheight
                                   :weightx :weighty :anchor :fill
                                   :insets :ipadx :ipady]))]
                          `(.add ~component (new GridBagConstraints
                                                 ~@constraint-values)))))
                    ret)))))
          ret)))))

Благодарю Амальлой , user100464 и kotarak за помощь.

...