~malept/ubuntu/lucid/python2.6/dev-dependency-fix

« back to all changes in this revision

Viewing changes to Lib/idlelib/tabbedpages.py

  • Committer: Bazaar Package Importer
  • Author(s): Matthias Klose
  • Date: 2009-02-13 12:51:00 UTC
  • Revision ID: james.westby@ubuntu.com-20090213125100-uufgcb9yeqzujpqw
Tags: upstream-2.6.1
ImportĀ upstreamĀ versionĀ 2.6.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""An implementation of tabbed pages using only standard Tkinter.
 
2
 
 
3
Originally developed for use in IDLE. Based on tabpage.py.
 
4
 
 
5
Classes exported:
 
6
TabbedPageSet -- A Tkinter implementation of a tabbed-page widget.
 
7
TabSet -- A widget containing tabs (buttons) in one or more rows.
 
8
 
 
9
"""
 
10
from Tkinter import *
 
11
 
 
12
class InvalidNameError(Exception): pass
 
13
class AlreadyExistsError(Exception): pass
 
14
 
 
15
 
 
16
class TabSet(Frame):
 
17
    """A widget containing tabs (buttons) in one or more rows.
 
18
 
 
19
    Only one tab may be selected at a time.
 
20
 
 
21
    """
 
22
    def __init__(self, page_set, select_command,
 
23
                 tabs=None, n_rows=1, max_tabs_per_row=5,
 
24
                 expand_tabs=False, **kw):
 
25
        """Constructor arguments:
 
26
 
 
27
        select_command -- A callable which will be called when a tab is
 
28
        selected. It is called with the name of the selected tab as an
 
29
        argument.
 
30
 
 
31
        tabs -- A list of strings, the names of the tabs. Should be specified in
 
32
        the desired tab order. The first tab will be the default and first
 
33
        active tab. If tabs is None or empty, the TabSet will be initialized
 
34
        empty.
 
35
 
 
36
        n_rows -- Number of rows of tabs to be shown. If n_rows <= 0 or is
 
37
        None, then the number of rows will be decided by TabSet. See
 
38
        _arrange_tabs() for details.
 
39
 
 
40
        max_tabs_per_row -- Used for deciding how many rows of tabs are needed,
 
41
        when the number of rows is not constant. See _arrange_tabs() for
 
42
        details.
 
43
 
 
44
        """
 
45
        Frame.__init__(self, page_set, **kw)
 
46
        self.select_command = select_command
 
47
        self.n_rows = n_rows
 
48
        self.max_tabs_per_row = max_tabs_per_row
 
49
        self.expand_tabs = expand_tabs
 
50
        self.page_set = page_set
 
51
 
 
52
        self._tabs = {}
 
53
        self._tab2row = {}
 
54
        if tabs:
 
55
            self._tab_names = list(tabs)
 
56
        else:
 
57
            self._tab_names = []
 
58
        self._selected_tab = None
 
59
        self._tab_rows = []
 
60
 
 
61
        self.padding_frame = Frame(self, height=2,
 
62
                                   borderwidth=0, relief=FLAT,
 
63
                                   background=self.cget('background'))
 
64
        self.padding_frame.pack(side=TOP, fill=X, expand=False)
 
65
 
 
66
        self._arrange_tabs()
 
67
 
 
68
    def add_tab(self, tab_name):
 
69
        """Add a new tab with the name given in tab_name."""
 
70
        if not tab_name:
 
71
            raise InvalidNameError("Invalid Tab name: '%s'" % tab_name)
 
72
        if tab_name in self._tab_names:
 
73
            raise AlreadyExistsError("Tab named '%s' already exists" %tab_name)
 
74
 
 
75
        self._tab_names.append(tab_name)
 
76
        self._arrange_tabs()
 
77
 
 
78
    def remove_tab(self, tab_name):
 
79
        """Remove the tab named <tab_name>"""
 
80
        if not tab_name in self._tab_names:
 
81
            raise KeyError("No such Tab: '%s" % page_name)
 
82
 
 
83
        self._tab_names.remove(tab_name)
 
84
        self._arrange_tabs()
 
85
 
 
86
    def set_selected_tab(self, tab_name):
 
87
        """Show the tab named <tab_name> as the selected one"""
 
88
        if tab_name == self._selected_tab:
 
89
            return
 
90
        if tab_name is not None and tab_name not in self._tabs:
 
91
            raise KeyError("No such Tab: '%s" % page_name)
 
92
 
 
93
        # deselect the current selected tab
 
94
        if self._selected_tab is not None:
 
95
            self._tabs[self._selected_tab].set_normal()
 
96
        self._selected_tab = None
 
97
 
 
98
        if tab_name is not None:
 
99
            # activate the tab named tab_name
 
100
            self._selected_tab = tab_name
 
101
            tab = self._tabs[tab_name]
 
102
            tab.set_selected()
 
103
            # move the tab row with the selected tab to the bottom
 
104
            tab_row = self._tab2row[tab]
 
105
            tab_row.pack_forget()
 
106
            tab_row.pack(side=TOP, fill=X, expand=0)
 
107
 
 
108
    def _add_tab_row(self, tab_names, expand_tabs):
 
109
        if not tab_names:
 
110
            return
 
111
 
 
112
        tab_row = Frame(self)
 
113
        tab_row.pack(side=TOP, fill=X, expand=0)
 
114
        self._tab_rows.append(tab_row)
 
115
 
 
116
        for tab_name in tab_names:
 
117
            tab = TabSet.TabButton(tab_name, self.select_command,
 
118
                                   tab_row, self)
 
119
            if expand_tabs:
 
120
                tab.pack(side=LEFT, fill=X, expand=True)
 
121
            else:
 
122
                tab.pack(side=LEFT)
 
123
            self._tabs[tab_name] = tab
 
124
            self._tab2row[tab] = tab_row
 
125
 
 
126
        # tab is the last one created in the above loop
 
127
        tab.is_last_in_row = True
 
128
 
 
129
    def _reset_tab_rows(self):
 
130
        while self._tab_rows:
 
131
            tab_row = self._tab_rows.pop()
 
132
            tab_row.destroy()
 
133
        self._tab2row = {}
 
134
 
 
135
    def _arrange_tabs(self):
 
136
        """
 
