;;; Java Swing examples from the book "Complete Java 2 Certification"
;;; Compile the file and call (swing-test/swing-tests).

(ns swing-test
  (:import (javax.swing AbstractAction ButtonGroup JButton JCheckBox
                        JCheckBoxMenuItem JComboBox JFrame JLabel JMenu
                        JMenuBar JMenuItem JOptionPane JPanel JRadioButton
                        JRadioButtonMenuItem JScrollBar JScrollPane JSplitPane
                        JTable JTextArea JTextField JTree)
           (javax.swing.table AbstractTableModel)
           (javax.swing.tree DefaultMutableTreeNode DefaultTreeModel)
           (java.awt BorderLayout Color FlowLayout GridLayout)
           (java.awt.event ActionListener AdjustmentListener KeyListener)))

;;; This is a map that stores the functions with their (string) names
(def *swing-tests* (ref {}))

;;; The main function that creates buttons for all sample programs
;;; WARNING: this exits Clojure when you close the window
(defn swing-tests []
  (let [frame (new JFrame "Swing Tests")
        cont (. frame getContentPane)]
    (. cont setLayout (new FlowLayout))
    (doseq [name test] (deref *swing-tests*)
      (let [button (new JButton name)]
        (. cont add button)
        (. button addActionListener
           (proxy [ActionListener] []
             (actionPerformed [e]
               (test))))))
    (doto frame
      (setDefaultCloseOperation (. JFrame EXIT_ON_CLOSE))
      (setSize 350 250)
      (setVisible true))))

