Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add FileChooser #40

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open

Add FileChooser #40

wants to merge 6 commits into from

Conversation

fdeitylink
Copy link
Contributor

@fdeitylink fdeitylink commented Oct 18, 2019

Do not merge this yet!

This PR adds FileChooser to cljfx (along with necessary coercions for FileChooser.ExtensionFilter and File).

This is not yet complete. I have implemented functionality for :title, :initial-directory, :initial-file-name, and :selected-extension-filter. However, the actual file choosing behavior is not implemented yet.

FileChooser is not event-driven in terms of using handlers when the user selects a file. Instead, the three functions it has (showOpenDialog, showOpenMultipleDialog, and showSaveDialog) return File (or List<File>) with the expectation that the dialog will block the rest of the program and the code will handle the selection imperatively. I believe this warrants more discussion for how we should strive to make this more event-driven.

My two ideas are:

  1. Using cljfx.mutator, similar to how the Canvas :draw property works
  2. Adding event-based functionality so users can handle file selection the same way most other events are handled

@vlaaad
Copy link
Contributor

vlaaad commented Oct 18, 2019

Hi! I thought about adding support for File Chooser dialogs, but haven't come up with any ideas, so I'm interested what it will lead to. I used blocking dialogs and although they seem unnatural, I found them convenient and not really limiting. Although I haven't used them in full-cljfx project, so I'm also interested why you want to make them event-driven. Do you have troubles using them as is?

@fdeitylink
Copy link
Contributor Author

fdeitylink commented Oct 20, 2019

It is certainly possible to use the FileChooser class as is, but I would say that using maps for descriptions and events is more convenient and also consistent with other code using the cljfx API.

As an example, one of my programs allows the user to load a new project or reload the last opened project. Here's a snippet of the event handling code:

;; User has opted to load a new project. Use FileChooser so they can select the file.
;; Then trigger the central project loading event, passing in the selected file.
(defmethod event-handler :load-new-project [_]
  @(fx/on-fx-thread
    (let [file (-> (doto (FileChooser.)
                     (.setTitle "Open a project")
                     (.setInitialDirectory (File. "a/file/path")))
                   ;; Could also grab primary stage instance to make this dialog blocking
                   (.showOpenDialog (Stage.)))]
      {:dispatch {:event/type :load-project :file file}})))

;; User has opted to load the previous project
;; Trigger the central project loading event, passing in the previous project file.
(defmethod event-handler :load-last-project [{:keys [fx/context]}]
  {:dispatch {:event/type :load-project :file (fx/sub context :last-project-path)}})

;; Load in the project
(defmethod event-handler :load-project [{:keys [fx/event fx/context file]}]
  ;; ...
)

This code is fine, but I think something more consistent with cljfx style (i.e. map description, coercions, events) would be cleaner. Perhaps something like the following:

(defmethod event-handler :load-new-project [_]
  (fx/on-fx-thread
    (fx/create-component
      {:fx/type :file-chooser
       :title "Open a project"
       :initial-directory "a/file/path"
       :owner-window (Stage.)
       :showing :open-dialog
       ;; Event handler method then receives selected file in the :fx/event key
       :on-open-hidden {:event/type :load-project}})))

In terms of how to implement this functionality, I think adding the following extra properties could work:

  • :showing: One of :{open, open-multiple, save}-dialog or false. Corresponds to which dialog type to open. No dialog is opened if it is false. Set to false when a dialog closes. I managed to do this with mutator/setter.
  • :on-{open, open-multiple, save}-hidden: Event handlers for when the corresponding dialog has been closed.
  • :owner-window: The Window instance to pass to the dialog methods if blocking is desired.

Is there an extension lifecycle or other functionality in cljfx for storing extra props in a component? (i.e. the handlers and owner window) I looked at fx/make-ext-with-propsbut the props still depend on functionality implemented in the original Java class. If there is no feature for that, then I think any solution for this would end up being unwieldy.

Admittedly, counterarguments to adding FileChooser to cljfx would be that the actual window that gets displayed is OS-level and isn't really part of JavaFX nor can JavaFX be used to describe it beyond a few basic settings. It also doesn't persist for very long—only as long as it takes the user to choose a file.

@torkus
Copy link

torkus commented Sep 27, 2020

(the FileChooser$ExtensionFilter logic has been helpful and saved me a bit of time, thank you)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants