How to use sliders in clojure seesaw - clojure

I'm new to clojure (and even newer to seesaw) but have a lot of Java experience and a fair amount of swing experience.
I'm trying to create a window with some drop-down text boxes and a slider on it. However, I'm having trouble getting all the pieces to be displayed on one window (rather than one at a time) and for some reason the slider isn't being displayed.
I cannot really find a lot of tutorials on this, so maybe I'm missing something obvious.
Here's what I'm trying to do...
(defn window [cuisine-input rating-input location-slider]
(seesaw/frame
:title "Resturant Selector"
:content (cuisine-input rating-input location-slider)
:width 200
:height 50
:on-close :exit))
(defn -main
[& args]
(def cuisine (seesaw/input "Please choose a type of cuisine: "
:choices ["Indian" "Japanese" "Chinese"
"Burgers"]))
(def rating (seesaw/input "Please choose the ideal rating: "
:choices ["1 star" "2 stars" "3 stars" "4 stars"
"5 stars"]))
(def location (seesaw/slider
:value 5 :min 0 :max 20
:minor-tick-spacing 1 :major-tick-spacing 2
:snap-to-ticks? true
:paint-ticks? true :paint-labels? true))
(def main-window (window cuisine rating location))
(seesaw/pack! (window main-window))
(seesaw/show! (window main-window))
)
I've also tried something like this:
(seesaw/frame :title "Resturant Selector" :on-close :exit
:content (:items [
(seesaw/input "Please choose a type of cuisine: "
:choices ["Indian" "Japanese" "Chinese"
"Burgers"])
(seesaw/input "Please choose the ideal rating: "
:choices ["1 star" "2 stars" "3 stars" "4 stars"
"5 stars"])
(seesaw/slider
:value 5 :min 0 :max 20
:minor-tick-spacing 1 :major-tick-spacing 2
:snap-to-ticks? true
:paint-ticks? true :paint-labels? true)]
)
)

seesaw/input creates an input dialog, while you want to create a JComboBox. The wiki has a nice help about how to create widgets and you can find a list of available widgets in the API doc.
To have more than one widget in a frame you need a container.
So for your particular example, you will need something similar to:
(defn window [content]
(seesaw/frame
:title "Resturant Selector"
:content content
:width 200
:height 50
:on-close :close))
(defn -main
[& args]
(let [rating-label (seesaw/label :text "Please choose rating:")
rating (seesaw/combobox :model ["1 star" "2 star"])
location (seesaw/slider
:value 5 :min 0 :max 20
:minor-tick-spacing 1 :major-tick-spacing 2
:snap-to-ticks? true
:paint-ticks? true :paint-labels? true)
main-window (window (seesaw/vertical-panel :items [rating-label rating location]))]
(seesaw/invoke-later
(seesaw/pack! main-window)
(seesaw/show! main-window))))

Related

Cljfx: two equal map's keys