;;; A macro that stores the function and its name in *swing-tests*
(defmacro defswingtest [str & body]
  (let [name (gensym "swing-test-")]
    `(do (defn ~name [] ~@body)
         (dosync (commute *swing-tests* assoc ~str ~name)))))

(defswingtest "Frame"
  (let [frame (new JFrame "Frame Demo")]
    (doto frame
      (setSize 350 250)
      (setVisible true))))

(defswingtest "Content Pane"
  (let [frame (new JFrame "Content Pane Demo")
        cont (. frame getContentPane)]
    (. cont setLayout (new FlowLayout))
    (doseq i (range 1 4) (. cont add (new JButton (str "Button #" i))))
    (doto frame
      (setSize 350 250)
      (setVisible true))))

(defswingtest "Panel"
  (let [frame (new JFrame "Content Pane Demo")
        cont (. frame getContentPane)]
    (. cont setLayout (new GridLayout 2 1))
    (dotimes i 2
      (let [pan (new JPanel)]
        (. pan setBackground
           (if (= i 0) (. Color lightGray) (. Color white)))
        (dotimes _ 3 (. pan add (new JButton "Button")))
        (. cont add pan)))
    (doto frame
      (setSize 350 250)
      (setVisible true))))

(defswingtest "Label"
  (let [frame (new JFrame "Label Demo")
        cont (. frame getContentPane)]
    (doto cont
      (setLayout (new FlowLayout))
      (add (new JLabel "This is a JLabel")))
    (doto frame
      (setSize 350 250)
      (setVisible true))))

(defswingtest "Button"
  (let [frame (new JFrame "Button Demo")
        cont (. frame getContentPane)
        hello (new JButton "Hello")
        bye (new JButton "Goodbye")
        listener (proxy [ActionListener] []
                   (actionPerformed [e]
                     (if (= (. e getSource) hello)
                       (println "Hello")
                       (println "Goodbye"))))]
    (. cont setLayout (new FlowLayout))
    (doseq x [hello bye]
      (. cont add x)
      (. x addActionListener listener))
    (doto frame
      (setSize 350 250)
      (setVisible true))))

(defswingtest "Check Box"
  (let [frame (new JFrame "CheckBox Demo")
        cont (. frame getContentPane)]
    (doto cont
      (setLayout (new FlowLayout))
      (add (new JCheckBox "Charge my acct"))
      (add (new JCheckBox "Gift wrap"))
      (add (new JButton "Submit")))
    (doto frame
      (setSize 350 250)
      (setVisible true))))

(defswingtest "Radio Button"
  (let [frame (new JFrame "RadioButton Demo")
        cont (. frame getContentPane)
        group (new ButtonGroup)]
    (. cont setLayout (new FlowLayout))
    (doseq [str x] [["Rare" true] ["Medium"] ["Well Done"]]
      (let [btn (new JRadioButton str x)]
        (. group add btn)
        (. cont add btn)))
    (doto frame
      (setSize 350 250)
      (setVisible true))))

(defswingtest "Scroll Bar"
  (let [frame (new JFrame "Scroll Bar Demo")
        cont (. frame getContentPane)
        sbar (new JScrollBar (. JScrollBar HORIZONTAL))]
    (. cont add sbar (. BorderLayout NORTH))
    (. sbar addAdjustmentListener
       (proxy [AdjustmentListener] []
         (adjustmentValueChanged [e]
           (println (str "Val = " (. e getValue))))))
    (doto frame
      (setSize 350 250)
      (setVisible true))))

(defswingtest "Text"
  (let [frame (new JFrame "TextDemo")
        cont (. frame getContentPane)
        field (new JTextField "Type here")
        area (new JTextArea)]
    (doto cont
      (add field (. BorderLayout NORTH))
      (add area (. BorderLayout CENTER)))
    (doto field
      (addActionListener
       (proxy [ActionListener] []
         (actionPerformed [e]
           (. area append (str "ACTION: " (. field getText) "\n")))))
      (addKeyListener
       (proxy [KeyListener] []
         (keyPressed [e])
         (keyReleased [e])
         (keyTyped [e]
           (. area append (str "KEY: " (. e getKeyChar) "\n"))))))
    (doto frame
      (setSize 350 250)
      (setVisible true))))

(defswingtest "Combo Box"
  (let [frame (new JFrame "ComboDemo")
        cont (. frame getContentPane)
        combo (new JComboBox (to-array ["Dragon" "Ghost" "Unicorn"]))]
    (. combo setEditable true)
    (doto cont
      (setLayout (new FlowLayout))
      (add combo))
    (doto frame
      (setSize 350 250)
      (setVisible true))))

(defswingtest "Menu"
  (let [frame (new JFrame "Menu")
        menubar (new JMenuBar)
        file-menu (new JMenu "File")
        sample-menu (new JMenu "Sample")
        submenu (new JMenu "SubOptions")
        group (new ButtonGroup)]
    (doseq title ["New" "Exit"]
      (. file-menu add (new JMenuItem title)))
    (doto sample-menu
      (add (new JMenuItem "Plain"))
      (insertSeparator 1)
      (add (new JCheckBoxMenuItem "Check")))
    (dotimes i 2
      (let [radio (new JRadioButtonMenuItem (str "Radio" i))]
        (. group add radio)
        (. sample-menu add radio)))
    (doseq title ["AAA" "BBB" "CCC"]
      (. submenu (add (new JMenuItem title))))
    (. sample-menu add submenu)
    (doto menubar
      (add file-menu)
      (add sample-menu))
    (doto frame
      (setJMenuBar menubar)
      (setSize 350 250)
      (setVisible true))))

(defswingtest "Table"
  (let [frame (new JFrame)
        strings ["January" "February" "March" "April" "May" "June" "July"
                 "August" "September" "October" "November" "December"]
        model (proxy [AbstractTableModel] []
                (getRowCount [] (count strings))
                (getColumnCount [] 2)
                (getValueAt [row col]
                  (if (= col 0)
                    (nth strings row)
                    (count (nth strings row))))
                (getColumnName [col] (if (= col 0) "String" "Length")))
        table (new JTable model)]
    (.. frame getContentPane (add (new JScrollPane table)))
    (doto frame
      (setSize 500 200)
      (setVisible true))))

(def *sample-tree*
     '("Trouble Fields"
       ("Location" "Boston" "Chicago" "New York" "Parkersburg")
       ("Reporter" "Padula" "Hunter" "Gant" "Anonymous")
       ("Engineer" "Wort" "Brown" "Carrigan" "None")
       ("Category" "Network" "Office" "Workstation")
       ("No Troubles" ("In" ("Lisp" "Creating" "Highly" "Nested" "Lists")))))

;;; Notice how much more elegant is this in Lisp...

(defn build-sample-tree-rec [node data]
  (doseq element data
    (let [element-list (if (list? element) element (list element))
          new-node (new DefaultMutableTreeNode (first element-list))]
      (. node add new-node)
      (build-sample-tree-rec new-node (rest element-list)))))

(defn build-sample-tree [tree]
  (let [root (new DefaultMutableTreeNode (first tree))]
    (build-sample-tree-rec root (rest tree))
    (new DefaultTreeModel root false)))

(defswingtest "Tree"
  (let [frame (new JFrame "Sample Tree")
        model (build-sample-tree *sample-tree*)
        tree (new JTree model)]
    (.. frame getContentPane (add (new JScrollPane tree)))
    (doto frame
      (setSize 200 200)
      (setVisible true))))

(defswingtest "Action"
  (let [frame (new JFrame "Action Test")
        menubar (new JMenuBar)
        menu (new JMenu "Sample")
        message "Some argument."]
    (. menu add (proxy [AbstractAction] [message]
                  (actionPerformed [e] (println message))))
    (. menubar add menu)
    (doto frame
      (setJMenuBar menubar)
      (setSize 100 100)
      (setVisible true))))

;;; Parts taken from previous examples
(defswingtest "Split Pane / Option Pane"
  (let [frame (new JFrame "SplitPane Test")
        strings ["January" "February" "March" "April" "May" "June" "July"
                 "August" "September" "October" "November" "December"]
        model (proxy [AbstractTableModel] []
                (getRowCount [] (count strings))
                (getColumnCount [] 2)
                (getValueAt [row col]
                  (if (= col 0)
                    (nth strings row)
                    (count (nth strings row))))
                (getColumnName [col] (if (= col 0) "String" "Length")))
        table (new JTable model)
        tree (new JTree (build-sample-tree *sample-tree*))
        message "PROTOTYPE"
        menubar (new JMenuBar)
        menu (new JMenu "Sample")
        split (new JSplitPane (. JSplitPane HORIZONTAL_SPLIT) tree table)]
    (.. JOptionPane (showMessageDialog nil "Tree created" "Program note"
                                       (. JOptionPane INFORMATION_MESSAGE)))
    (when (= (.. JOptionPane
                 (showConfirmDialog nil "Load the GUI?" "Roll Call!"
                                    (. JOptionPane YES_NO_CANCEL_OPTION)
                                    (. JOptionPane QUESTION_MESSAGE)))
             (. JOptionPane YES_OPTION))
      (. menu add (proxy [AbstractAction] [message]
                  (actionPerformed [e] (println message))))
      (. menubar add menu)
      (.. frame getContentPane (add split (. BorderLayout WEST)))
      (doto frame
        (setJMenuBar menubar)
        (setSize 300 200)
        (setVisible true)))))