137
        Arrange the tabs in rows, in the order in which they were added.
 
138
 
 
139
        If n_rows >= 1, this will be the number of rows used. Otherwise the
 
140
        number of rows will be calculated according to the number of tabs and
 
141
        max_tabs_per_row. In this case, the number of rows may change when
 
142
        adding/removing tabs.
 
143
 
 
144
        """
 
145
        # remove all tabs and rows
 
146
        for tab_name in self._tabs.keys():
 
147
            self._tabs.pop(tab_name).destroy()
 
148
        self._reset_tab_rows()
 
149
 
 
150
        if not self._tab_names:
 
151
            return
 
152
 
 
153
        if self.n_rows is not None and self.n_rows > 0:
 
154
            n_rows = self.n_rows
 
155
        else:
 
156
            # calculate the required number of rows
 
157
            n_rows = (len(self._tab_names) - 1) // self.max_tabs_per_row + 1
 
158
 
 
159
        # not expanding the tabs with more than one row is very ugly
 
160
        expand_tabs = self.expand_tabs or n_rows > 1
 
161
        i = 0 # index in self._tab_names
 
162
        for row_index in xrange(n_rows):
 
163
            # calculate required number of tabs in this row
 
164
            n_tabs = (len(self._tab_names) - i - 1) // (n_rows - row_index) + 1
 
165
            tab_names = self._tab_names[i:i + n_tabs]
 
166
            i += n_tabs
 
167
            self._add_tab_row(tab_names, expand_tabs)
 
168
 
 
169
        # re-select selected tab so it is properly displayed
 
170
        selected = self._selected_tab
 
171
        self.set_selected_tab(None)
 
172
        if selected in self._tab_names:
 
173
            self.set_selected_tab(selected)
 
174
 
 
175
    class TabButton(Frame):
 
176
        """A simple tab-like widget."""
 
177
 
 
178
        bw = 2 # borderwidth
 
179
 
 
180
        def __init__(self, name, select_command, tab_row, tab_set):
 
181
            """Constructor arguments:
 
182
 
 
183
            name -- The tab's name, which will appear in its button.
 
184
 
 
185
            select_command -- The command to be called upon selection of the
 
186
            tab. It is called with the tab's name as an argument.
 
