~ubuntu-branches/ubuntu/karmic/tovid/karmic

« back to all changes in this revision

Viewing changes to libtovid/metagui/control.py

  • Committer: Bazaar Package Importer
  • Author(s): Matvey Kozhev
  • Date: 2008-01-24 22:04:40 UTC
  • Revision ID: james.westby@ubuntu.com-20080124220440-x7cheljduf1rdgnq
Tags: upstream-0.31
ImportĀ upstreamĀ versionĀ 0.31

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#! /usr/bin/env python
 
2
# control.py
 
3
 
 
4
"""Control widget classes.
 
5
 
 
6
This module defines a Control class and several derivatives. A Control is a
 
7
special-purpose GUI widget for setting a value such as a number or filename.
 
8
 
 
9
"""
 
10
 
 
11
__all__ = [
 
12
    'Control',
 
13
    # Control subclasses
 
14
    'Choice',
 
15
    'Color',
 
16
    'Filename',
 
17
    'Flag',
 
18
    'FlagGroup',
 
19
    'Font',
 
20
    'Text',
 
21
    'List',
 
22
    'Number',
 
23
    'FileList',
 
24
    'TextList']
 
25
 
 
26
import Tkinter as tk
 
27
from widget import Widget
 
28
from support import ListVar
 
29
import support
 
30
import os
 
31
 
 
32
### --------------------------------------------------------------------
 
33
class MissingOption (Exception):
 
34
    def __init__(self, option):
 
35
        self.option = option
 
36
 
 
37
### --------------------------------------------------------------------
 
38
# Map python types to Tkinter variable types
 
39
_vartypes = {
 
40
    str: tk.StringVar,
 
41
    bool: tk.BooleanVar,
 
42
    int: tk.IntVar,
 
43
    float: tk.DoubleVar,
 
44
    list: ListVar}
 
45
 
 
46
### --------------------------------------------------------------------
 
47
from tooltip import ToolTip
 
48
 
 
49
class Control (Widget):
 
50
    """A widget that controls the value of a command-line option.
 
51
 
 
52
    A Control is a specialized GUI widget that controls a command-line option
 
53
    via a local variable, accessed via get() and set() methods.
 
54
    
 
55
    Control subclasses may have any number of sub-widgets such as labels,
 
56
    buttons or entry boxes; one of the sub-widgets should be linked to the
 
57
    controlled variable via an option like:
 
58
    
 
59
        entry = Entry(self, textvariable=self.variable)
 
60
    
 
61
    See the Control subclasses below for examples of how self.variable,
 
62
    get() and set() are used.
 
63
    """
 
64
    def __init__(self,
 
65
                 vartype=str,
 
66
                 label='',
 
67
                 option='',
 
68
                 default='',
 
69
                 help='',
 
70
                 **kwargs):
 
71
        """Create a Control for an option.
 
72
 
 
73
            vartype:  Type of stored variable (str, bool, int, float, list)
 
74
            label:    Label shown in the GUI for the Control
 
75
            option:   Command-line option associated with this Control, or
 
76
                      '' to create a positional argument
 
77
            default:  Default value for the Control
 
78
            help:     Help text to show in a tooltip
 
79
            **kwargs: Keyword arguments of the form key1=arg1, key2=arg2
 
80
        
 
81
        Keyword arguments allowed:
 
82
        
 
83
            pull=Control(...):  Mirror value from another Control
 
84
            required=True:      Required option, must be set or run will fail
 
85
            filter=function:    Text-filtering function for pulled values
 
86
            toggles=True:       May be toggled on and off
 
87
        """
 
88
        Widget.__init__(self)
 
89
        self.vartype = vartype
 
90
        self.variable = None
 
91
        self.label = label
 
92
        self.option = option
 
93
        self.default = default or vartype()
 
94
        self.help = help
 
95
        self.kwargs = kwargs
 
96
 
 
97
        # TODO: Find a way to condense/separate keyword handling
 
98
        # Controls a mandatory option?
 
99
        self.required = False
 
100
        if 'required' in kwargs:
 
101
            self.required = bool(kwargs['required'])
 
102
        # Has an enable/disable toggle button
 
103
        self.toggles = False
 
104
        if 'toggles' in self.kwargs:
 
105
            self.toggles = bool(self.kwargs['toggles'])
 
