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