187
 
 
188
            """
 
189
            Frame.__init__(self, tab_row, borderwidth=self.bw, relief=RAISED)
 
190
 
 
191
            self.name = name
 
192
            self.select_command = select_command
 
193
            self.tab_set = tab_set
 
194
            self.is_last_in_row = False
 
195
 
 
196
            self.button = Radiobutton(
 
197
                self, text=name, command=self._select_event,
 
198
                padx=5, pady=1, takefocus=FALSE, indicatoron=FALSE,
 
199
                highlightthickness=0, selectcolor='', borderwidth=0)
 
200
            self.button.pack(side=LEFT, fill=X, expand=True)
 
201
 
 
202
            self._init_masks()
 
203
            self.set_normal()
 
204
 
 
205
        def _select_event(self, *args):
 
206
            """Event handler for tab selection.
 
207
 
 
208
            With TabbedPageSet, this calls TabbedPageSet.change_page, so that
 
209
            selecting a tab changes the page.
 
210
 
 
211
            Note that this does -not- call set_selected -- it will be called by
 
212
            TabSet.set_selected_tab, which should be called when whatever the
 
213
            tabs are related to changes.
 
214
 
 
215
            """
 
216
            self.select_command(self.name)
 
217
            return
 
218
 
 
219
        def set_selected(self):
 
220
            """Assume selected look"""
 
221
            self._place_masks(selected=True)
 
222
 
 
223
        def set_normal(self):
 
224
            """Assume normal look"""
 
225
            self._place_masks(selected=False)
 
226
 
 
227
        def _init_masks(self):
 
228
            page_set = self.tab_set.page_set
 
229
            background = page_set.pages_frame.cget('background')
 
230
            # mask replaces the middle of the border with the background color
 
231
            self.mask = Frame(page_set, borderwidth=0, relief=FLAT,
 
232
                              background=background)
 
233
            # mskl replaces the bottom-left corner of the border with a normal
 
234
            # left border
 
235
            self.mskl = Frame(page_set, borderwidth=0, relief=FLAT,
 
236
                              background=background)
 
237
            self.mskl.ml = Frame(self.mskl, borderwidth=self.bw,
 
238
                                 relief=RAISED)
 
239
            self.mskl.ml.place(x=0, y=-self.bw,
 
240
                               width=2*self.bw, height=self.bw*4)
 
241
            # mskr replaces the bottom-right corner of the border with a normal
 
242
            # right border
 
243
            self.mskr = Frame(page_set, borderwidth=0, relief=FLAT,
 
244
                              background=background)
 
245
            self.mskr.mr = Frame(self.mskr, borderwidth=self.bw,
 
246
                                 relief=RAISED)
 
247
 
 
248
        def _place_masks(self, selected=False):
 
249
            height = self.bw
 
250
            if selected:
 
251
                height += self.bw
 
252
 
 
253
            self.mask.place(in_=self,
 
254
                            relx=0.0, x=0,
 
255
                            rely=1.0, y=0,
 
256
                            relwidth=1.0, width=0,
 
257
                            relheight=0.0, height=height)
 
258
 
 
259
            self.mskl.place(in_=self,
 
260
                            relx=0.0, x=-self.bw,
 
261
                            rely=1.0, y=0,
 
262
                            relwidth=0.0, width=self.bw,
 
263
                            relheight=0.0, height=height)
 
264
 
 
265
            page_set = self.tab_set.page_set
 
266
            if selected and ((not self.is_last_in_row) or
 
267
                             (self.winfo_rootx() + self.winfo_width() <
 
268
                              page_set.winfo_rootx() + page_set.winfo_width())
 
269
                             ):
 
270
                # for a selected tab, if its rightmost edge isn't on the
 
271
                # rightmost edge of the page set, the right mask should be one
 
272
                # borderwidth shorter (vertically)
 
273
                height -= self.bw
 
274
 
 
275
            self.mskr.place(in_=self,
 
276
                            relx=1.0, x=0,
 
277
                            rely=1.0, y=0,
 
278
                            relwidth=0.0, width=self.bw,
 
279
                            relheight=0.0, height=height)
 
280
 
 
281
            self.mskr.mr.place(x=-self.bw, y=-self.bw,
 
282
                               width=2*self.bw, height=height + self.bw*2)
 
283
 
 
284
            # finally, lower the tab set so that all of the frames we just
 
285
            # placed hide it
 
286
            self.tab_set.lower()
 
287
 
 
288
class TabbedPageSet(Frame):
 
289
    """A Tkinter tabbed-pane widget.
 
290
 
 
291
    Constains set of 'pages' (or 'panes') with tabs above for selecting which
 
292
    page is displayed. Only one page will be displayed at a time.
 