106
        # List of Controls to copy updated values to
 
107
        self.copies = []
 
108
        if 'pull' in self.kwargs:
 
109
            if not isinstance(self.kwargs['pull'], Control):
 
110
                raise TypeError("Can only pull values from a Control.")
 
111
            pull_from = self.kwargs['pull']
 
112
            pull_from.copy_to(self)
 
113
        # Filter expression when pulling from another Control
 
114
        self.filter = None
 
115
        if 'filter' in self.kwargs:
 
116
            if not callable(self.kwargs['filter']):
 
117
                raise TypeError("Pull filter must be a function.")
 
118
            self.filter = self.kwargs['filter']
 
119
 
 
120
    def copy_to(self, control):
 
121
        """Update another control whenever this control's value changes.
 
122
        """
 
123
        if not isinstance(control, Control):
 
124
            raise TypeError("Can only copy values to a Control.")
 
125
        self.copies.append(control)
 
126
 
 
127
    def draw(self, master):
 
128
        """Draw the control widgets in the given master.
 
129
        
 
130
        Override this method in derived classes, and call the base
 
131
        class draw() method:
 
132
        
 
133
            Control.draw(self, master)
 
134
        
 
135
        """
 
136
        Widget.draw(self, master)
 
137
        # Create tk.Variable to store Control's value
 
138
        if self.vartype in _vartypes:
 
139
            self.variable = _vartypes[self.vartype]()
 
140
        else:
 
141
            self.variable = tk.Variable()
 
142
        # Set default value
 
143
        if self.default:
 
144
            self.variable.set(self.default)
 
145
        # Draw tooltip
 
146
        if self.help != '':
 
147
            self.tooltip = ToolTip(self, text=self.help, delay=1000)
 
148
        # Draw enabler checkbox
 
149
        if self.toggles:
 
150
            self.enabled = tk.BooleanVar()
 
151
            self.check = tk.Checkbutton(self, text='',
 
152
                                        variable=self.enabled,
 
153
                                        command=self.enabler)
 
154
            self.check.pack(side='left')
 
155
 
 
156
    def post(self):
 
157
        """Post-draw initialization.
 
158
        """
 
159
        if self.toggles:
 
160
            self.enabler()
 
161
 
 
162
    def enabler(self):
 
163
        """Enable or disable the Control when the checkbox is toggled.
 
164
        """
 
165
        if self.enabled.get():
 
166
            self.enable()
 
167
        else:
 
168
            self.disable()
 
169
            self.check.config(state='normal')
 
170
 
 
171
    def get(self):
 
172
        """Return the value of the Control's variable."""
 
173
        # self.variable isn't set until draw() is called
 
174
        if not self.variable:
 
175
            raise "Must call draw() before get()"
 
176
        return self.variable.get()
 
177
 
 
178
    def set(self, value):
 
179
        """Set the Control's variable to the given value."""
 
180
        # self.variable isn't set until draw() is called
 
181
        if not self.variable:
 
182
            raise "Must call draw() before set()"
 
183
        self.variable.set(value)
 
184
        # Update controls that are copying this control's value
 
185
        for control in self.copies:
 
186
            control.variable.set(value)
 
187
 
 
188
    def get_args(self):
 
189
        """Return a list of arguments for passing this command-line option.
 
190
        draw() must be called before this function.
 
191
        """
 
192
        # TODO: Raise exception if draw() hasn't been called
 
193
        args = []
 
194
        value = self.get()
 
195
 
 
196
        # For toggles, return 
 
197
        if self.toggles:
 
198
            if not self.enabled.get():
 
199
                return []
 
200
        # Skip if unmodified or empty
 
201
        elif value == self.default or value == []:
 
202
            # ...unless it's required
 
203
            if self.required:
 
204
                raise MissingOption(self.option)
 
205
            else:
 
206
                return []
 
207
 
 
208
        # '-option'
 
209
        if self.option != '':
 
210
            args.append(self.option)
 
211
        # List of arguments
 
212
        if type(value) == list:
 
213
            args.extend(value)
 
214
        # Single argument
 
215
        else:
 
216
            args.append(value)
 
217
        return args
 
218
 
 
219
    def __repr__(self):
 
220
        """Return a Python code representation of this Control."""
 
221
        # Get derived class name
 
222
        control = str(self.__class__).split('.')[-1]
 
