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

File dialog locations #1108

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
9ec1249
file dialog: use ./ as start dir by default
charlesdaniels Jun 15, 2020
d0e337a
file dialog: Add ShowFile*At methods
charlesdaniels Jun 15, 2020
c5561ca
fixed linter errors
charlesdaniels Jun 15, 2020
db857e1
file dialog: break out effectiveStartingDir
charlesdaniels Jun 15, 2020
6fbdb2f
add tests for effectiveStartingDir()
charlesdaniels Jun 15, 2020
be9c018
break out just effectiveStartingDir
charlesdaniels Jun 16, 2020
a20d4ed
Merge branch 'master' into file_dialog_locations
charlesdaniels Jun 16, 2020
ec1f970
Merge branch 'develop' into file_dialog_locations
charlesdaniels Sep 4, 2020
7794089
make public API use ListableURI
charlesdaniels Sep 4, 2020
7ee7b21
fix conditional check for ./
charlesdaniels Sep 4, 2020
7a4a4a5
Migrate file dialog to use URIs throughout
charlesdaniels Sep 8, 2020
78c6984
feedback for #1108
charlesdaniels Sep 13, 2020
689a256
Merge branch 'develop' into file_dialog_locations
charlesdaniels Sep 13, 2020
e8a2269
try to fix WIndows build failures
charlesdaniels Sep 13, 2020
59c5fb7
try again to fix Windows build failure
charlesdaniels Sep 13, 2020
de97723
try to implement Windows path support
charlesdaniels Sep 17, 2020
bc607aa
Merge branch 'develop' into file_dialog_locations
charlesdaniels Sep 17, 2020
4d39cfd
fix staticcheck errors
charlesdaniels Sep 17, 2020
d46f9ff
try to fix windows paths
charlesdaniels Sep 17, 2020
210fe34
Fix some issues related to ListableURI and dialog
PucklaJ Sep 21, 2020
2916628
Replace fws with bws in setDirectory for windows
PucklaJ Sep 21, 2020
9c055c7
Fix save and open dialog tests
PucklaJ Sep 21, 2020
f3e0660
Merge pull request #1 from PucklaMotzer09/file_dialog_locations_fixes
charlesdaniels Sep 21, 2020
347e49a
Replace backslashes in NewURI
PucklaJ Sep 25, 2020
5681882
Add tests for parentGeneric
PucklaJ Sep 25, 2020
60a46e4
Improve tests and always end Parent with slash
PucklaJ Sep 25, 2020
b7a6dd8
Don't return as soon as one fails in loadFavorites
PucklaJ Sep 25, 2020
20cbea5
Update breadcrumbs creation and add test for them
PucklaJ Sep 25, 2020
dc79d37
Remove checking for "file" scheme in setDirectory
PucklaJ Sep 25, 2020
d5ce136
Fix staticcheck
PucklaJ Sep 25, 2020
ac81a66
Merge pull request #2 from PucklaMotzer09/file_dialog_locations
charlesdaniels Oct 6, 2020
45233fe
revert starting dir to ~, remove OpenAt functions
charlesdaniels Oct 8, 2020
ae3b91f
re-enable overwrite callback
charlesdaniels Oct 8, 2020
5b5c3a3
update file dialog tests for ~ starting dir
charlesdaniels Oct 8, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
230 changes: 180 additions & 50 deletions dialog/file.go
@@ -1,7 +1,7 @@
package dialog