293
 
 
294
    Pages may be accessed through the 'pages' attribute, which is a dictionary
 
295
    of pages, using the name given as the key. A page is an instance of a
 
296
    subclass of Tk's Frame widget.
 
297
 
 
298
    The page widgets will be created (and destroyed when required) by the
 
299
    TabbedPageSet. Do not call the page's pack/place/grid/destroy methods.
 
300
 
 
301
    Pages may be added or removed at any time using the add_page() and
 
302
    remove_page() methods.
 
303
 
 
304
    """
 
305
    class Page(object):
 
306
        """Abstract base class for TabbedPageSet's pages.
 
307
 
 
308
        Subclasses must override the _show() and _hide() methods.
 
309
 
 
310
        """
 
311
        uses_grid = False
 
312
 
 
313
        def __init__(self, page_set):
 
314
            self.frame = Frame(page_set, borderwidth=2, relief=RAISED)
 
315
 
 
316
        def _show(self):
 
317
            raise NotImplementedError
 
318
 
 
319
        def _hide(self):
 
320
            raise NotImplementedError
 
321
 
 
322
    class PageRemove(Page):
 
323
        """Page class using the grid placement manager's "remove" mechanism."""
 
324
        uses_grid = True
 
325
 
 
326
        def _show(self):
 
327
            self.frame.grid(row=0, column=0, sticky=NSEW)
 
328
 
 
329
        def _hide(self):
 
330
            self.frame.grid_remove()
 
331
 
 
332
    class PageLift(Page):
 
333
        """Page class using the grid placement manager's "lift" mechanism."""
 
334
        uses_grid = True
 
335
 
 
336
        def __init__(self, page_set):
 
337
            super(TabbedPageSet.PageLift, self).__init__(page_set)
 
338
            self.frame.grid(row=0, column=0, sticky=NSEW)
 
339
            self.frame.lower()
 
340
 
 
341
        def _show(self):
 
342
            self.frame.lift()
 
343
 
 
344
        def _hide(self):
 
345
            self.frame.lower()
 
346
 
 
347
    class PagePackForget(Page):
 
348
        """Page class using the pack placement manager's "forget" mechanism."""
 
349
        def _show(self):
 
350
            self.frame.pack(fill=BOTH, expand=True)
 
351
 
 
352
        def _hide(self):
 
353
            self.frame.pack_forget()
 
354
 
 
355
    def __init__(self, parent, page_names=None, page_class=PageLift,
 
356
                 n_rows=1, max_tabs_per_row=5, expand_tabs=False,
 
357
                 **kw):
 
358
        """Constructor arguments:
 
359
 
 
360
        page_names -- A list of strings, each will be the dictionary key to a
 
361
        page's widget, and the name displayed on the page's tab. Should be
 
362
        specified in the desired page order. The first page will be the default
 
363
        and first active page. If page_names is None or empty, the
 
364
        TabbedPageSet will be initialized empty.
 
365
 
 
366
        n_rows, max_tabs_per_row -- Parameters for the TabSet which will
 
367
        manage the tabs. See TabSet's docs for details.
 
368
 
 
369
        page_class -- Pages can be shown/hidden using three mechanisms:
 
370
 
 
371
        * PageLift - All pages will be rendered one on top of the other. When
 
372
          a page is selected, it will be brought to the top, thus hiding all
 
373
          other pages. Using this method, the TabbedPageSet will not be resized
 
374
          when pages are switched. (It may still be resized when pages are
 
375
          added/removed.)
 
376
 
 
377
        * PageRemove - When a page is selected, the currently showing page is
 
378
          hidden, and the new page shown in its place. Using this method, the
 
379
          TabbedPageSet may resize when pages are changed.
 
380
 
 
381
        * PagePackForget - This mechanism uses the pack placement manager.
 
382
          When a page is shown it is packed, and when it is hidden it is
 
383
          unpacked (i.e. pack_forget). This mechanism may also cause the
 
384
          TabbedPageSet to resize when the page is changed.
 
385
 
 
386
        """
 
387
        Frame.__init__(self, parent, **kw)
 
388
 
 
389
        self.page_class = page_class
 
390
        self.pages = {}
 
391
        self._pages_order = []
 
392
        self._current_page = None
 
393
        self._default_page = None
 
394
 
 
395
        self.columnconfigure(0, weight=1)
 
396
        self.rowconfigure(1, weight=1)
 
