symbian-qemu-0.9.1-12/python-2.6.1/Lib/idlelib/tabbedpages.py
changeset 1 2fb8b9db1c86
equal deleted inserted replaced
0:ffa851df0825 1:2fb8b9db1c86
       
     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()