223
        return "%s('%s', '%s')" % (control, self.option, self.label)
 
224
 
 
225
### --------------------------------------------------------------------
 
226
### Control subclasses
 
227
### --------------------------------------------------------------------
 
228
 
 
229
class Flag (Control):
 
230
    """A widget for controlling a yes/no value."""
 
231
    def __init__(self,
 
232
                 label="Flag",
 
233
                 option='',
 
234
                 default=False,
 
235
                 help='',
 
236
                 **kwargs):
 
237
        """Create a Flag widget with the given label and default value.
 
238
 
 
239
            label:    Text label for the flag
 
240
            option:   Command-line flag passed
 
241
            default:  Default value (True or False)
 
242
            help:     Help text to show in a tooltip
 
243
        """
 
244
        Control.__init__(self, bool, label, option, default, help, **kwargs)
 
245
        # Enable an associated control when this Flag is True
 
246
        self.enables = None
 
247
        if 'enables' in kwargs:
 
248
            self.enables = kwargs['enables']
 
249
            if not isinstance(self.enables, Widget):
 
250
                raise "A Flag can only enable a Widget (Control or Panel)"
 
251
 
 
252
    def draw(self, master):
 
253
        """Draw control widgets in the given master."""
 
254
        Control.draw(self, master)
 
255
        self.check = tk.Checkbutton(self, text=self.label,
 
256
                                    variable=self.variable,
 
257
                                    command=self.enabler)
 
258
        self.check.pack(side='left')
 
259
        # Draw any controls enabled by this one
 
260
        if self.enables:
 
261
            self.enables.draw(self)
 
262
            self.enables.pack(side='left', fill='x', expand=True)
 
263
            # Disable if False
 
264
            if not self.default:
 
265
                self.enables.disable()
 
266
        Control.post(self)
 
267
 
 
268
    def enabler(self):
 
269
        """Enable/disable a Control based on the value of the Flag."""
 
270
        if not self.enables:
 
271
            return
 
272
        if self.get():
 
273
            self.enables.enable()
 
274
        else:
 
275
            self.enables.disable()
 
276
 
 
277
    def get_args(self):
 
278
        """Return a list of arguments for passing this command-line option.
 
279
        draw() must be called before this function.
 
280
        """
 
281
        args = []
 
282
        if self.get() == True:
 
283
            if self.option:
 
284
                args.append(self.option)
 
285
            if self.enables:
 
286
                args.extend(self.enables.get_args())
 
287
        return args
 
288
 
 
289
### --------------------------------------------------------------------
 
290
 
 
291
class FlagGroup (Control):
 
292
    """A wrapper widget for grouping Flag controls, and allowing
 
293
    mutually-exclusive flags.
 
294
    """
 
295
    def __init__(self,
 
296
                 label='',
 
297
                 state='normal',
 
298
                 *flags,
 
299
                 **kwargs):
 
300
        """Create a FlagGroup with the given label and state.
 
301
        
 
302
            label:    Label for the group
 
303
            state:    'normal' for regular Flags, 'exclusive' for
 
304
                      mutually-exclusive Flags
 
305
            *flags:   All additional arguments are Flag controls
 
306
        """
 
307
        Control.__init__(self, str, '', label, '', '')
 
308
        self.flags = flags
 
309
        self.state = state
 
310
        self.label = label
 
311
    
 
312
    def draw(self, master):
 
313
        """Draw Flag controls in the given master."""
 
314
        Control.draw(self, master)
 
315
        frame = tk.LabelFrame(self, text=self.label)
 
316
        frame.pack(fill='x', expand=True)
 
317
        for flag in self.flags:
 
318
            flag.draw(frame)
 
319
            flag.check.bind('<Button-1>', self.select)
 
320
            flag.pack(anchor='nw', side='top', fill='x', expand=True)
 
321
        Control.post(self)
 
322
 
 
323
    def select(self, event):
 
324
        """Event handler called when a Flag is selected."""
 
325
        # For normal flags, nothing to do
 
326
        if self.state != 'exclusive':
 
327
            return
 
328
        # For exclusive flags, clear all but the clicked Flag
 
329
        for flag in self.flags:
 
330
            if flag.check != event.widget:
 
331
                flag.set(False)
 
332
            flag.enabler()
 