397
 
 
398
        self.pages_frame = Frame(self)
 
399
        self.pages_frame.grid(row=1, column=0, sticky=NSEW)
 
400
        if self.page_class.uses_grid:
 
401
            self.pages_frame.columnconfigure(0, weight=1)
 
402
            self.pages_frame.rowconfigure(0, weight=1)
 
403
 
 
404
        # the order of the following commands is important
 
405
        self._tab_set = TabSet(self, self.change_page, n_rows=n_rows,
 
406
                               max_tabs_per_row=max_tabs_per_row,
 
407
                               expand_tabs=expand_tabs)
 
408
        if page_names:
 
409
            for name in page_names:
 
410
                self.add_page(name)
 
411
        self._tab_set.grid(row=0, column=0, sticky=NSEW)
 
412
 
 
413
        self.change_page(self._default_page)
 
414
 
 
415
    def add_page(self, page_name):
 
416
        """Add a new page with the name given in page_name."""
 
417
        if not page_name:
 
418
            raise InvalidNameError("Invalid TabPage name: '%s'" % page_name)
 
419
        if page_name in self.pages:
 
420
            raise AlreadyExistsError(
 
421
                "TabPage named '%s' already exists" % page_name)
 
422
 
 
423
        self.pages[page_name] = self.page_class(self.pages_frame)
 
424
        self._pages_order.append(page_name)
 
425
        self._tab_set.add_tab(page_name)
 
426
 
 
427
        if len(self.pages) == 1: # adding first page
 
428
            self._default_page = page_name
 
429
            self.change_page(page_name)
 
430
 
 
431
    def remove_page(self, page_name):
 
432
        """Destroy the page whose name is given in page_name."""
 
433
        if not page_name in self.pages:
 
434
            raise KeyError("No such TabPage: '%s" % page_name)
 
435
 
 
436
        self._pages_order.remove(page_name)
 
437
 
 
438
        # handle removing last remaining, default, or currently shown page
 
439
        if len(self._pages_order) > 0:
 
440
            if page_name == self._default_page:
 
441
                # set a new default page
 
442
                self._default_page = self._pages_order[0]
 
443
        else:
 
444
            self._default_page = None
 
445
 
 
446
        if page_name == self._current_page:
 
447
            self.change_page(self._default_page)
 
448
 
 
449
        self._tab_set.remove_tab(page_name)
 
450
        page = self.pages.pop(page_name)
 
451
        page.frame.destroy()
 
452
 
 
453
    def change_page(self, page_name):
 
454
        """Show the page whose name is given in page_name."""
 
455
        if self._current_page == page_name:
 
456
            return
 
457
        if page_name is not None and page_name not in self.pages:
 
458
            raise KeyError("No such TabPage: '%s'" % page_name)
 
459
 
 
460
        if self._current_page is not None:
 
461
            self.pages[self._current_page]._hide()
 
462
        self._current_page = None
 
463
 
 
464
        if page_name is not None:
 
465
            self._current_page = page_name
 
466
            self.pages[page_name]._show()
 
467
 
 
468
        self._tab_set.set_selected_tab(page_name)
 
469
 
 
470
if __name__ == '__main__':
 
471
    # test dialog
 
472
    root=Tk()
 
473
    tabPage=TabbedPageSet(root, page_names=['Foobar','Baz'], n_rows=0,
 
474
                          expand_tabs=False,
 
475
                          )
 
476
    tabPage.pack(side=TOP, expand=TRUE, fill=BOTH)
 
477
    Label(tabPage.pages['Foobar'].frame, text='Foo', pady=20).pack()
 
478
    Label(tabPage.pages['Foobar'].frame, text='Bar', pady=20).pack()
 
479
    Label(tabPage.pages['Baz'].frame, text='Baz').pack()
 
480
    entryPgName=Entry(root)
 
481
    buttonAdd=Button(root, text='Add Page',
 
482
            command=lambda:tabPage.add_page(entryPgName.get()))
 
483
    buttonRemove=Button(root, text='Remove Page',
 
484
            command=lambda:tabPage.remove_page(entryPgName.get()))
 
485
    labelPgName=Label(root, text='name of page to add/remove:')
 
486
    buttonAdd.pack(padx=5, pady=5)
 
487
    buttonRemove.pack(padx=5, pady=5)
 
488
    labelPgName.pack(padx=5)
 
489
    entryPgName.pack(padx=5)
 
490
    root.mainloop()