552 lines
17 KiB
Python
552 lines
17 KiB
Python
#!/usr/bin/env python
|
|
|
|
"""
|
|
Simple desktop dialogue box support for Python.
|
|
|
|
Copyright (C) 2007, 2009 Paul Boddie <paul@boddie.org.uk>
|
|
|
|
This program is free software; you can redistribute it and/or modify it under
|
|
the terms of the GNU Lesser General Public License as published by the Free
|
|
Software Foundation; either version 3 of the License, or (at your option) any
|
|
later version.
|
|
|
|
This program is distributed in the hope that it will be useful, but WITHOUT
|
|
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
|
details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public License along
|
|
with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
--------
|
|
|
|
Opening Dialogue Boxes (Dialogs)
|
|
--------------------------------
|
|
|
|
To open a dialogue box (dialog) in the current desktop environment, relying on
|
|
the automatic detection of that environment, use the appropriate dialogue box
|
|
class:
|
|
|
|
question = desktop.dialog.Question("Are you sure?")
|
|
result = question.open()
|
|
|
|
To override the detected desktop, specify the desktop parameter to the open
|
|
function as follows:
|
|
|
|
question.open("KDE") # Insists on KDE
|
|
question.open("GNOME") # Insists on GNOME
|
|
question.open("MATE") # Insists on MATE
|
|
|
|
The dialogue box options are documented in each class's docstring.
|
|
|
|
Available dialogue box classes are listed in the desktop.dialog.available
|
|
attribute.
|
|
|
|
Supported desktop environments are listed in the desktop.dialog.supported
|
|
attribute.
|
|
"""
|
|
|
|
from desktop import use_desktop, _run, _readfrom, _status
|
|
|
|
class _wrapper:
|
|
def __init__(self, handler):
|
|
self.handler = handler
|
|
|
|
class _readvalue(_wrapper):
|
|
def __call__(self, cmd, shell):
|
|
return self.handler(cmd, shell).strip()
|
|
|
|
class _readinput(_wrapper):
|
|
def __call__(self, cmd, shell):
|
|
return self.handler(cmd, shell)[:-1]
|
|
|
|
class _readvalues_kdialog(_wrapper):
|
|
def __call__(self, cmd, shell):
|
|
result = self.handler(cmd, shell).strip().strip('"')
|
|
if result:
|
|
return result.split('" "')
|
|
else:
|
|
return []
|
|
|
|
class _readvalues_zenity(_wrapper):
|
|
def __call__(self, cmd, shell):
|
|
result = self.handler(cmd, shell).strip()
|
|
if result:
|
|
return result.split("|")
|
|
else:
|
|
return []
|
|
|
|
class _readvalues_Xdialog(_wrapper):
|
|
def __call__(self, cmd, shell):
|
|
result = self.handler(cmd, shell).strip()
|
|
if result:
|
|
return result.split("/")
|
|
else:
|
|
return []
|
|
|
|
# Dialogue parameter classes.
|
|
|
|
class String:
|
|
|
|
"A generic parameter."
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
def convert(self, value, program):
|
|
return [value or ""]
|
|
|
|
class Strings(String):
|
|
|
|
"Multiple string parameters."
|
|
|
|
def convert(self, value, program):
|
|
return value or []
|
|
|
|
class StringPairs(String):
|
|
|
|
"Multiple string parameters duplicated to make identifiers."
|
|
|
|
def convert(self, value, program):
|
|
l = []
|
|
for v in value:
|
|
l.append(v)
|
|
l.append(v)
|
|
return l
|
|
|
|
class StringKeyword:
|
|
|
|
"A keyword parameter."
|
|
|
|
def __init__(self, keyword, name):
|
|
self.keyword = keyword
|
|
self.name = name
|
|
|
|
def convert(self, value, program):
|
|
return [self.keyword + "=" + (value or "")]
|
|
|
|
class StringKeywords:
|
|
|
|
"Multiple keyword parameters."
|
|
|
|
def __init__(self, keyword, name):
|
|
self.keyword = keyword
|
|
self.name = name
|
|
|
|
def convert(self, value, program):
|
|
l = []
|
|
for v in value or []:
|
|
l.append(self.keyword + "=" + v)
|
|
return l
|
|
|
|
class Integer(String):
|
|
|
|
"An integer parameter."
|
|
|
|
defaults = {
|
|
"width" : 40,
|
|
"height" : 15,
|
|
"list_height" : 10
|
|
}
|
|
scale = 8
|
|
|
|
def __init__(self, name, pixels=0):
|
|
String.__init__(self, name)
|
|
if pixels:
|
|
self.factor = self.scale
|
|
else:
|
|
self.factor = 1
|
|
|
|
def convert(self, value, program):
|
|
if value is None:
|
|
value = self.defaults[self.name]
|
|
return [str(int(value) * self.factor)]
|
|
|
|
class IntegerKeyword(Integer):
|
|
|
|
"An integer keyword parameter."
|
|
|
|
def __init__(self, keyword, name, pixels=0):
|
|
Integer.__init__(self, name, pixels)
|
|
self.keyword = keyword
|
|
|
|
def convert(self, value, program):
|
|
if value is None:
|
|
value = self.defaults[self.name]
|
|
return [self.keyword + "=" + str(int(value) * self.factor)]
|
|
|
|
class Boolean(String):
|
|
|
|
"A boolean parameter."
|
|
|
|
values = {
|
|
"kdialog" : ["off", "on"],
|
|
"zenity" : ["FALSE", "TRUE"],
|
|
"Xdialog" : ["off", "on"]
|
|
}
|
|
|
|
def convert(self, value, program):
|
|
values = self.values[program]
|
|
if value:
|
|
return [values[1]]
|
|
else:
|
|
return [values[0]]
|
|
|
|
class MenuItemList(String):
|
|
|
|
"A menu item list parameter."
|
|
|
|
def convert(self, value, program):
|
|
l = []
|
|
for v in value:
|
|
l.append(v.value)
|
|
l.append(v.text)
|
|
return l
|
|
|
|
class ListItemList(String):
|
|
|
|
"A radiolist/checklist item list parameter."
|
|
|
|
def __init__(self, name, status_first=0):
|
|
String.__init__(self, name)
|
|
self.status_first = status_first
|
|
|
|
def convert(self, value, program):
|
|
l = []
|
|
for v in value:
|
|
boolean = Boolean(None)
|
|
status = boolean.convert(v.status, program)
|
|
if self.status_first:
|
|
l += status
|
|
l.append(v.value)
|
|
l.append(v.text)
|
|
if not self.status_first:
|
|
l += status
|
|
return l
|
|
|
|
# Dialogue argument values.
|
|
|
|
class MenuItem:
|
|
|
|
"A menu item which can also be used with radiolists and checklists."
|
|
|
|
def __init__(self, value, text, status=0):
|
|
self.value = value
|
|
self.text = text
|
|
self.status = status
|
|
|
|
# Dialogue classes.
|
|
|
|
class Dialogue:
|
|
|
|
commands = {
|
|
"KDE" : "kdialog",
|
|
"GNOME" : "zenity",
|
|
"MATE" : "zenity",
|
|
"XFCE" : "zenity", # NOTE: Based on observations with Xubuntu.
|
|
"X11" : "Xdialog"
|
|
}
|
|
|
|
def open(self, desktop=None):
|
|
|
|
"""
|
|
Open a dialogue box (dialog) using a program appropriate to the desktop
|
|
environment in use.
|
|
|
|
If the optional 'desktop' parameter is specified then attempt to use
|
|
that particular desktop environment's mechanisms to open the dialog
|
|
instead of guessing or detecting which environment is being used.
|
|
|
|
Suggested values for 'desktop' are "standard", "KDE", "GNOME",
|
|
"MATE", "Mac OS X", "Windows".
|
|
|
|
The result of the dialogue interaction may be a string indicating user
|
|
input (for Input, Password, Menu, Pulldown), a list of strings
|
|
indicating selections of one or more items (for RadioList, CheckList),
|
|
or a value indicating true or false (for Question, Warning, Message,
|
|
Error).
|
|
|
|
Where a string value may be expected but no choice is made, an empty
|
|
string may be returned. Similarly, where a list of values is expected
|
|
but no choice is made, an empty list may be returned.
|
|
"""
|
|
|
|
# Decide on the desktop environment in use.
|
|
|
|
desktop_in_use = use_desktop(desktop)
|
|
|
|
# Get the program.
|
|
|
|
try:
|
|
program = self.commands[desktop_in_use]
|
|
except KeyError:
|
|
raise OSError("Desktop '%s' not supported (no known dialogue box command could be suggested)" % desktop_in_use)
|
|
|
|
# The handler is one of the functions communicating with the subprocess.
|
|
# Some handlers return boolean values, others strings.
|
|
|
|
handler, options = self.info[program]
|
|
|
|
cmd = [program]
|
|
for option in options:
|
|
if isinstance(option, str):
|
|
cmd.append(option)
|
|
else:
|
|
value = getattr(self, option.name, None)
|
|
cmd += option.convert(value, program)
|
|
|
|
return handler(cmd, 0)
|
|
|
|
class Simple(Dialogue):
|
|
def __init__(self, text, width=None, height=None):
|
|
self.text = text
|
|
self.width = width
|
|
self.height = height
|
|
|
|
class Question(Simple):
|
|
|
|
"""
|
|
A dialogue asking a question and showing response buttons.
|
|
Options: text, width (in characters), height (in characters)
|
|
Response: a boolean value indicating an affirmative response (true) or a
|
|
negative response
|
|
"""
|
|
|
|
name = "question"
|
|
info = {
|
|
"kdialog" : (_status, ["--yesno", String("text")]),
|
|
"zenity" : (_status, ["--question", StringKeyword("--text", "text")]),
|
|
"Xdialog" : (_status, ["--stdout", "--yesno", String("text"), Integer("height"), Integer("width")]),
|
|
}
|
|
|
|
class Warning(Simple):
|
|
|
|
"""
|
|
A dialogue asking a question and showing response buttons.
|
|
Options: text, width (in characters), height (in characters)
|
|
Response: a boolean value indicating an affirmative response (true) or a
|
|
negative response
|
|
"""
|
|
|
|
name = "warning"
|
|
info = {
|
|
"kdialog" : (_status, ["--warningyesno", String("text")]),
|
|
"zenity" : (_status, ["--warning", StringKeyword("--text", "text")]),
|
|
"Xdialog" : (_status, ["--stdout", "--yesno", String("text"), Integer("height"), Integer("width")]),
|
|
}
|
|
|
|
class Message(Simple):
|
|
|
|
"""
|
|
A message dialogue.
|
|
Options: text, width (in characters), height (in characters)
|
|
Response: a boolean value indicating an affirmative response (true) or a
|
|
negative response
|
|
"""
|
|
|
|
name = "message"
|
|
info = {
|
|
"kdialog" : (_status, ["--msgbox", String("text")]),
|
|
"zenity" : (_status, ["--info", StringKeyword("--text", "text")]),
|
|
"Xdialog" : (_status, ["--stdout", "--msgbox", String("text"), Integer("height"), Integer("width")]),
|
|
}
|
|
|
|
class Error(Simple):
|
|
|
|
"""
|
|
An error dialogue.
|
|
Options: text, width (in characters), height (in characters)
|
|
Response: a boolean value indicating an affirmative response (true) or a
|
|
negative response
|
|
"""
|
|
|
|
name = "error"
|
|
info = {
|
|
"kdialog" : (_status, ["--error", String("text")]),
|
|
"zenity" : (_status, ["--error", StringKeyword("--text", "text")]),
|
|
"Xdialog" : (_status, ["--stdout", "--msgbox", String("text"), Integer("height"), Integer("width")]),
|
|
}
|
|
|
|
class Menu(Simple):
|
|
|
|
"""
|
|
A menu of options, one of which being selectable.
|
|
Options: text, width (in characters), height (in characters),
|
|
list_height (in items), items (MenuItem objects)
|
|
Response: a value corresponding to the chosen item
|
|
"""
|
|
|
|
name = "menu"
|
|
info = {
|
|
"kdialog" : (_readvalue(_readfrom), ["--menu", String("text"), MenuItemList("items")]),
|
|
"zenity" : (_readvalue(_readfrom), ["--list", StringKeyword("--text", "text"), StringKeywords("--column", "titles"),
|
|
MenuItemList("items")]
|
|
),
|
|
"Xdialog" : (_readvalue(_readfrom), ["--stdout", "--menubox",
|
|
String("text"), Integer("height"), Integer("width"), Integer("list_height"), MenuItemList("items")]
|
|
),
|
|
}
|
|
item = MenuItem
|
|
number_of_titles = 2
|
|
|
|
def __init__(self, text, titles, items=None, width=None, height=None, list_height=None):
|
|
|
|
"""
|
|
Initialise a menu with the given heading 'text', column 'titles', and
|
|
optional 'items' (which may be added later), 'width' (in characters),
|
|
'height' (in characters) and 'list_height' (in items).
|
|
"""
|
|
|
|
Simple.__init__(self, text, width, height)
|
|
self.titles = ([""] * self.number_of_titles + titles)[-self.number_of_titles:]
|
|
self.items = items or []
|
|
self.list_height = list_height
|
|
|
|
def add(self, *args, **kw):
|
|
|
|
"""
|
|
Add an item, passing the given arguments to the appropriate item class.
|
|
"""
|
|
|
|
self.items.append(self.item(*args, **kw))
|
|
|
|
class RadioList(Menu):
|
|
|
|
"""
|
|
A list of radio buttons, one of which being selectable.
|
|
Options: text, width (in characters), height (in characters),
|
|
list_height (in items), items (MenuItem objects), titles
|
|
Response: a list of values corresponding to chosen items (since some
|
|
programs, eg. zenity, appear to support multiple default
|
|
selections)
|
|
"""
|
|
|
|
name = "radiolist"
|
|
info = {
|
|
"kdialog" : (_readvalues_kdialog(_readfrom), ["--radiolist", String("text"), ListItemList("items")]),
|
|
"zenity" : (_readvalues_zenity(_readfrom),
|
|
["--list", "--radiolist", StringKeyword("--text", "text"), StringKeywords("--column", "titles"),
|
|
ListItemList("items", 1)]
|
|
),
|
|
"Xdialog" : (_readvalues_Xdialog(_readfrom), ["--stdout", "--radiolist",
|
|
String("text"), Integer("height"), Integer("width"), Integer("list_height"), ListItemList("items")]
|
|
),
|
|
}
|
|
number_of_titles = 3
|
|
|
|
class CheckList(Menu):
|
|
|
|
"""
|
|
A list of checkboxes, many being selectable.
|
|
Options: text, width (in characters), height (in characters),
|
|
list_height (in items), items (MenuItem objects), titles
|
|
Response: a list of values corresponding to chosen items
|
|
"""
|
|
|
|
name = "checklist"
|
|
info = {
|
|
"kdialog" : (_readvalues_kdialog(_readfrom), ["--checklist", String("text"), ListItemList("items")]),
|
|
"zenity" : (_readvalues_zenity(_readfrom),
|
|
["--list", "--checklist", StringKeyword("--text", "text"), StringKeywords("--column", "titles"),
|
|
ListItemList("items", 1)]
|
|
),
|
|
"Xdialog" : (_readvalues_Xdialog(_readfrom), ["--stdout", "--checklist",
|
|
String("text"), Integer("height"), Integer("width"), Integer("list_height"), ListItemList("items")]
|
|
),
|
|
}
|
|
number_of_titles = 3
|
|
|
|
class Pulldown(Menu):
|
|
|
|
"""
|
|
A pull-down menu of options, one of which being selectable.
|
|
Options: text, width (in characters), height (in characters),
|
|
items (list of values)
|
|
Response: a value corresponding to the chosen item
|
|
"""
|
|
|
|
name = "pulldown"
|
|
info = {
|
|
"kdialog" : (_readvalue(_readfrom), ["--combobox", String("text"), Strings("items")]),
|
|
"zenity" : (_readvalue(_readfrom),
|
|
["--list", "--radiolist", StringKeyword("--text", "text"), StringKeywords("--column", "titles"),
|
|
StringPairs("items")]
|
|
),
|
|
"Xdialog" : (_readvalue(_readfrom),
|
|
["--stdout", "--combobox", String("text"), Integer("height"), Integer("width"), Strings("items")]),
|
|
}
|
|
item = str
|
|
number_of_titles = 2
|
|
|
|
class Input(Simple):
|
|
|
|
"""
|
|
An input dialogue, consisting of an input field.
|
|
Options: text, input, width (in characters), height (in characters)
|
|
Response: the text entered into the dialogue by the user
|
|
"""
|
|
|
|
name = "input"
|
|
info = {
|
|
"kdialog" : (_readinput(_readfrom),
|
|
["--inputbox", String("text"), String("data")]),
|
|
"zenity" : (_readinput(_readfrom),
|
|
["--entry", StringKeyword("--text", "text"), StringKeyword("--entry-text", "data")]),
|
|
"Xdialog" : (_readinput(_readfrom),
|
|
["--stdout", "--inputbox", String("text"), Integer("height"), Integer("width"), String("data")]),
|
|
}
|
|
|
|
def __init__(self, text, data="", width=None, height=None):
|
|
Simple.__init__(self, text, width, height)
|
|
self.data = data
|
|
|
|
class Password(Input):
|
|
|
|
"""
|
|
A password dialogue, consisting of a password entry field.
|
|
Options: text, width (in characters), height (in characters)
|
|
Response: the text entered into the dialogue by the user
|
|
"""
|
|
|
|
name = "password"
|
|
info = {
|
|
"kdialog" : (_readinput(_readfrom),
|
|
["--password", String("text")]),
|
|
"zenity" : (_readinput(_readfrom),
|
|
["--entry", StringKeyword("--text", "text"), "--hide-text"]),
|
|
"Xdialog" : (_readinput(_readfrom),
|
|
["--stdout", "--password", "--inputbox", String("text"), Integer("height"), Integer("width")]),
|
|
}
|
|
|
|
class TextFile(Simple):
|
|
|
|
"""
|
|
A text file input box.
|
|
Options: filename, text, width (in characters), height (in characters)
|
|
Response: any text returned by the dialogue program (typically an empty
|
|
string)
|
|
"""
|
|
|
|
name = "textfile"
|
|
info = {
|
|
"kdialog" : (_readfrom, ["--textbox", String("filename"), Integer("width", pixels=1), Integer("height", pixels=1)]),
|
|
"zenity" : (_readfrom, ["--text-info", StringKeyword("--filename", "filename"), IntegerKeyword("--width", "width", pixels=1),
|
|
IntegerKeyword("--height", "height", pixels=1)]
|
|
),
|
|
"Xdialog" : (_readfrom, ["--stdout", "--textbox", String("filename"), Integer("height"), Integer("width")]),
|
|
}
|
|
|
|
def __init__(self, filename, text="", width=None, height=None):
|
|
Simple.__init__(self, text, width, height)
|
|
self.filename = filename
|
|
|
|
# Available dialogues.
|
|
|
|
available = [Question, Warning, Message, Error, Menu, CheckList, RadioList, Input, Password, Pulldown, TextFile]
|
|
|
|
# Supported desktop environments.
|
|
|
|
supported = list(Dialogue.commands.keys())
|
|
|
|
# vim: tabstop=4 expandtab shiftwidth=4
|