clj_twitter_feelings/src/clj_twitter_feelings/ui.clj

226 lines
9.4 KiB
Clojure

(ns clj-twitter-feelings.ui
(:import [java.awt Dimension Color Font]
[java.awt.event KeyEvent KeyAdapter]
[javax.imageio ImageIO]
[javax.swing JPanel JFrame JLabel JDialog JTextField JPasswordField
JButton JOptionPane
Timer WindowConstants UIManager]
[org.jfree.chart ChartFactory ChartPanel]
[org.jfree.chart.plot PiePlot]
[org.jfree.chart.labels StandardPieSectionLabelGenerator]
[org.jfree.data.general DefaultPieDataset]
[org.jfree.data.time Millisecond TimeSeries TimeSeriesCollection]
[org.jfree.ui RefineryUtilities])
(:use [clj-twitter-feelings.core]
[clojure.java.io :only (resource)]
[clojure.contrib
[miglayout :only (miglayout)]
[swing-utils :only (add-action-listener add-key-typed-listener
do-swing)]])
(:gen-class))
(JFrame/setDefaultLookAndFeelDecorated true)
(JDialog/setDefaultLookAndFeelDecorated true)
(UIManager/setLookAndFeel
"org.jvnet.substance.skin.SubstanceModerateLookAndFeel")
(let [message-type {
:error JOptionPane/ERROR_MESSAGE
:info JOptionPane/INFORMATION_MESSAGE
:warn JOptionPane/WARNING_MESSAGE
:question JOptionPane/QUESTION_MESSAGE
:plain JOptionPane/PLAIN_MESSAGE
}]
(defn show-message [frame message title type]
(JOptionPane/showMessageDialog frame message title (type message-type))))
(defn exit-app [^JFrame frame]
(doto frame (.setVisible false) (.dispose))
(System/exit 0))
(defn create-auth-input-dialog
"Creates a JDialog to take the input of username and password from the user.
Returns the dialog.
Arguments are:
parent: the parent frame
dialog-title: the title of the dialog
dialog-message: the message shown in the dialog
dialog-width : the width of the dialog
dialog-height: the height of the dialog
username-lbl-text: the text shown on the username label
password-lbl-text: the text shown on the password label
input-field-size: the size of the username and password input fields
ok-btn-text: the text shown on the ok button
cancel-btn-text: the text shown on the cancel button
validation-fn: a function which is called to validate the user input when
the user presses the ok button.
the function is called with arguments: username, password, this dialog.
if the function return a string, it is shown on the dialog as the error
message and the dialog remains visible. otherwise ok-fn is called.
ok-fn: a function which is called when the user presses ok button and the
input is valid as per the call to validation-fn.
the function is called with arguments: username, password, this dialog.
the dialog is hidden before the call.
cancel-fn: a function which is called when the user presses cancel button.
the function is called with arguments: this dialog.
the dialog is hidden before the call.
"
[^JFrame parent
^String dialog-title ^String dialog-message dialog-width dialog-height
^String username-lbl-text ^String password-lbl-text input-field-size
^String ok-btn-text ^String cancel-btn-text
validation-fn ok-fn cancel-fn]
(let [username-input (JTextField. (int input-field-size))
password-input (JPasswordField. (int input-field-size))
validation-msg-lbl (JLabel. " ")
ok-btn (JButton. ok-btn-text)
cancel-btn (JButton. cancel-btn-text)
dialog (JDialog. parent dialog-title true)]
(doseq [^JTextField in [username-input password-input]]
(.addKeyListener in
(proxy [KeyAdapter] []
(keyTyped [^KeyEvent e]
(when (= (.getKeyChar e) \newline)
(.doClick ok-btn)))
(keyPressed [^KeyEvent e]
(when (= (.getKeyCode e) KeyEvent/VK_ESCAPE)
(.doClick cancel-btn))))))
(doto dialog
(.setDefaultCloseOperation JDialog/DO_NOTHING_ON_CLOSE)
(.setContentPane
(miglayout (JPanel.)
:layout {:wrap 2}
(JLabel. dialog-message) {:span 2}
(JLabel. username-lbl-text) username-input
(JLabel. password-lbl-text) password-input
validation-msg-lbl {:span 2 :align "center"}
(miglayout (JPanel.)
(doto ok-btn
(add-action-listener
(fn [e]
(let [username (.getText username-input)
password (.getText password-input)]
(if-let [validation-msg
(validation-fn username password dialog)]
(.setText validation-msg-lbl validation-msg)
(do (.setText validation-msg-lbl " ")
(.setVisible dialog false)
(ok-fn username password dialog)))))))
(doto cancel-btn
(add-action-listener
(fn [e]
(.setVisible dialog false)
(cancel-fn dialog)))))
{:span 2 :align "center"}))
(.setSize dialog-width dialog-height))))
(defn init-gui [adjective-map]
(let [frame (JFrame. "Twitter Feelings")
^DefaultPieDataset pie-dataset
(reduce #(do (.setValue ^DefaultPieDataset %1 ^String %2 0) %1)
(DefaultPieDataset.) (sort (keys @adjective-type-count)))
pie-chart (ChartFactory/createPieChart
"Distribution" pie-dataset true false false)
pie-chart-panel (doto (ChartPanel. pie-chart)
(.setPreferredSize (Dimension. 500 400)))
time-series-map
(into (sorted-map)
(map #(vector % (TimeSeries. % Millisecond))
(keys @adjective-type-count)))
time-series-dataset
(reduce #(do (.addSeries ^TimeSeriesCollection %1 %2) %1)
(TimeSeriesCollection.) (vals time-series-map))
time-series-chart
(ChartFactory/createTimeSeriesChart
"History" "Time" "Percentage" time-series-dataset
true false false)
time-series-chart-panel
(doto (ChartPanel. time-series-chart)
(.setPreferredSize (Dimension. 550 400)))
adjective-lbl (JLabel. "<html><h2>...</h2></html>")
status-lbl (doto (JLabel. " ")
(.setFont (Font/getFont "Arial Unicode MS")))
timer (doto (Timer. 1000 nil)
(add-action-listener
(fn [e]
(let [a-count @adjective-type-count
total (reduce + 0 (vals a-count))]
(doseq [[^String k v] a-count]
(.setValue pie-dataset k (double v))
(.add ^TimeSeries (time-series-map k)
(Millisecond.) (* 100 (safe-divide v total))))))))
^JDialog auth-input-dialog
(create-auth-input-dialog frame
"Credentials" "Input your Twitter credentials" 220 150
"Screen Name" "Password" 20
"OK" "Cancel"
(fn [uname pass dialog]
(when (or (empty? uname) (empty? pass))
(str "Please input Screen Name and Password")))
(fn [uname pass ^JDialog dialog]
(future
(try
(process-tweet-stream
(tweet-stream (twitter-stream-client uname pass) "sample")
[(adjective-processor adjective-map) (status-processor)])
(catch Exception e
(do-swing
(show-message frame
(str "Error happened: " (.getMessage e)
".\nPlease restart.")
"Error" :error)
(.setVisible dialog true)))))
(.start timer))
(fn [dialog] (exit-app frame)))]
(add-watch adjective-seen :adjective-lbl
(fn [_ _ _ n]
(do-swing
(.setText adjective-lbl (str "<html><h2>" n "</h2></html>")))))
(add-watch status-seen :status-lbl
(fn [_ _ _ n] (do-swing (.setText status-lbl n))))
(doto ^PiePlot (.getPlot pie-chart)
(.setNoDataMessage "No data available")
(.setLabelGenerator (StandardPieSectionLabelGenerator. "{0} {2}"))
(.setBackgroundPaint (Color. 238 238 238)))
(doto (.getXYPlot time-series-chart)
(.setBackgroundPaint (Color. 238 238 238)))
(doto (.. time-series-chart getXYPlot getDomainAxis)
(.setAutoRange true)
(.setFixedAutoRange 120000.0))
(doto (.. time-series-chart getXYPlot getRangeAxis)
(.setRange 0.0 100.0))
(doto frame
(.setIconImage
(ImageIO/read (resource "clj_twitter_adjectives/favicon.jpg")))
(.setDefaultCloseOperation WindowConstants/EXIT_ON_CLOSE)
(.setResizable false)
(.setContentPane
(miglayout (JPanel.)
:layout [:wrap 1]
(miglayout (JPanel.)
:layout [:wrap 2]
(JLabel. "<html><h1>How is Twitter feeling now?</h1></html>")
{:align "left"}
adjective-lbl {:align "right"}
pie-chart-panel time-series-chart-panel)
status-lbl {:align "left"}))
(.pack)
(.setVisible true)
(RefineryUtilities/centerFrameOnScreen))
(doto auth-input-dialog
(RefineryUtilities/centerFrameOnScreen)
(.setVisible true))))
(defn -main [& args]
(let [adjective-map (adjectives)]
(reset! adjective-type-count
(zipmap (distinct (vals adjective-map)) (repeat 0)))
(do-swing (init-gui adjective-map))))