333
 
 
334
    def get_args(self):
 
335
        """Return a list of arguments for setting the relevant flag(s)."""
 
336
        args = []
 
337
        for flag in self.flags:
 
338
            if flag.option != 'none':
 
339
                args.extend(flag.get_args())
 
340
        return args
 
341
 
 
342
### --------------------------------------------------------------------
 
343
from libtovid.odict import Odict, convert_list
 
344
from support import ComboBox
 
345
 
 
346
class Choice (Control):
 
347
    """A widget for choosing one of several options.
 
348
    """
 
349
    def __init__(self,
 
350
                 label="Choices",
 
351
                 option='',
 
352
                 default=None,
 
353
                 help='',
 
354
                 choices='A|B',
 
355
                 style='radio',
 
356
                 packside='left',
 
357
                 **kwargs):
 
358
        """Initialize Choice widget with the given label and list of choices.
 
359
 
 
360
            label:    Text label for the choices
 
361
            option:   Command-line option to set
 
362
            default:  Default choice, or None to use first choice in list
 
363
            help:     Help text to show in a tooltip
 
364
            choices:  Available choices, in string form: 'one|two|three'
 
365
                      or list form: ['one', 'two', 'three'], or as a
 
366
                      list-of-lists: [['a', "Use A"], ['b', "Use B"], ..].
 
367
                      A dictionary is also allowed, as long as you don't
 
368
                      care about preserving choice order.
 
369
            style:    'radio' for radiobuttons, 'dropdown' for a drop-down list
 
370
        """
 
371
        self.choices = convert_list(choices)
 
372
        Control.__init__(self, str, label, option,
 
373
                         default or self.choices.values()[0],
 
374
                         help, **kwargs)
 
375
        if style not in ['radio', 'dropdown']:
 
376
            raise ValueError("Choice style must be 'radio' or 'dropdown'")
 
377
        self.style = style
 
378
        self.packside = packside
 
379
 
 
380
    def draw(self, master):
 
381
        """Draw control widgets in the given master."""
 
382
        Control.draw(self, master)
 
383
        if self.style == 'radio':
 
384
            frame = tk.LabelFrame(self, text=self.label)
 
385
            frame.pack(anchor='nw', fill='x')
 
386
            self.rb = {}
 
387
            for choice, label in self.choices.items():
 
388
                self.rb[choice] = tk.Radiobutton(frame,
 
389
                    text=label, value=choice, variable=self.variable)
 
390
                self.rb[choice].pack(anchor='nw', side=self.packside)
 
391
        else: # dropdown/combobox
 
392
            tk.Label(self, text=self.label).pack(side='left')
 
393
            self.combo = ComboBox(self, self.choices.keys(),
 
394
                                  variable=self.variable)
 
395
            self.combo.pack(side='left')
 
396
        Control.post(self)
 
397
 
 
398
### --------------------------------------------------------------------
 
399
 
 
400
class Number (Control):
 
401
    """A widget for choosing or entering a number"""
 
402
    def __init__(self,
 
403
                 label="Number",
 
404
                 option='',
 
405
                 default=None,
 
406
                 help='',
 
407
                 min=1,
 
408
                 max=10,
 
409
                 style='spin',
 
410
                 units='',
 
411
                 **kwargs):
 
412
        """Create a number-setting widget.
 
413
        
 
414
            label:    Text label describing the meaning of the number
 
415
            option:   Command-line option to set
 
416
            default:  Default value, or None to use minimum
 
417
            help:     Help text to show in a tooltip
 
418
            min, max: Range of allowable numbers (inclusive)
 
419
            style:    'spin' for a spinbox, or 'scale' for a slider
 
420
            units:    Units of measurement (ex. "kbits/sec"), used as a label
 
421
        """
 
422
        if default is None:
 
423
            default = min
 
424
        Control.__init__(self, int, label, option, default,
 
425
                         help, **kwargs)
 
426
        self.min = min
 
427
        self.max = max
 
428
        self.style = style
 
429
        self.units = units
 
430
 
 
431
    def draw(self, master):
 
432
        """Draw control widgets in the given master."""
 
433
        Control.draw(self, master)
 
434
        tk.Label(self, name='label', text=self.label).pack(side='left')
 
435
        if self.style == 'spin':
 
