From bf695310a65b77c8feda2acfda75ff567439eea3 Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Sun, 4 Dec 2022 09:44:29 +0100 Subject: [PATCH 1/2] WIP - present a method to use native file selector Even if the Fyne file dialogs are OK, it nice to be able to use the native file selector as it proposes some native actions as "create a directory", use bookmarks, etc. Zenity and KDialogs are commonly installed in Linux. This work in progress proposes a way to use native dialogs. NOTE: this could be extended to info dialogs, alerts, and many others. --- cmd/native_dialog/main.go | 27 ++++++++++ dialog/native_dialogs.go | 110 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 cmd/native_dialog/main.go create mode 100644 dialog/native_dialogs.go diff --git a/cmd/native_dialog/main.go b/cmd/native_dialog/main.go new file mode 100644 index 00000000..483fc5ce --- /dev/null +++ b/cmd/native_dialog/main.go @@ -0,0 +1,27 @@ +package main + +import ( + "log" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/app" + "fyne.io/fyne/v2/widget" + "fyne.io/x/fyne/dialog" +) + +func main() { + app := app.New() + w := app.NewWindow("Hello") + + button := widget.NewButton("Click Me", func() { + dialog.NewFileSelector(func(file fyne.URIReadCloser, err error) { + log.Println("File selected", file.URI(), err) + }, w).Open() + + }) + + w.SetContent(button) + + w.ShowAndRun() + +} diff --git a/dialog/native_dialogs.go b/dialog/native_dialogs.go new file mode 100644 index 00000000..28e6c637 --- /dev/null +++ b/dialog/native_dialogs.go @@ -0,0 +1,110 @@ +package dialog + +import ( + "log" + "os" + "os/exec" + "strings" + + "fyne.io/fyne/v2" + fyneDialog "fyne.io/fyne/v2/dialog" + "fyne.io/fyne/v2/storage" + "fyne.io/fyne/v2/storage/repository" +) + +type wm uint8 + +const ( + wm_UNKNOWN wm = iota + wm_GNOME + wm_KDE +) + +type FileSelector struct { + Title string + Filters []string + callback func(fyne.URIReadCloser, error) + parentWindow fyne.Window +} + +func detectWM() wm { + // detect WM + xdgCurrentDesktop := os.Getenv("XDG_CURRENT_DESKTOP") + switch xdgCurrentDesktop { + case "GNOME": + return wm_GNOME + case "KDE": + return wm_KDE + default: + return wm_UNKNOWN + } +} + +func NewFileSelector(callback func(fyne.URIReadCloser, error), parent fyne.Window) *FileSelector { + return &FileSelector{ + Title: "Select a file", + callback: callback, + } +} + +func ShowFileSelector(callback func(fyne.URIReadCloser, error), parent fyne.Window) { + NewFileSelector(callback, parent).Show() +} + +func (f *FileSelector) AddFilter(name string, extensions ...string) { + f.Filters = append(f.Filters, name) +} + +func (f *FileSelector) Show() { + wm := detectWM() + log.Println("WM:", wm) + switch wm { + case wm_GNOME: + // use zenity + //command := exec.Command("zenity", "--file-selection", "--title", f.Title) + command := exec.Command("zenity", "--file-selection", "--title", f.Title, "--file-filter", "All files | *") + out, err := command.Output() + if err != nil { + f.defaultDialog() + return + } + path := string(out) + u, err := f.getReadCloser(path) + if err != nil { + log.Println("Error:", err) + return + } + f.callback(u, nil) + case wm_KDE: + // use kdialog + //command := exec.Command("kdialog", "--getopenfilename", f.Title) + command := exec.Command("kdialog", "--getopenfilename", f.Title, "All files | *") + out, err := command.Output() + if err != nil { + f.defaultDialog() + return + } + path := string(out) + u, err := f.getReadCloser(path) + if err != nil { + log.Println("Error:", err) + return + } + f.callback(u, nil) + + default: + // use native dialog + f.defaultDialog() + } + +} + +func (f *FileSelector) defaultDialog() { + d := fyneDialog.NewFileOpen(f.callback, f.parentWindow) + d.Show() +} + +func (f *FileSelector) getReadCloser(path string) (fyne.URIReadCloser, error) { + uri := repository.NewFileURI(strings.TrimSpace(path)) + return storage.Reader(uri) +} From 7faf051b85258d48a33cbe4b7c650fd3c8d144dc Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Sun, 4 Dec 2022 10:53:55 +0100 Subject: [PATCH 2/2] Try to make it closer to the fyne implementation --- cmd/native_dialog/main.go | 4 +- dialog/native_dialogs.go | 139 ++++++++++++++++++++++++-------------- 2 files changed, 89 insertions(+), 54 deletions(-) diff --git a/cmd/native_dialog/main.go b/cmd/native_dialog/main.go index 483fc5ce..9302f82d 100644 --- a/cmd/native_dialog/main.go +++ b/cmd/native_dialog/main.go @@ -14,9 +14,9 @@ func main() { w := app.NewWindow("Hello") button := widget.NewButton("Click Me", func() { - dialog.NewFileSelector(func(file fyne.URIReadCloser, err error) { + dialog.NewFileOpen(func(file fyne.URIReadCloser, err error) { log.Println("File selected", file.URI(), err) - }, w).Open() + }, w).Show() }) diff --git a/dialog/native_dialogs.go b/dialog/native_dialogs.go index 28e6c637..a47649a4 100644 --- a/dialog/native_dialogs.go +++ b/dialog/native_dialogs.go @@ -20,13 +20,6 @@ const ( wm_KDE ) -type FileSelector struct { - Title string - Filters []string - callback func(fyne.URIReadCloser, error) - parentWindow fyne.Window -} - func detectWM() wm { // detect WM xdgCurrentDesktop := os.Getenv("XDG_CURRENT_DESKTOP") @@ -40,71 +33,113 @@ func detectWM() wm { } } -func NewFileSelector(callback func(fyne.URIReadCloser, error), parent fyne.Window) *FileSelector { - return &FileSelector{ - Title: "Select a file", - callback: callback, +// FileDialog is a file dialog that uses native file dialogs on Linux. +type FileDialog struct { + *fyneDialog.FileDialog + Title string + filter storage.FileFilter + callback interface{} + parentWindow fyne.Window + save bool +} + +// NewFileOpen creates a new file dialog that uses native file dialogs on Linux. +func NewFileOpen(callback func(fyne.URIReadCloser, error), parent fyne.Window) *FileDialog { + return &FileDialog{ + FileDialog: fyneDialog.NewFileOpen(callback, parent), + Title: "Select a file", + callback: callback, + save: false, + } +} + +func NewFileSave(callback func(fyne.URIWriteCloser, error), parent fyne.Window) *FileDialog { + return &FileDialog{ + FileDialog: fyneDialog.NewFileSave(callback, parent), + Title: "Save file", + callback: callback, + save: true, } } -func ShowFileSelector(callback func(fyne.URIReadCloser, error), parent fyne.Window) { - NewFileSelector(callback, parent).Show() +// SaveFileDialog is a save file dialog that uses native file dialogs on Linux. +func ShowFileOpen(callback func(fyne.URIReadCloser, error), parent fyne.Window) { + NewFileOpen(callback, parent).Show() } -func (f *FileSelector) AddFilter(name string, extensions ...string) { - f.Filters = append(f.Filters, name) +// SaveFileDialog is a save file dialog that uses native file dialogs on Linux. At this time, it does not support filters. +func (f *FileDialog) SetFilter(filter storage.FileFilter) { + f.FileDialog.SetFilter(filter) + f.filter = filter } -func (f *FileSelector) Show() { +// Show shows the file dialog. +func (f *FileDialog) Show() { wm := detectWM() log.Println("WM:", wm) switch wm { case wm_GNOME: - // use zenity - //command := exec.Command("zenity", "--file-selection", "--title", f.Title) - command := exec.Command("zenity", "--file-selection", "--title", f.Title, "--file-filter", "All files | *") - out, err := command.Output() - if err != nil { - f.defaultDialog() - return + if u, err := f.zenityFileDialog(); err != nil { + log.Println("zenity error:", err) + f.FileDialog.Show() + } else { + if !f.save { + f.callback.(func(fyne.URIReadCloser, error))(u.(fyne.URIReadCloser), nil) + } else { + f.callback.(func(fyne.URIWriteCloser, error))(u.(fyne.URIWriteCloser), nil) + } } - path := string(out) - u, err := f.getReadCloser(path) - if err != nil { - log.Println("Error:", err) - return - } - f.callback(u, nil) case wm_KDE: - // use kdialog - //command := exec.Command("kdialog", "--getopenfilename", f.Title) - command := exec.Command("kdialog", "--getopenfilename", f.Title, "All files | *") - out, err := command.Output() - if err != nil { - f.defaultDialog() - return - } - path := string(out) - u, err := f.getReadCloser(path) - if err != nil { - log.Println("Error:", err) - return + if u, err := f.kdialogFileDialog(); err != nil { + log.Println("kdialog error:", err) + f.FileDialog.Show() + } else { + if !f.save { + f.callback.(func(fyne.URIReadCloser, error))(u.(fyne.URIReadCloser), nil) + } else { + f.callback.(func(fyne.URIWriteCloser, error))(u.(fyne.URIWriteCloser), nil) + } } - f.callback(u, nil) - default: - // use native dialog - f.defaultDialog() + f.FileDialog.Show() } } -func (f *FileSelector) defaultDialog() { - d := fyneDialog.NewFileOpen(f.callback, f.parentWindow) - d.Show() +func (f *FileDialog) getReadCloser(path string) (fyne.URIReadCloser, error) { + uri := repository.NewFileURI(strings.TrimSpace(path)) + return storage.Reader(uri) } -func (f *FileSelector) getReadCloser(path string) (fyne.URIReadCloser, error) { +func (f *FileDialog) getWriteCloser(path string) (fyne.URIWriteCloser, error) { uri := repository.NewFileURI(strings.TrimSpace(path)) - return storage.Reader(uri) + return storage.Writer(uri) +} + +func (f *FileDialog) zenityFileDialog() (interface{}, error) { + command := exec.Command("zenity", "--file-selection", "--title", f.Title, "--file-filter", "All files | *") + out, err := command.Output() + if err != nil { + return nil, err + } + path := string(out) + + if f.save { + return f.getWriteCloser(path) + } + + return f.getReadCloser(path) +} + +func (f *FileDialog) kdialogFileDialog() (interface{}, error) { + command := exec.Command("kdialog", "--getopenfilename", f.Title, "All files | *") + out, err := command.Output() + if err != nil { + return nil, err + } + path := string(out) + if f.save { + return f.getWriteCloser(path) + } + return f.getReadCloser(path) }