-
Notifications
You must be signed in to change notification settings - Fork 427
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #106 from Davideddu/filechooser
Add file chooser facade and support for Linux and Windows
- Loading branch information
Showing
6 changed files
with
486 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
''' | ||
Linux file chooser | ||
------------------ | ||
''' | ||
|
||
from plyer.facades import FileChooser | ||
from distutils.spawn import find_executable as which | ||
import os | ||
import subprocess as sp | ||
import time | ||
|
||
|
||
class SubprocessFileChooser(object): | ||
"""A file chooser implementation that allows using | ||
subprocess back-ends. | ||
Normally you only need to override _gen_cmdline, executable, | ||
separator and successretcode. | ||
""" | ||
|
||
executable = "" | ||
"""The name of the executable of the back-end. | ||
""" | ||
|
||
separator = "|" | ||
"""The separator used by the back-end. Override this for automatic | ||
splitting, or override _split_output. | ||
""" | ||
|
||
successretcode = 0 | ||
"""The return code which is returned when the user doesn't close the | ||
dialog without choosing anything, or when the app doesn't crash. | ||
""" | ||
|
||
path = None | ||
multiple = False | ||
filters = [] | ||
preview = False | ||
title = None | ||
icon = None | ||
show_hidden = False | ||
|
||
def __init__(self, **kwargs): | ||
# Simulate Kivy's behavior | ||
for i in kwargs: | ||
setattr(self, i, kwargs[i]) | ||
|
||
_process = None | ||
|
||
def _run_command(self, cmd): | ||
self._process = sp.Popen(cmd, stdout=sp.PIPE) | ||
while True: | ||
ret = self._process.poll() | ||
if ret != None: | ||
if ret == self.successretcode: | ||
out = self._process.communicate()[0].strip() | ||
self.selection = self._split_output(out) | ||
return self.selection | ||
else: | ||
return None | ||
time.sleep(0.1) | ||
|
||
def _split_output(self, out): | ||
"""This methods receives the output of the back-end and turns | ||
it into a list of paths. | ||
""" | ||
return out.split(self.separator) | ||
|
||
def _gen_cmdline(self): | ||
"""Returns the command line of the back-end, based on the current | ||
properties. You need to override this. | ||
""" | ||
raise NotImplementedError() | ||
|
||
def run(self): | ||
return self._run_command(self._gen_cmdline()) | ||
|
||
|
||
class ZenityFileChooser(SubprocessFileChooser): | ||
"""A FileChooser implementation using Zenity (on GNU/Linux). | ||
Not implemented features: | ||
* show_hidden | ||
* preview | ||
""" | ||
|
||
executable = "zenity" | ||
separator = "|" | ||
successretcode = 0 | ||
|
||
def _gen_cmdline(self): | ||
cmdline = [which(self.executable), "--file-selection", "--confirm-overwrite"] | ||
if self.multiple: | ||
cmdline += ["--multiple"] | ||
if self.mode == "save": | ||
cmdline += ["--save"] | ||
elif self.mode == "dir": | ||
cmdline += ["--directory"] | ||
if self.path: | ||
cmdline += ["--filename", self.path] | ||
if self.title: | ||
cmdline += ["--name", self.title] | ||
if self.icon: | ||
cmdline += ["--window-icon", self.icon] | ||
for f in self.filters: | ||
if type(f) == str: | ||
cmdline += ["--file-filter", f] | ||
else: | ||
cmdline += ["--file-filter", "{name} | {flt}".format(name=f[0], flt=" ".join(f[1:]))] | ||
return cmdline | ||
|
||
class KDialogFileChooser(SubprocessFileChooser): | ||
"""A FileChooser implementation using KDialog (on GNU/Linux). | ||
Not implemented features: | ||
* show_hidden | ||
* preview | ||
""" | ||
|
||
executable = "kdialog" | ||
separator = "\n" | ||
successretcode = 0 | ||
|
||
def _gen_cmdline(self): | ||
cmdline = [which(self.executable)] | ||
|
||
filt = [] | ||
|
||
for f in self.filters: | ||
if type(f) == str: | ||
filt += [f] | ||
else: | ||
filt += list(f[1:]) | ||
|
||
if self.mode == "dir": | ||
cmdline += ["--getexistingdirectory", (self.path if self.path else os.path.expanduser("~"))] | ||
elif self.mode == "save": | ||
cmdline += ["--getopenfilename", (self.path if self.path else os.path.expanduser("~")), " ".join(filt)] | ||
else: | ||
cmdline += ["--getopenfilename", (self.path if self.path else os.path.expanduser("~")), " ".join(filt)] | ||
if self.multiple: | ||
cmdline += ["--multiple", "--separate-output"] | ||
if self.title: | ||
cmdline += ["--title", self.title] | ||
if self.icon: | ||
cmdline += ["--icon", self.icon] | ||
return cmdline | ||
|
||
class YADFileChooser(SubprocessFileChooser): | ||
"""A NativeFileChooser implementation using YAD (on GNU/Linux). | ||
Not implemented features: | ||
* show_hidden | ||
""" | ||
|
||
executable = "yad" | ||
separator = "|?|" | ||
successretcode = 0 | ||
|
||
def _gen_cmdline(self): | ||
cmdline = [which(self.executable), "--file-selection", "--confirm-overwrite", "--geometry", "800x600+150+150"] | ||
if self.multiple: | ||
cmdline += ["--multiple", "--separator", self.separator] | ||
if self.mode == "save": | ||
cmdline += ["--save"] | ||
elif self.mode == "dir": | ||
cmdline += ["--directory"] | ||
if self.preview: | ||
cmdline += ["--add-preview"] | ||
if self.path: | ||
cmdline += ["--filename", self.path] | ||
if self.title: | ||
cmdline += ["--name", self.title] | ||
if self.icon: | ||
cmdline += ["--window-icon", self.icon] | ||
for f in self.filters: | ||
if type(f) == str: | ||
cmdline += ["--file-filter", f] | ||
else: | ||
cmdline += ["--file-filter", "{name} | {flt}".format(name=f[0], flt=" ".join(f[1:]))] | ||
return cmdline | ||
|
||
CHOOSERS = {"gnome": ZenityFileChooser, | ||
"kde": KDialogFileChooser, | ||
"yad": YADFileChooser} | ||
|
||
class LinuxFileChooser(FileChooser): | ||
"""FileChooser implementation for GNu/Linux. Accepts one additional | ||
keyword argument, *desktop_override*, which, if set, overrides the | ||
back-end that will be used. Set it to "gnome" for Zenity, to "kde" | ||
for KDialog and to "yad" for YAD (Yet Another Dialog). | ||
If set to None or not set, a default one will be picked based on | ||
the running desktop environment and installed back-ends. | ||
""" | ||
|
||
desktop = None | ||
if str(os.environ.get("XDG_CURRENT_DESKTOP")).lower() == "kde" and which("kdialog"): | ||
desktop = "kde" | ||
elif which("yad"): | ||
desktop = "yad" | ||
elif which("zenity"): | ||
desktop = "gnome" | ||
|
||
def _file_selection_dialog(self, desktop_override=desktop, **kwargs): | ||
if not desktop_override: | ||
desktop_override = desktop | ||
# This means we couldn't find any back-end | ||
if not desktop_override: | ||
raise OSError("No back-end available. Please install one.") | ||
|
||
chooser = CHOOSERS[desktop_override] | ||
c = chooser(**kwargs) | ||
return c.run() | ||
|
||
|
||
def instance(): | ||
return LinuxFileChooser() |
Oops, something went wrong.