436
            self.number = tk.Spinbox(self, from_=self.min, to=self.max,
 
437
                                     width=4, textvariable=self.variable)
 
438
            self.number.pack(side='left')
 
439
            tk.Label(self, name='units', text=self.units).pack(side='left')
 
440
 
 
441
        else: # 'scale'
 
442
            tk.Label(self, name='units', text=self.units).pack(side='left')
 
443
            self.number = tk.Scale(self, from_=self.min, to=self.max,
 
444
                                   tickinterval=(self.max - self.min),
 
445
                                   variable=self.variable, orient='horizontal')
 
446
            self.number.pack(side='left', fill='x', expand=True)
 
447
        Control.post(self)
 
448
 
 
449
    def enable(self, enabled=True):
 
450
        """Enable or disable all sub-widgets."""
 
451
        # Overridden to make Scale widget look disabled
 
452
        Control.enable(self, enabled)
 
453
        if self.style == 'scale':
 
454
            if enabled:
 
455
                self.number['fg'] = 'black'
 
456
                self.number['troughcolor'] = 'white'
 
457
            else:
 
458
                self.number['fg'] = '#A3A3A3'
 
459
                self.number['troughcolor'] = '#D9D9D9'
 
460
                
 
461
 
 
462
### --------------------------------------------------------------------
 
463
 
 
464
class Text (Control):
 
465
    """A widget for entering a line of text"""
 
466
    def __init__(self,
 
467
                 label="Text",
 
468
                 option='',
 
469
                 default='',
 
470
                 help='',
 
471
                 **kwargs):
 
472
        """
 
473
            label:    Label for the text
 
474
            option:   Command-line option to set
 
475
            default:  Default value of text widget
 
476
            help:     Help text to show in a tooltip
 
477
        """
 
478
        Control.__init__(self, str, label, option, default, help, **kwargs)
 
479
 
 
480
    def draw(self, master):
 
481
        """Draw control widgets in the given master."""
 
482
        Control.draw(self, master)
 
483
        tk.Label(self, text=self.label, justify='left').pack(side='left')
 
484
        self.entry = tk.Entry(self, textvariable=self.variable)
 
485
        self.entry.pack(side='left', fill='x', expand=True)
 
486
        Control.post(self)
 
487
 
 
488
### --------------------------------------------------------------------
 
489
import shlex
 
490
 
 
491
class List (Text):
 
492
    """A widget for entering a space-separated list of text items"""
 
493
    def __init__(self,
 
494
                 label="List",
 
495
                 option='',
 
496
                 default='',
 
497
                 help='',
 
498
                 **kwargs):
 
499
        Text.__init__(self, label, option, default, help, **kwargs)
 
500
 
 
501
    def draw(self, master):
 
502
        """Draw control widgets in the given master."""
 
503
        Text.draw(self, master)
 
504
        Text.post(self)
 
505
 
 
506
    def get(self):
 
507
        """Split text into a list at whitespace boundaries."""
 
508
        text = Text.get(self)
 
509
        return shlex.split(text)
 
510
 
 
511
    def set(self, listvalue):
 
512
        """Set a value to a list, joined with spaces."""
 
513
        text = ' '.join(listvalue)
 
514
        Text.set(self, text)
 
515
    
 
516
 
 
517
### --------------------------------------------------------------------
 
518
from tkFileDialog import asksaveasfilename, askopenfilename
 
519
 
 
520
class Filename (Control):
 
521
    """A widget for entering or browsing for a filename"""
 
522
    def __init__(self,
 
523
                 label='Filename',
 
524
                 option='',
 
525
                 default='',
 
526
                 help='',
 
527
                 action='load',
 
528
                 desc='Select a file to load',
 
529
                 **kwargs):
 
530
        """Create a Filename with label, text entry, and browse button.
 
531
        
 
532
            label:   Text of label next to file entry box
 
533
            option:  Command-line option to set
 
534
            default: Default filename
 
535
            help:    Help text to show in a tooltip
 
536
            action:  Do you intend to 'load' or 'save' this file?
 
537
            desc:    Brief description (shown in title bar of file
 
538
                     browser dialog)
 
539
        """
 
540
        Control.__init__(self, str, label, option, default, help, **kwargs)
 
541
        self.action = action
 
542
        self.desc = desc
 
543
 
 
544
    def draw(self, master):
 
545
        """Draw control widgets in the given master."""
 