import (
"io/ioutil"
"fmt"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -29,7 +29,7 @@ type fileDialog struct {

win *widget.PopUp
selected *fileDialogItem
dir string
dir fyne.ListableURI
}

// FileDialog is a dialog containing a file picker for use in opening or saving files.
Expand All @@ -41,6 +41,10 @@ type FileDialog struct {
parent fyne.Window
dialog *fileDialog
dismissText string

// StartingLocation allows overriding the default location where the
// file dialog should "view" when it is first opened.
StartingLocation fyne.ListableURI
}

// Declare conformity to Dialog interface
Expand Down Expand Up @@ -77,17 +81,21 @@ func (f *fileDialog) makeUI() fyne.CanvasObject {
if f.file.save {
callback := f.file.callback.(func(fyne.URIWriteCloser, error))
name := f.fileName.(*widget.Entry).Text
path := filepath.Join(f.dir, name)
path, _ := storage.Child(f.dir, name)

exists, _ := storage.Exists(path)

info, err := os.Stat(path)
if os.IsNotExist(err) {
_, err := storage.ListerForURI(path)

if !exists {
f.win.Hide()
if f.file.onClosedCallback != nil {
f.file.onClosedCallback(true)
}
callback(storage.SaveFileToURI(storage.NewURI("file://" + path)))
callback(storage.SaveFileToURI(path))
return
} else if info.IsDir() {
} else if err != nil {
// check if the directory exists
ShowInformation("Cannot overwrite",
"Files cannot replace a directory,\ncheck the file name and try again", f.file.parent)
return
Expand All @@ -101,7 +109,7 @@ func (f *fileDialog) makeUI() fyne.CanvasObject {
return
}

callback(storage.SaveFileToURI(storage.NewURI("file://" + path)))
callback(storage.SaveFileToURI(path))
if f.file.onClosedCallback != nil {
f.file.onClosedCallback(true)
}
Expand All @@ -112,7 +120,9 @@ func (f *fileDialog) makeUI() fyne.CanvasObject {
if f.file.onClosedCallback != nil {
f.file.onClosedCallback(true)
}
callback(storage.OpenFileFromURI(storage.NewURI("file://" + f.selected.path)))
path := f.selected.location
// On windows replace '\\' with '/'
callback(storage.OpenFileFromURI(path))
}
})
f.open.Style = widget.PrimaryButton
Expand Down Expand Up @@ -151,54 +161,113 @@ func (f *fileDialog) makeUI() fyne.CanvasObject {
body := fyne.NewContainerWithLayout(layout.NewBorderLayout(scrollBread, nil, nil, nil),
scrollBread, f.fileScroll)
header := widget.NewLabelWithStyle(label+" File", fyne.TextAlignLeading, fyne.TextStyle{Bold: true})
favorites := widget.NewGroup("Favorites", f.loadFavorites()...)
return fyne.NewContainerWithLayout(layout.NewBorderLayout(header, footer, favorites, nil),
favorites, header, footer, body)

favorites, err := f.loadFavorites()
if err != nil {
// only generate the Favorites group if we were able to load
// them successfully
favorites = []fyne.CanvasObject{}
fyne.LogError("Unable to load favorites", err)
}

favoritesGroup := widget.NewGroup("Favorites", favorites...)
return fyne.NewContainerWithLayout(layout.NewBorderLayout(header, footer, favoritesGroup, nil),
favoritesGroup, header, footer, body)

}

func (f *fileDialog) loadFavorites() []fyne.CanvasObject {
home, _ := os.UserHomeDir()
places := []fyne.CanvasObject{
makeFavoriteButton("Home", theme.HomeIcon(), func() {
func (f *fileDialog) loadFavorites() ([]fyne.CanvasObject, error) {
var home fyne.ListableURI
var documents fyne.ListableURI
var downloads fyne.ListableURI
var osHome string
var err, err1 error

osHome, err = os.UserHomeDir()

if err == nil {
home, err1 = storage.ListerForURI(storage.NewURI("file://" + osHome))
if err1 == nil {
var documentsURI fyne.URI
documentsURI, err1 = storage.Child(home, "Documents")
if err1 == nil {
documents, err1 = storage.ListerForURI(documentsURI)
if err1 != nil {
err = err1
}
} else {
err = err1
}
var downloadsURI fyne.URI
downloadsURI, err1 = storage.Child(home, "Downloads")
if err1 == nil {
downloads, err1 = storage.ListerForURI(downloadsURI)
if err1 != nil {
err = err1
}
} else {
err = err1
}
} else {
err = err1
}
}

var places []fyne.CanvasObject

if home != nil {
places = append(places, makeFavoriteButton("Home", theme.HomeIcon(), func() {
f.setDirectory(home)
}),
makeFavoriteButton("Documents", theme.DocumentIcon(), func() {
f.setDirectory(filepath.Join(home, "Documents"))
}),
makeFavoriteButton("Downloads", theme.DownloadIcon(), func() {
f.setDirectory(filepath.Join(home, "Downloads"))
}),
}))
}
if documents != nil {
places = append(places, makeFavoriteButton("Documents", theme.DocumentIcon(), func() {
f.setDirectory(documents)
}))
}
if downloads != nil {
places = append(places, makeFavoriteButton("Downloads", theme.DownloadIcon(), func() {
f.setDirectory(downloads)
}))
}

places = append(places, f.loadPlaces()...)
return places
return places, err
}

func (f *fileDialog) refreshDir(dir string) {
func (f *fileDialog) refreshDir(dir fyne.ListableURI) {
f.files.Objects = nil

files, err := ioutil.ReadDir(dir)
files, err := dir.List()
if err != nil {
fyne.LogError("Unable to read path "+dir, err)
fyne.LogError("Unable to read path "+dir.String(), err)
return
}

var icons []fyne.CanvasObject
parent := filepath.Dir(dir)
if parent != dir {
fi := &fileDialogItem{picker: f, name: "(Parent)", path: filepath.Dir(dir), dir: true}
parent, err := storage.Parent(dir)
if err != nil && err != storage.URIRootError {
fyne.LogError("Unable to get parent of "+dir.String(), err)
return
}
if parent != nil && parent.String() != dir.String() {
fi := &fileDialogItem{picker: f,
name: "(Parent)", location: parent, dir: true}
fi.ExtendBaseWidget(fi)
icons = append(icons, fi)
}
for _, file := range files {
if isHidden(file.Name(), dir) {
if isHidden(file.Name(), dir.Name()) {
continue
}
itemPath := filepath.Join(dir, file.Name())
if file.IsDir() {
icons = append(icons, f.newFileItem(itemPath, true))
} else if f.file.filter == nil || f.file.filter.Matches(storage.NewURI("file://"+itemPath)) {
icons = append(icons, f.newFileItem(itemPath, false))

_, err := storage.ListerForURI(file)
if err == nil {
// URI points to a directory
icons = append(icons, f.newFileItem(file, true))

} else if f.file.filter == nil || f.file.filter.Matches(file) {
icons = append(icons, f.newFileItem(file, false))
}
}

Expand All @@ -208,13 +277,20 @@ func (f *fileDialog) refreshDir(dir string) {
f.fileScroll.Refresh()
}

func (f *fileDialog) setDirectory(dir string) {
func (f *fileDialog) setDirectory(dir fyne.ListableURI) error {
if dir == nil {
return fmt.Errorf("failed to open nil directory")
}

f.setSelected(nil)
f.dir = dir

f.breadcrumb.Children = nil
buildDir := filepath.VolumeName(dir)
for i, d := range strings.Split(dir, string(filepath.Separator)) {

localdir := dir.String()[len(dir.Scheme())+3:]

buildDir := filepath.VolumeName(localdir)
for i, d := range strings.Split(localdir, "/") {
if d == "" {
if i > 0 { // what we get if we split "/"
break
Expand All @@ -228,15 +304,23 @@ func (f *fileDialog) setDirectory(dir string) {
buildDir = d + string(os.PathSeparator)
}

newDir := buildDir
newDir, err := storage.ListerForURI(storage.NewURI("file://" + buildDir))
if err != nil {
return err
}
f.breadcrumb.Append(
widget.NewButton(d, func() {
f.setDirectory(newDir)
err := f.setDirectory(newDir)
if err != nil {
fyne.LogError("Failed to set directory", err)
}
}),
)
}

f.refreshDir(dir)

return nil
}

func (f *fileDialog) setSelected(file *fileDialogItem) {
Expand All @@ -245,39 +329,85 @@ func (f *fileDialog) setSelected(file *fileDialogItem) {
f.selected.Refresh()
}
if file != nil && file.isDirectory() {
f.setDirectory(file.path)
lister, err := storage.ListerForURI(file.location)
if err != nil {
fyne.LogError("Failed to create lister for URI"+file.location.String(), err)
}
f.setDirectory(lister)
return
}
f.selected = file

if file == nil || file.path == "" {
if file == nil || file.location.String()[len(file.location.Scheme())+3:] == "" {
f.fileName.SetText("")
f.open.Disable()
} else {
file.isCurrent = true
f.fileName.SetText(filepath.Base(file.path))
f.fileName.SetText(file.location.Name())
f.open.Enable()
}
}

// effectiveStartingDir calculates the directory at which the file dialog
// should open, based on the values of CWD, home, and any error conditions
// which occur.
// effectiveStartingDir calculates the directory at which the file dialog should
// open, based on the values of StartingDirectory, CWD, home, and any error
// conditions which occur.
//
// Order of precedence is:
//
// * file.StartingDirectory if non-empty, os.Stat()-able, and uses the file://
// URI scheme
// * os.UserHomeDir()
// * os.Getwd()
// * "/" (should be filesystem root on all supported platforms)
func (f *FileDialog) effectiveStartingDir() string {
//
func (f *FileDialog) effectiveStartingDir() fyne.ListableURI {
var startdir fyne.ListableURI = nil

if f.StartingLocation != nil {
startdir = f.StartingLocation
}

if startdir != nil {
if startdir.Scheme() == "file" {
path := startdir.String()[len(startdir.Scheme())+3:]

// the starting directory is set explicitly
if _, err := os.Stat(path); err != nil {
fyne.LogError("Error with StartingLocation", err)
} else {
return startdir
}
}

}

// Try home dir
dir, err := os.UserHomeDir()
if err == nil {
return dir
lister, err := storage.ListerForURI(storage.NewURI("file://" + dir))
if err == nil {
return lister
}
fyne.LogError("Could not create lister for user home dir", err)
}
fyne.LogError("Could not load user home dir", err)

return "/"
// Try to get ./
wd, err := os.Getwd()
if err == nil {
lister, err := storage.ListerForURI(storage.NewURI("file://" + wd))
if err == nil {
return lister
}
fyne.LogError("Could not create lister for working dir", err)
}

lister, err := storage.ListerForURI(storage.NewURI("file:///"))
if err != nil {
fyne.LogError("could not create lister for /", err)
return nil
}
return lister
}

func showFile(file *FileDialog) *fileDialog {
Expand Down
8 changes: 7 additions & 1 deletion dialog/file_other.go
Expand Up @@ -4,12 +4,18 @@ package dialog

import (
"fyne.io/fyne"
"fyne.io/fyne/storage"
"fyne.io/fyne/theme"
)

func (f *fileDialog) loadPlaces() []fyne.CanvasObject {
return []fyne.CanvasObject{makeFavoriteButton("Computer", theme.ComputerIcon(), func() {
f.setDirectory("/")
lister, err := storage.ListerForURI(storage.NewURI("file:///"))
if err != nil {
fyne.LogError("could not create lister for /", err)
return
}
f.setDirectory(lister)
})}
}

Expand Down