I want to get this behavior: when the button is pressed with user the button's text and the label's text should are changed together.
But the problem is button and label have the equal name of keys for the text properties. And I can't store equal keys in one hash-map.
;;I have two atoms keeps the state of button text and state of label text
(def *button-text (atom
{:text "click me"}))
(def *label-text (atom
{:text "press the button"}))
;;I have the root function which should accepts arguments for button and label props.
But these props have equal names - text - and I can't store two equal keys in one map.
{:fx/type root
:text (:text *button-text)
:text (:text *label-text)}
;;This will cause an error.
This is how I solved this problem. But is too much of the code and out of normal way.
(ns examp.core
(:gen-class)
(:require [cljfx.api :as fx])
(:import [javafx.application Platform]))
(def *button-text (atom
{:text "click me"}))
(def *label-text (atom
{:text "press the button"}))
(def renderer (fx/create-renderer))
(defn root [{:keys [one two]}]
(let [button-text (:text one)
label-text (:text two)]
{:fx/type :stage
:showing true
:title "Window"
:width 250
:height 150
:scene {:fx/type :scene
:root {:fx/type :v-box
:alignment :center
:spacing 10
:children [{:fx/type :label
:text label-text}
{:fx/type :button
:min-width 100
:min-height 50
:text button-text
:on-action (fn [_]
(if (= button-text "click me")
(do
(swap! *button-text assoc :text "clicked")
(swap! *label-text assoc :text "button is pressed")
(renderer
{:fx/type root
:one #*button-text
:two #*label-text}))
(do
(swap! *button-text assoc :text "click me")
(swap! *label-text assoc :text "presse the button")
(renderer
{:fx/type root
:one #*button-text
:two #*label-text}))))}]}}}))
(defn -main [& args]
(Platform/setImplicitExit true)
(renderer {:fx/type root
:one #*button-text
:two #*label-text}))
Before I will show you my attempt, here is the link to the official Cljfx examples repository. These examples should be useful for you, as they show practices for managing app state, event handling and so on.
For this situation, I recommend studying e05_fn_fx_like_state.clj- this is also an example I based my code on:
(ns examp.core
(:require [cljfx.api :as fx])
(:gen-class))
(def *state
(atom {:label-text "Click the button."
:button-text "Click me!"}))
(defn root [{:keys [label-text button-text]}]
{:fx/type :stage
:showing true
:title "Window"
:width 250
:height 150
:scene {:fx/type :scene
:root {:fx/type :v-box
:alignment :center
:spacing 10
:children [{:fx/type :label
:text label-text}
{:fx/type :button
:text button-text
:min-width 100
:min-height 50
:on-action {:key :button-action}}]}}})
(defn map-event-handler [event]
(when (= (:key event) :button-action)
(if (= (:button-text #*state) "Click me!")
(reset! *state {:label-text "The button was clicked!"
:button-text "Clicked!"})
(reset! *state {:label-text "Click the button."
:button-text "Click me!"}))))
(def renderer
(fx/create-renderer
:middleware (fx/wrap-map-desc root)
:opts {:fx.opt/map-event-handler map-event-handler}))
(defn -main [& args]
(fx/mount-renderer *state renderer))

manipulating an atom containing a ref collection in clojure

I have an application that is supposed to book flights for customers within their specified budget. As such I have customer data and available flights data. I then develop the solutions in Clojure as follows.
First, I create a flights atom:
(def flights
(atom []))
I then create a function to initialize flights into an atom containing a collection of refs. Here I pass flights data which is included further down this post.
(defn initialize-flights [initial-flights]
(reset! flights (map ref initial-flights)))
I then process customers through the process-customers function as follows. And this is where it gets really confusing.
(defn process-customers [customers]
(doseq [customer1 (partitionCustomerInput N-THREADS customers)]
(doseq [customer2 customer1]
(swap! flights
(fn [flights_collection]
(if-let [updated-flight (process-customer flights_collection customer2)]
(assoc flights (:id updated-flight) updated-flight)
flights_collection)))))
(reset! finished-processing? true))
Inside process-customers I pass flights-collection to process-customer (notice process-customer is a helper function for process-customers and they are not the same function). Flights-collection at this point is a collection of flight refs process-customer is supposed to search through the list and in case a customer qualifies for a flight therein, it uses the book function to edit the flight. How should I pass flights-collection to process-customer? As it is, process-customer does not search through the flight refs and it does not alter the flight refs either?
Below is the process-customer function followed by its helper functions.
(defn- process-customer [flights customer]
"Try to book a flight from `flights` for `customer`, returning the updated
flight if found, or nil if no suitable flight was found."
(if-let [{:keys [flight price]} (find-flight flights customer)]
(let [updated-flight (book flight price (:seats customer))]
(log "Customer" (:id customer) "booked" (:seats customer)
"seats on flight" (:id updated-flight) "at $" price " (< budget of $"
(:budget customer) ").")
updated-flight)
(do
(log "Customer" (:id customer) "did not find a flight.")
nil)))
(defn filter-pricing-with-n-seats [pricing seats]
"Get `pricing` for which there are at least `seats` empty seats available."
(filter #(>= (second %) seats) pricing))
(defn lowest-available-price [flight seats]
"Returns the lowest price in `flight` for which at least `seats` empty seats
are available, or nil if none found."
(-> (:pricing flight) ; [[price available taken]]
(filter-pricing-with-n-seats seats)
(sort-pricing)
(first) ; [price available taken]
(first))) ; price
(defn- find-flight [flights customer]
"Find a flight in `flights` that is on the route and within the budget of
`customer`. If a flight was found, returns {:flight flight :price price},
else returns nil."
(let [{:keys [_id from to seats budget]}
customer
flights-and-prices
; flights that are on the route and within budget, and their price
(for [f flights
:when (and (= (:from f) from) (= (:to f) to))
:let [lowest-price (lowest-available-price f seats)]
:when (and (some? lowest-price) (<= lowest-price budget))]
{:flight f :price lowest-price})
cheapest-flight-and-price
(first (sort-by :price flights-and-prices))]
cheapest-flight-and-price))
(defn- book [flight price seats]
"Updates `flight` to book `seats` at `price`."
(update flight :pricing
(fn [pricing]
(for [[p a t] pricing]
(if (= p price)
[p (- a seats) (+ t seats)]
[p a t])))))
(def finished-processing?
"Set to true once all customers have been processed, so that sales process
can end."
(atom false))
(defn partitionCustomerInput
[threads customers]
(let [partitions (partition-all
(Math/ceil (/ (count customers) threads)) customers)]
partitions))
Below is the main function. It initializes flights and kickstarts customer procecessing
(defn main []
(initialize-flights input/flights)
(let [f1 (future (time (process-customers input/customers)))
#f1
)
(println "Flights:")
(print-flights (map deref #flights)))
(main)
(shutdown-agents)
Below are the customers and flights collection.
(def flights
[{:id 0
:from "BRU" :to "ATL"
:carrier "Delta"
:pricing [[600 150 0] ; price; # seats available at that price; # seats taken at that price
[650 50 0]
[700 50 0]
[800 50 0]]}
{:id 1
:from "BRU" :to "LON"
:carrier "Brussels Airlines"
:pricing [[300 150 0]
[350 50 0]
[370 20 0]
[380 30 0]]}
{:id 2
:from "BRU" :to "LON"
:carrier "Brussels Airlines"
:pricing [[250 100 0]
[300 50 0]]}
{:id 3
:from "BRU" :to "MAD"
:carrier "Brussels Airlines"
:pricing [[200 150 0]
[250 50 0]
[300 100 0]]}
{:id 4
:from "BRU" :to "MAD"
:carrier "Iberia"
:pricing [[250 150 0]
[300 50 0]]}])
(def customers
[{:id 0 :from "BRU" :to "ATL" :seats 5 :budget 700}
{:id 1 :from "BRU" :to "ATL" :seats 5 :budget 550}
{:id 2 :from "BRU" :to "LON" :seats 6 :budget 270}
{:id 3 :from "BRU" :to "ATL" :seats 4 :budget 600}
{:id 4 :from "BRU" :to "LON" :seats 3 :budget 270}
{:id 5 :from "BRU" :to "LON" :seats 9 :budget 250}
{:id 6 :from "BRU" :to "MAD" :seats 5 :budget 200}
{:id 7 :from "BRU" :to "MAD" :seats 9 :budget 150}
{:id 8 :from "BRU" :to "LON" :seats 5 :budget 250}
{:id 9 :from "BRU" :to "ATL" :seats 4 :budget 500}
{:id 10 :from "BRU" :to "MAD" :seats 1 :budget 180}
{:id 11 :from "BRU" :to "LON" :seats 2 :budget 320}
{:id 12 :from "BRU" :to "ATL" :seats 3 :budget 850}
{:id 13 :from "BRU" :to "ATL" :seats 4 :budget 200}])
Also, note that I want to use refs for this implementation to alter the flights as ref offers support for coordinated read and writes to change the flights atomically. I aim to formulate a highly parallelized solution for this application and conflicts cannot be tolerated.
I think you need a ref instead of an atom at the top level. It seems that you will need to coordinate change to individual flight and change to the list of flight. What if one thread is modifying a flight while another thread removes it from the list? Your process-customer side effects must all be done within a (dosync).
Performance wise, it should be ok, because if you don't modify your list-of-flights ref in transaction, it will not cause other transaction that alters it to retry.
Another reason is because you are breaking a very important rule for swap!. The function passed to swap! must be free of side effects as it can be retried by the STM. Altering a ref is side effect, and may cause difficult to understand bugs.
So I would do something like
(def flights
(ref [(ref {:id "flight-1"})
(ref {:id "flight-2"})]))
;; Run a bunch of these in their own threads...
(doseq [customer partitioned-customers]
(dosync (process-customer customer flights)))
Then you can fine tune process-customer with alter, commute and ensure to maximize concurrency and minimize retries.
Hope this helps and good luck!

Clojure: Update value of record field

I have defined record to store User details and Address Details.
(defrecord User [id name address])
(defrecord Address [id location street city state])
(def usr (User. 1 "Abc"
(Address. 1 "Location 1" "Street" "NY" "US")))
I have updated "name" to "BCD" using the below code
(assoc usr :name "BCD")
Output:
#async_tea_party.core.User{:id 1, :name "BCD", :address #async_tea_party.core.Address{:id 1, :location "Location 1", :street "Street", :city "NY", :state "US"}}
(usr)
OutPut:
#async_tea_party.core.User{:id 1, :name "Abc", :address #async_tea_party.core.Address{:id 1, :location "Location 1", :street "Street", :city "NY", :state "US"}}
New value of name field has not updated and It still shows old value.
How can I update "name" field permanently in "User" record?
(def usr (User...)) is kind of immutable. You cannot change it.
When you do (assoc usr :name "BCD") you are not changing it. You create a new one. In order to do what you want you need an atom.
(def usr (atom (User. 1 "Abc"
(Address. 1 "Location 1" "Street" "NY" "US"))))
(:name #usr) ;; "Abc"
(swap! usr assoc :name "BCD")
(:name #usr) ;; "BCD"
This is called immutability and is one of the main reasons for me to like clojure so much.
To understand the reasoning why this behaviour is so beneficial, reading values and state really helped me

Reagent-Forms radio buttons displaying as text fields

I am trying to display a group of radio buttons in a reagent/cljs app. I have followed the same process from http://yogthos.github.io/reagent-forms-example.html but the radio buttons I am displaying are showing up as textfield input boxes.
(def ^:private options (atom nil))
(defn set-options []
(reset! options
[{:name "label name"}
{:name "label name"}
{:name "label name"}]))
(defn set-radio-buttons []
(set-options)
(for [option #options]
[:div.radio
[:label
[:input {:field :radio}]
(option :name)]]))
(defn response-box []
[:div#response
(set-radio-buttons)])
I am then placing response-box in the ui layer of the app.
Thanks
Field is not a correct input element attribute.
[:input {:field :radio}]
(option :name)]]))
Should probably be
[:input {:type :radio}]
(option :name)]]))

In clojure how to refresh the table data

I have a table created using seesaw.swingx and i want to refresh the data in the rows of the table (or even clearing the entire table and giving new data to it will do). How do i achieve this, I know i might have to use table/table-model, but how do i give this table model to my current table?
my table is created as
(swingx/table-x :id :data-table
:horizontal-scroll-enabled? true
:model [:columns [{:key :first-name :text "First Name"}
{:key :last-name :text "Last Name"}]
:rows (get-data)]))
EDIT:
So this is my handler where i want to update my table
(defn- return-movie-handler
[event]
(let [root (seesaw/to-root event)
table (seesaw/select root [:#data-table])]
;some code
(seesaw/replace! root table (get-table-model))))))
and my get-table-model is
(defn- get-table-model
[]
(seesaw.table/table-model :columns [{:key :first-name :text "First Name"}
{:key :last-name :text "Last Name"}]
:rows (get-data)))
doing this i get an exception java.lang.IllegalArgumentException: No implementation of method: :make-widget* of protocol: #'seesaw.make-widget/MakeWidget found for class: seesaw.table.proxy$javax.swing.table.DefaultTableModel
you may use replace! , http://daveray.github.io/seesaw/seesaw.core-api.html#seesaw.core/replace!
(replace! Container Old widget Table Model)
Here is updated code I base on your code. I tested on my local ,works good.
(use 'seesaw.core)
(defn- get-table-model
[a b]
(seesaw.core/table :model [:columns [ :first-name :last-name]
:rows [[ a b]]]))
(def base_frame (frame :title "Base Frame" :content (vertical-panel :items [(border-panel :north (get-table-model "a" "b" ) :id :panel_id)])))
(show! base_frame)
(pack! base_frame)
(replace! (select base_frame [:#panel_id]) (first (select base_frame [:<javax.swing.JTable>])) (get-table-model "C" "D") )
It's a bit late, but you can also use (config! (select root [:#data-table]) :model new-model), having kept the model in an atom or using a generator function. Much simpler than (replace!) imo.