546
        Control.draw(self, master)
 
547
        # Create and pack widgets
 
548
        tk.Label(self, text=self.label, justify='left').pack(side='left')
 
549
        self.entry = tk.Entry(self, textvariable=self.variable)
 
550
        self.button = tk.Button(self, text="Browse...", command=self.browse)
 
551
        self.entry.pack(side='left', fill='x', expand=True)
 
552
        self.button.pack(side='left')
 
553
        Control.post(self)
 
554
 
 
555
    def browse(self, event=None):
 
556
        """Event handler when browse button is pressed"""
 
557
        if self.action == 'save':
 
558
            filename = asksaveasfilename(parent=self, title=self.desc)
 
559
        else: # 'load'
 
560
            filename = askopenfilename(parent=self, title=self.desc)
 
561
        # Got a filename? Display it
 
562
        if filename:
 
563
            self.set(filename)
 
564
 
 
565
### --------------------------------------------------------------------
 
566
import tkColorChooser
 
567
 
 
568
class Color (Control):
 
569
    """A widget for choosing a color"""
 
570
    def __init__(self,
 
571
                 label="Color",
 
572
                 option='',
 
573
                 default='',
 
574
                 help='',
 
575
                 **kwargs):
 
576
        """Create a widget that opens a color-chooser dialog.
 
577
        
 
578
            label:   Text label describing the color to be selected
 
579
            option:  Command-line option to set
 
580
            default: Default color (named color or hexadecimal RGB)
 
581
            help:    Help text to show in a tooltip
 
582
        """
 
583
        Control.__init__(self, str, label, option, default, help, **kwargs)
 
584
 
 
585
    def draw(self, master):
 
586
        """Draw control widgets in the given master."""
 
587
        Control.draw(self, master)
 
588
        tk.Label(self, text=self.label).pack(side='left')
 
589
        self.button = tk.Button(self, text="None", command=self.change)
 
590
        self.button.pack(side='left')
 
591
        Control.post(self)
 
592
 
 
593
    def change(self):
 
594
        """Choose a color, and set the button's label and color to match."""
 
595
        rgb, color = tkColorChooser.askcolor(self.get())
 
596
        if color:
 
597
            self.set(color)
 
598
            self.button.config(text=color, foreground=color)
 
599
    
 
600
### --------------------------------------------------------------------
 
601
 
 
602
class Font (Control):
 
603
    """A font selector widget"""
 
604
    def __init__(self,
 
605
                 label='Font',
 
606
                 option='',
 
607
                 default='Helvetica',
 
608
                 help='',
 
609
                 **kwargs):
 
610
        """Create a widget that opens a font chooser dialog.
 
611
        
 
612
            label:   Text label for the font
 
613
            option:  Command-line option to set
 
614
            default: Default font
 
615
            help:    Help text to show in a tooltip
 
616
        """
 
617
        Control.__init__(self, str, label, option, default, help, **kwargs)
 
618
 
 
619
    def draw(self, master):
 
620
        """Draw control widgets in the given master."""
 
621
        Control.draw(self, master)
 
622
        tk.Label(self, text=self.label).pack(side='left')
 
623
        self.button = tk.Button(self, textvariable=self.variable,
 
624
                                command=self.choose)
 
625
        self.button.pack(side='left', padx=8)
 
626
        Control.post(self)
 
627
 
 
628
    def choose(self):
 
629
        """Open a font chooser to select a font."""
 
630
        chooser = support.FontChooser()
 
631
        if chooser.result:
 
632
            self.variable.set(chooser.result)
 
633
 
 
634
### --------------------------------------------------------------------
 
635
from support import DragList
 
636
 
 
637
class TextList (Control):
 
638
    """A widget for listing and editing several text strings"""
 
639
    def __init__(self,
 
640
                 label="Text list",
 
641
                 option='',
 
642
                 default=None,
 
643
                 help='',
 
644
                 **kwargs):
 
645
        Control.__init__(self, list, label, option, default, help, **kwargs)
 
646
 
 
647
    def draw(self, master):
 
648
        """Draw control widgets in the given master."""
 
649
        Control.draw(self, master)
 
650
        frame = tk.LabelFrame(self, text=self.label)
 
651
        frame.pack(fill='x', expand=True)
 
652
        self.selected = tk.StringVar()
 
