4
"""Classes for creating and laying out GUI applications.
20
from widget import Widget
21
from control import Control, MissingOption
22
from libtovid.cli import Command
24
### --------------------------------------------------------------------
25
### GUI interface-definition API
26
### --------------------------------------------------------------------
29
"""A group of option controls in a rectangular frame.
34
Filename('bgaudio', "Background audio file"),
35
Flag('submenus', "Create submenus"),
36
Number('menu-length', "Length of menu (seconds)")
39
This creates a panel with three GUI widgets that control command-line
40
options '-bgaudio', '-submenus', and '-menu-length'.
42
def __init__(self, title='', *contents):
43
"""Create a Panel to hold control widgets or sub-panels.
45
title: Title of panel (name shown in tab bar)
46
contents: One or more Widgets (Controls, Panels, Drawers etc.)
49
if type(title) != str:
50
raise TypeError("First argument to Panel must be a text label.")
54
if isinstance(item, Widget):
55
self.contents.append(item)
58
# Print helpful info from stack trace
59
last_error = traceback.extract_stack()[0]
60
filename, lineno, foo, code = last_error
61
print "Error on line %(lineno)s of %(filename)s:" % vars()
63
print "Panel '%s' may only contain Widget subclasses" \
64
" (Controls, Panels, Drawers etc.)" \
65
" got %s instead" % type(item)
68
def draw(self, master, side='top'):
69
"""Draw Panel and its contents in the given master.
71
Widget.draw(self, master)
72
# Get a labeled or unlabeled frame
74
frame = tk.LabelFrame(self, text=self.title)
75
frame.pack(fill='both', expand=True)
78
# Draw all contents in the frame
79
for item in self.contents:
81
item.pack(side=side, anchor='nw', fill='x', expand=True,
85
"""Return a list of all command-line options from contained widgets.
86
Print error messages if any required options are missing.
89
for item in self.contents:
91
args += item.get_args()
92
except MissingOption, missing:
93
print "Missing a required option: " + missing.option
96
### --------------------------------------------------------------------
99
"""A panel with widgets packed left-to-right"""
100
def __init__(self, title='', *contents):
101
Panel.__init__(self, title, *contents)
103
def draw(self, master):
104
Panel.draw(self, master, 'left')
106
### --------------------------------------------------------------------
108
class VPanel (Panel):
109
"""A panel with widgets packed top-to-bottom"""
110
def __init__(self, title='', *contents):
111
Panel.__init__(self, title, *contents)
113
def draw(self, master):
114
Panel.draw(self, master, 'top')
116
### --------------------------------------------------------------------
117
from support import ComboBox, ListVar
118
from control import Control
119
from libtovid.odict import Odict
121
class Dropdowns (Panel):
122
"""A Panel that uses dropdowns for selecting and setting options.
124
Given a list of controls, the Dropdowns panel displays, initially,
125
a single dropdown list. Each control is a choice in the list, and
126
is shown as two columns, option and label (with help shown in a tooltip).
128
Selecting a control causes that control to be displayed, so that it
129
may be set to the desired value (along with a "remove" button to discard
130
the control). The dropdown list is shifted downward, so another control
131
and option may be set.
133
def __init__(self, title='', *contents):
134
Panel.__init__(self, title, *contents)
135
# Controls, indexed by option
136
self.controls = Odict()
137
for control in self.contents:
138
if not isinstance(control, Control):
139
raise TypeError("Can only add Controls to a Dropdown")
140
self.controls[control.option] = control
142
def draw(self, master):
144
tk.LabelFrame.__init__(self, master, text=self.title,
147
tk.LabelFrame.__init__(self, master, bd=0, text='',
149
self.choices = ListVar(items=self.controls.keys())
150
self.chosen = tk.StringVar()
151
self.chooser = ComboBox(self, self.choices, variable=self.chosen,
152
command=self.choose_new)
153
self.chooser.pack(fill='both', expand=True)
155
def choose_new(self, event=None):
156
"""Create and display the chosen control."""
157
chosen = self.chosen.get()
160
self.chooser.pack_forget()
161
# Put control and remove button in a frame
162
frame = tk.Frame(self)
163
button = tk.Button(frame, text="X",
164
command=lambda:self.remove(chosen))
165
button.pack(side='left')
166
control = self.controls[chosen]
168
control.pack(side='left', fill='x', expand=True)
169
frame.pack(fill='x', expand=True)
170
# Remove the chosen control/panel from the list of available ones
171
self.choices.remove(chosen)
174
def remove(self, option):
175
"""Remove a given option's control from the interface."""
176
frame = self.controls[option].master
179
# Make this option available in the dropdown
180
self.choices.append(option)
183
"""Return a list of all command-line options from contained widgets.
186
for control in self.contents:
188
args += control.get_args()
192
### --------------------------------------------------------------------
194
class Drawer (Widget):
195
"""Like a Panel, but may be hidden or "closed" like a drawer."""
196
def __init__(self, title='', *contents):
197
Widget.__init__(self)
198
self.panel = Panel(title, *contents)
201
def draw(self, master):
202
Widget.draw(self, master)
204
button = tk.Button(self, text=self.panel.title,
205
command=self.show_hide)
206
button.pack(anchor='nw', fill='x', expand=True)
207
# Draw panel, but don't pack
208
self.panel.draw(self)
213
self.panel.pack_forget()
217
self.panel.pack(anchor='nw', fill='both', expand=True)
221
return self.panel.get_args()
223
### --------------------------------------------------------------------
226
"""A widget with tab buttons that switch between several panels.
228
def __init__(self, *panels, **kwargs):
229
"""Create tabs that switch between several Panels.
231
Widget.__init__(self)
235
if not isinstance(panel, Panel):
236
raise TypeError("Tabs may only contain Panels")
237
self.panels.append(panel)
239
def add(self, panel):
240
"""Add the given Panel to the Tabs.
242
self.panels.append(panel)
244
def draw(self, master, side='top'):
245
"""Draw the Tabs widget in the given master."""
246
Widget.draw(self, master)
247
self.selected = tk.IntVar()
249
# Tkinter configuration common to all tab buttons
251
'variable': self.selected,
252
'command': self.change,
253
'selectcolor': 'white',
255
'offrelief': 'groove',
259
# Frame to hold tab buttons
260
self.buttons = tk.Frame(self)
261
# For tabs on left or right, pack tab buttons vertically
262
if self.side in ['left', 'right']:
270
# Tab buttons, numbered from 0
271
for index, panel in enumerate(self.panels):
272
button = tk.Radiobutton(self.buttons, text=panel.title,
273
value=index, **config)
274
button.pack(anchor='nw', side=button_side,
275
fill='both', expand=True)
277
self.buttons.pack(anchor=bar_anchor, side=self.side,
279
# Activate the first tab
284
"""Switch to the selected tab's frame.
286
# Unpack the existing panel
287
self.panels[self.index].pack_forget()
288
# Pack the newly-selected panel
289
selected = self.selected.get()
290
self.panels[selected].pack(side=self.side, fill='both', expand=True)
291
# Remember this tab's index
292
self.index = selected
295
"""Return command-line arguments from each of the tab Panels.
298
for panel in self.panels:
299
args += panel.get_args()
302
### --------------------------------------------------------------------
305
class Application (Widget):
306
"""Graphical frontend for a command-line program
308
def __init__(self, program, panels=None, style=None):
309
"""Define a GUI application frontend for a command-line program.
311
program: Command-line program that the GUI is a frontend for
312
panels: List of Panels (groups of widgets), containing controls
313
for the given program's options. Use [panel] to pass
314
a single panel. If there are multiple panels, a tabbed
315
application is created.
317
After defining the Application, call run() to show/execute it.
319
Widget.__init__(self)
320
self.program = program
321
# TODO: Friendlier error-handling
322
if not type(panels) == list or len(panels) == 0:
323
raise TypeError("Application needs a list of Panels")
324
self.panels = panels or []
328
def draw(self, master):
329
"""Draw the Application in the given master.
331
Widget.draw(self, master)
332
# Single-panel application
333
if len(self.panels) == 1:
334
panel = self.panels[0]
336
panel.pack(anchor='n', fill='x', expand=True)
337
# Multi-panel (tabbed) application
339
tabs = Tabs(*self.panels)
341
tabs.pack(anchor='n', fill='x', expand=True)
343
button = tk.Button(self, text="Run %s now" % self.program,
344
command=self.execute)
345
button.pack(anchor='s', fill='x')
348
"""Get a list of all command-line arguments from all panels.
350
if isinstance(self.panels, Panel):
351
return self.panels.get_args()
352
elif isinstance(self.panels, list):
354
for panel in self.panels:
355
args += panel.get_args()
359
"""Run the program with all the supplied options.
361
args = self.get_args()
362
command = Command(self.program, *args)
364
print "Running command:", command
366
if tkMessageBox.askyesno(message="Run %s now?" % self.program):
368
root = self.master.master
372
except KeyboardInterrupt:
373
tkMessageBox.showerror(message="todisc was interrupted!")
375
tkMessageBox.showinfo(message="todisc finished running!")
378
### --------------------------------------------------------------------
379
from support import ConfigWindow, Style
381
DEFAULT_CONFIG = os.path.expanduser('~/.metagui/config')
384
"""GUI with one or more Applications
386
def __init__(self, title, applications, width=600, height=600,
388
"""Create a GUI for the given applications.
390
title: Text shown in the title bar
391
applications: List of Applications to included in the GUI
392
width: Initial width of GUI window in pixels
393
height: Initial height of GUI window in pixels
394
inifile: Name of an .ini-formatted file with GUI configuration
397
self.geometry("%dx%d" % (width, height))
399
# TODO: Friendlier error-handling
400
if not type(applications) == list or len(applications) == 0:
401
raise TypeError("GUI needs a list of Applications")
402
self.apps = applications
405
self.inifile = inifile or DEFAULT_CONFIG
407
if os.path.exists(self.inifile):
408
print "Loading style from config file", self.inifile
409
self.style.load(self.inifile)
411
print "Creating config file", self.inifile
412
self.style.save(self.inifile)
413
# Show hidden file option in file dialogs
414
self.tk.call('namespace', 'import', '::tk::dialog::file::')
415
self.tk.call('set', '::tk::dialog::file::showHiddenBtn', '1')
416
self.tk.call('set', '::tk::dialog::file::showHiddenVar', '0')
422
# Enter the main event handler
424
# TODO: Interrupt handling
428
self.style.apply(self)
429
self.frame = tk.Frame(self, width=self.width, height=self.height)
430
self.frame.pack(fill='both', expand=True)
431
self.resizable(width=True, height=True)
432
# Single-application GUI
433
if len(self.apps) == 1:
436
app.pack(anchor='n', fill='both', expand=True)
437
# Multi-application (tabbed) GUI
439
tabs = Tabs(self.frame, 'top')
440
for app in self.apps:
442
tabs.add(app.program, app)
444
tabs.pack(anchor='n', fill='both', expand=True)
445
#self.frame.pack_propagate(False)
451
def draw_menu(self, window):
452
"""Draw a menu bar in the given top-level window.
454
# Create and add the menu bar
455
menubar = tk.Menu(window)
456
window.config(menu=menubar)
458
filemenu = tk.Menu(menubar, tearoff=False)
459
filemenu.add_command(label="Config", command=self.show_config)
460
filemenu.add_separator()
461
filemenu.add_command(label="Exit", command=self.quit)
462
menubar.add_cascade(label="File", menu=filemenu)
464
def show_config(self):
465
"""Open the GUI configuration dialog."""
466
config = ConfigWindow(self, self.style)
468
self.style = config.result
469
print "Saving configuration to", self.inifile
470
self.style.save(self.inifile)
471
self.style.apply(self)
474
### --------------------------------------------------------------------