653
        self.listbox = DragList(frame, choices=self.variable,
 
654
                                chosen=self.selected)
 
655
        self.listbox.pack(fill='x', expand=True)
 
656
        # TODO: Event handling to allow editing items
 
657
        self.editbox = tk.Entry(frame, width=30, textvariable=self.selected)
 
658
        self.editbox.bind('<Return>', self.setTitle)
 
659
        self.editbox.pack(fill='x', expand=True)
 
660
        Control.post(self)
 
661
 
 
662
    def setTitle(self, event):
 
663
        """Event handler when Enter is pressed after editing a title."""
 
664
        index = self.listbox.curindex
 
665
        self.variable[index] = self.selected.get()
 
666
        # TODO: Select next item in list and focus the editbox
 
667
 
 
668
### --------------------------------------------------------------------
 
669
from tkFileDialog import askopenfilenames
 
670
 
 
671
class FileList (Control):
 
672
    """A widget for listing several filenames"""
 
673
    def __init__(self,
 
674
                 label="File list",
 
675
                 option='',
 
676
                 default=None,
 
677
                 help='',
 
678
                 **kwargs):
 
679
        Control.__init__(self, list, label, option, default, help, **kwargs)
 
680
 
 
681
    def draw(self, master):
 
682
        """Draw control widgets in the given master."""
 
683
        Control.draw(self, master)
 
684
        frame = tk.LabelFrame(self, text=self.label)
 
685
        frame.pack(fill='x', expand=True)
 
686
        # List of files
 
687
        self.listbox = DragList(frame, choices=self.variable,
 
688
                                command=self.select)
 
689
        self.listbox.pack(fill='x', expand=True)
 
690
        # Add/remove buttons
 
691
        group = tk.Frame(frame)
 
692
        self.add = tk.Button(group, text="Add...", command=self.addFiles)
 
693
        self.remove = tk.Button(group, text="Remove", command=self.removeFiles)
 
694
        self.add.pack(side='left', fill='x', expand=True)
 
695
        self.remove.pack(side='left', fill='x', expand=True)
 
696
        group.pack(fill='x')
 
697
        Control.post(self)
 
698
 
 
699
    def select(self, event=None):
 
700
        """Event handler when a filename in the list is selected.
 
701
        """
 
702
        pass
 
703
 
 
704
    def addFiles(self):
 
705
        """Event handler to add files to the list"""
 
706
        files = askopenfilenames(parent=self, title='Add files')
 
707
        self.listbox.add(*files)
 
708
        for dest in self.copies:
 
709
            self.listbox.linked = dest.listbox
 
710
            dest.listbox.linked = self.listbox
 
711
            if dest.filter:
 
712
                titles = [dest.filter(file) for file in files]
 
713
                dest.listbox.add(*titles)
 
714
            else:
 
715
                dest.listbox.add(*files)
 
716
 
 
717
    def removeFiles(self):
 
718
        """Event handler to remove selected files from the list"""
 
719
        # TODO: Support multiple selection
 
720
        selected = self.listbox.curindex
 
721
        self.listbox.delete(selected)
 
722
        for control in self.copies:
 
723
            control.listbox.delete(selected)
 
724
 
 
725
### --------------------------------------------------------------------
 
726
 
 
727
# Exported control classes, indexed by name
 
728
CONTROLS = {
 
729
    'Choice': Choice,
 
730
    'Color': Color,
 
731
    'Filename': Filename,
 
732
    'Flag': Flag,
 
733
    'FlagGroup': FlagGroup,
 
734
    'Font': Font,
 
735
    'Text': Text,
 
736
    'List': List,
 
737
    'Number': Number,
 
738
    'FileList': FileList,
 
739
    'TextList': TextList}
 
740
 
 
741
### --------------------------------------------------------------------
 
742
 
 
743
# Demo
 
744
if __name__ == '__main__':
 
745
    root = tk.Tk()
 
746
    for name, control in CONTROLS.items():
 
747
        frame = tk.LabelFrame(root, text=name, padx=10, pady=10,
 
748
                          font=('Helvetica', 10, 'bold'))
 
749
        frame.pack(fill='both', expand=True)
 
750
        widget = control()
 
751
        widget.draw(frame)
 
752
        widget.pack(fill='both')
 
753
    root.mainloop()
 
754