]> granicus.if.org Git - python/commitdiff
Merged revisions 69053 via svnmerge from
authorGuilherme Polo <ggpolo@gmail.com>
Wed, 28 Jan 2009 16:06:51 +0000 (16:06 +0000)
committerGuilherme Polo <ggpolo@gmail.com>
Wed, 28 Jan 2009 16:06:51 +0000 (16:06 +0000)
svn+ssh://pythondev/python/trunk

........
  r69053 | guilherme.polo | 2009-01-28 13:56:01 -0200 (Wed, 28 Jan 2009) | 2 lines

  Demos for ttk added.
........

16 files changed:
Demo/tkinter/README
Demo/tkinter/ttk/combo_themes.py [new file with mode: 0644]
Demo/tkinter/ttk/dirbrowser.py [new file with mode: 0644]
Demo/tkinter/ttk/img/close.gif [new file with mode: 0644]
Demo/tkinter/ttk/img/close_active.gif [new file with mode: 0644]
Demo/tkinter/ttk/img/close_pressed.gif [new file with mode: 0644]
Demo/tkinter/ttk/listbox_scrollcmd.py [new file with mode: 0644]
Demo/tkinter/ttk/mac_searchentry.py [new file with mode: 0644]
Demo/tkinter/ttk/notebook_closebtn.py [new file with mode: 0644]
Demo/tkinter/ttk/plastik_theme.py [new file with mode: 0644]
Demo/tkinter/ttk/roundframe.py [new file with mode: 0644]
Demo/tkinter/ttk/theme_selector.py [new file with mode: 0644]
Demo/tkinter/ttk/treeview_multicolumn.py [new file with mode: 0644]
Demo/tkinter/ttk/ttkcalendar.py [new file with mode: 0644]
Demo/tkinter/ttk/widget_state.py [new file with mode: 0644]
Misc/NEWS

index f245d163cdfa71194c982506be13acaceb12ec79..c9f18df39b1a93eaf34947d26ddf9d44014ad9a4 100644 (file)
@@ -8,3 +8,4 @@ Subdirectories:
 
 guido          my original example set (fairly random collection)
 matt           Matt Conway's examples, to go with his lifesaver document
+ttk         Examples using the ttk module
diff --git a/Demo/tkinter/ttk/combo_themes.py b/Demo/tkinter/ttk/combo_themes.py
new file mode 100644 (file)
index 0000000..45eee2d
--- /dev/null
@@ -0,0 +1,46 @@
+"""Ttk Theme Selector.
+
+Although it is a theme selector, you won't notice many changes since
+there is only a combobox and a frame around.
+"""
+from tkinter import ttk
+
+class App(ttk.Frame):
+    def __init__(self):
+        ttk.Frame.__init__(self)
+
+        self.style = ttk.Style()
+        self._setup_widgets()
+
+    def _change_theme(self, event):
+        if event.widget.current(): # value #0 is not a theme
+            newtheme = event.widget.get()
+            # change to the new theme and refresh all the widgets
+            self.style.theme_use(newtheme)
+
+    def _setup_widgets(self):
+        themes = list(self.style.theme_names())
+        themes.insert(0, "Pick a theme")
+        # Create a readonly Combobox which will display 4 values at max,
+        # which will cause it to create a scrollbar if there are more
+        # than 4 values in total.
+        themes_combo = ttk.Combobox(self, values=themes, state="readonly",
+                                    height=4)
+        themes_combo.set(themes[0]) # sets the combobox value to "Pick a theme"
+        # Combobox widget generates a <<ComboboxSelected>> virtual event
+        # when the user selects an element. This event is generated after
+        # the listbox is unposted (after you select an item, the combobox's
+        # listbox disappears, then it is said that listbox is now unposted).
+        themes_combo.bind("<<ComboboxSelected>>", self._change_theme)
+        themes_combo.pack(fill='x')
+
+        self.pack(fill='both', expand=1)
+
+
+def main():
+    app = App()
+    app.master.title("Ttk Combobox")
+    app.mainloop()
+
+if __name__ == "__main__":
+    main()
diff --git a/Demo/tkinter/ttk/dirbrowser.py b/Demo/tkinter/ttk/dirbrowser.py
new file mode 100644 (file)
index 0000000..bacddb5
--- /dev/null
@@ -0,0 +1,93 @@
+"""A directory browser using Ttk Treeview.
+
+Based on the demo found in Tk 8.5 library/demos/browse
+"""
+import os
+import glob
+import tkinter
+from tkinter import ttk
+
+def populate_tree(tree, node):
+    if tree.set(node, "type") != 'directory':
+        return
+
+    path = tree.set(node, "fullpath")
+    tree.delete(*tree.get_children(node))
+
+    parent = tree.parent(node)
+    special_dirs = [] if parent else glob.glob('.') + glob.glob('..')
+
+    for p in special_dirs + os.listdir(path):
+        ptype = None
+        p = os.path.join(path, p).replace('\\', '/')
+        if os.path.isdir(p): ptype = "directory"
+        elif os.path.isfile(p): ptype = "file"
+
+        fname = os.path.split(p)[1]
+        id = tree.insert(node, "end", text=fname, values=[p, ptype])
+
+        if ptype == 'directory':
+            if fname not in ('.', '..'):
+                tree.insert(id, 0, text="dummy")
+                tree.item(id, text=fname)
+        elif ptype == 'file':
+            size = os.stat(p).st_size
+            tree.set(id, "size", "%d bytes" % size)
+
+
+def populate_roots(tree):
+    dir = os.path.abspath('.').replace('\\', '/')
+    node = tree.insert('', 'end', text=dir, values=[dir, "directory"])
+    populate_tree(tree, node)
+
+def update_tree(event):
+    tree = event.widget
+    populate_tree(tree, tree.focus())
+
+def change_dir(event):
+    tree = event.widget
+    node = tree.focus()
+    if tree.parent(node):
+        path = os.path.abspath(tree.set(node, "fullpath"))
+        if os.path.isdir(path):
+            os.chdir(path)
+            tree.delete(tree.get_children(''))
+            populate_roots(tree)
+
+def autoscroll(sbar, first, last):
+    """Hide and show scrollbar as needed."""
+    first, last = float(first), float(last)
+    if first <= 0 and last >= 1:
+        sbar.grid_remove()
+    else:
+        sbar.grid()
+    sbar.set(first, last)
+
+root = tkinter.Tk()
+
+vsb = ttk.Scrollbar(orient="vertical")
+hsb = ttk.Scrollbar(orient="horizontal")
+
+tree = ttk.Treeview(columns=("fullpath", "type", "size"),
+    displaycolumns="size", yscrollcommand=lambda f, l: autoscroll(vsb, f, l),
+    xscrollcommand=lambda f, l:autoscroll(hsb, f, l))
+
+vsb['command'] = tree.yview
+hsb['command'] = tree.xview
+
+tree.heading("#0", text="Directory Structure", anchor='w')
+tree.heading("size", text="File Size", anchor='w')
+tree.column("size", stretch=0, width=100)
+
+populate_roots(tree)
+tree.bind('<<TreeviewOpen>>', update_tree)
+tree.bind('<Double-Button-1>', change_dir)
+
+# Arrange the tree and its scrollbars in the toplevel
+tree.grid(column=0, row=0, sticky='nswe')
+vsb.grid(column=1, row=0, sticky='ns')
+hsb.grid(column=0, row=1, sticky='ew')
+root.grid_columnconfigure(0, weight=1)
+root.grid_rowconfigure(0, weight=1)
+
+root.mainloop()
diff --git a/Demo/tkinter/ttk/img/close.gif b/Demo/tkinter/ttk/img/close.gif
new file mode 100644 (file)
index 0000000..18cf6c7
Binary files /dev/null and b/Demo/tkinter/ttk/img/close.gif differ
diff --git a/Demo/tkinter/ttk/img/close_active.gif b/Demo/tkinter/ttk/img/close_active.gif
new file mode 100644 (file)
index 0000000..db7f392
Binary files /dev/null and b/Demo/tkinter/ttk/img/close_active.gif differ
diff --git a/Demo/tkinter/ttk/img/close_pressed.gif b/Demo/tkinter/ttk/img/close_pressed.gif
new file mode 100644 (file)
index 0000000..5616954
Binary files /dev/null and b/Demo/tkinter/ttk/img/close_pressed.gif differ
diff --git a/Demo/tkinter/ttk/listbox_scrollcmd.py b/Demo/tkinter/ttk/listbox_scrollcmd.py
new file mode 100644 (file)
index 0000000..05faf63
--- /dev/null
@@ -0,0 +1,37 @@
+"""Sample taken from: http://www.tkdocs.com/tutorial/morewidgets.html and
+converted to Python, mainly to demonstrate xscrollcommand option.
+
+grid [tk::listbox .l -yscrollcommand ".s set" -height 5] -column 0 -row 0 -sticky nwes
+grid [ttk::scrollbar .s -command ".l yview" -orient vertical] -column 1 -row 0 -sticky ns
+grid [ttk::label .stat -text "Status message here" -anchor w] -column 0 -row 1 -sticky we
+grid [ttk::sizegrip .sz] -column 1 -row 1 -sticky se
+grid columnconfigure . 0 -weight 1; grid rowconfigure . 0 -weight 1
+for {set i 0} {$i<100} {incr i} {
+   .l insert end "Line $i of 100"
+   }
+"""
+import tkinter
+from tkinter import ttk
+
+root = tkinter.Tk()
+
+l = tkinter.Listbox(height=5)
+l.grid(column=0, row=0, sticky='nwes')
+
+s = ttk.Scrollbar(command=l.yview, orient='vertical')
+l['yscrollcommand'] = s.set
+s.grid(column=1, row=0, sticky="ns")
+
+stat = ttk.Label(text="Status message here", anchor='w')
+stat.grid(column=0, row=1, sticky='we')
+
+sz = ttk.Sizegrip()
+sz.grid(column=1, row=1, sticky='se')
+
+root.grid_columnconfigure(0, weight=1)
+root.grid_rowconfigure(0, weight=1)
+
+for i in range(100):
+    l.insert('end', "Line %d of 100" % i)
+
+root.mainloop()
diff --git a/Demo/tkinter/ttk/mac_searchentry.py b/Demo/tkinter/ttk/mac_searchentry.py
new file mode 100644 (file)
index 0000000..97b1eaf
--- /dev/null
@@ -0,0 +1,78 @@
+"""Mac style search widget
+
+Translated from Tcl code by Schelte Bron, http://wiki.tcl.tk/18188
+"""
+import tkinter
+from tkinter import ttk
+
+root = tkinter.Tk()
+
+data = """
+R0lGODlhKgAaAOfnAFdZVllbWFpcWVtdWlxeW11fXF9hXmBiX2ZnZWhpZ2lraGxua25wbXJ0
+cXR2c3V3dHZ4dXh6d3x+e31/fH6AfYSGg4eJhoiKh4qMiYuNio2PjHmUqnqVq3yXrZGTkJKU
+kX+asJSWk32cuJWXlIGcs5aYlX6euZeZloOetZial4SftpqbmIWgt4GhvYahuIKivpudmYei
+uYOjv5yem4ijuoSkwIWlwYmlu56gnYamwp+hnoenw4unvaCin4ioxJCnuZykrImpxZmlsoaq
+zI2pv6KkoZGouoqqxpqms4erzaOloo6qwYurx5Kqu5untIiszqSmo5CrwoysyJeqtpOrvJyo
+tZGsw42typSsvaaopZKtxJWtvp6qt4+uy6epppOuxZCvzKiqp5quuZSvxoyx06mrqJWwx42y
+1JKxzpmwwaqsqZaxyI6z1ZqxwqutqpOzz4+01qyuq56yvpizypS00Jm0y5W10Zq1zJa20rCy
+rpu3zqizwbGzr6C3yZy4z7K0saG4yp250LO1sqK5y5660Z+70qO7zKy4xaC806S8zba4taG9
+1KW9zq66x6+7yLi6t6S/1rC8yrm7uLO8xLG9y7q8ubS9xabB2anB07K+zLW+xrO/za7CzrTA
+zrjAyLXBz77BvbbC0K/G2LjD0bnE0rLK28TGw8bIxcLL07vP28HN28rMycvOyr/T38DU4cnR
+2s/RztHT0NLU0cTY5MrW5MvX5dHX2c3Z59bY1dPb5Nbb3dLe7Nvd2t3f3NXh797g3d3j5dnl
+9OPl4eTm4+Ln6tzo9uXn5Obo5eDp8efp5uHq8uXq7ejq5+nr6OPs9Ovu6unu8O3v6+vw8+7w
+7ezx9O/x7vDy7/Hz8O/19/P18vT38/L3+fb49Pf59vX6/fj69/b7/vn7+Pr8+ff9//v9+vz/
++/7//P//////////////////////////////////////////////////////////////////
+/////////////////////////////////yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJZAD/ACwC
+AAIAKAAWAAAI/gD/CRz4bwUGCg8eQFjIsGHDBw4iTLAQgqBFgisuePCiyJOpUyBDihRpypMi
+Lx8qaLhIMIyGFZ5sAUsmjZrNmzhzWpO2DJgtTysqfGDpxoMbW8ekeQsXzty4p1CjRjUXrps3
+asJsuclQ4uKKSbamMR3n1JzZs2jRkh1HzuxVXX8y4CDYAwqua+DInVrRwMGJU2kDp31KThy1
+XGWGDlxhi1rTPAUICBBAoEAesoIzn6Vm68MKgVAUHftmzhOCBCtQwQKSoABgzZnJdSMmyIPA
+FbCotdUQAIhNa9B6DPCAGbZac+SowVIMRVe4pwkA4GpqDlwuAAmMZx4nTtfnf1mO5JEDNy46
+MHJkxQEDgKC49rPjwC0bqGaZuOoZAKjBPE4NgAzUvYcWOc0QZF91imAnCDHJ5JFAAJN0I2Ba
+4iRDUC/gOEVNDwIUcEABCAgAAATUTIgWOMBYRFp80ghiAQIIVAAEAwJIYI2JZnUji0XSYAYO
+NcsQA8wy0hCTwAASXGOiONFcxAtpTokTHznfiLMNMAkcAMuE43jDC0vLeGOWe2R5o4sn1LgH
+GzkWsvTPMgEOaA433Ag4TjjMuDkQMNi0tZ12sqWoJ0HATMPNffAZZ6U0wLAyqJ62RGoLLrhI
+aqmlpzwaEAAh+QQJZAD/ACwAAAAAKgAaAAAI/gD/CRw40JEhQoEC+fGjcOHCMRAjRkxDsKLF
+f5YcAcID582ZjyBDJhmZZIjJIUySEDHiBMhFghrtdNnRAgSHmzhz6sTZQcSLITx+CHn5bxSk
+Nz5MCMGy55CjTVCjbuJEtSrVQ3uwqDBRQwrFi476SHHxow8qXcemVbPGtm21t3CnTaP27Jgu
+VHtuiIjBsuImQkRiiEEFTNo2cOTMKV7MuLE5cN68QUOGSgwKG1EqJqJDY8+rZt8UjxtNunTj
+cY3DgZOWS46KIFgGjiI0ZIsqaqNNjWjgYMUpx8Adc3v2aosNMAI1DbqyI9WycOb4IAggQEAB
+A3lQBxet/TG4cMpI/tHwYeSfIzxM0uTKNs7UgAQrYL1akaDA7+3bueVqY4NJlUhIcQLNYx8E
+AIQ01mwjTQ8DeNAdfouNA8440GBCQxJY3MEGD6p4Y844CQCAizcSgpMLAAlAuJ03qOyQRBR3
+nEHEK+BMGKIui4kDDAAIPKiiYuSYSMQQRCDCxhiziPMYBgDkEaEaAGQA3Y+MjUPOLFoMoUUh
+cKxRC4ngeILiH8Qkk0cCAUzSDZWpzbLEE1EwggcYqWCj2DNADFDAAQUgIAAAEFDDJmPYqNJF
+F1s4cscTmCDjDTjdSPOHBQggUAEQDAgggTWDPoYMJkFoUdRmddyyjWLeULMMMcAsIw0x4wkM
+IME1g25zyxpHxFYUHmyIggw4H4ojITnfiLMNMAkcAAub4BQjihRdDGTJHmvc4Qo1wD6Imje6
+eILbj+BQ4wqu5Q3ECSJ0FOKKMtv4mBg33Pw4zjbKuBIIE1xYpIkhdQQiyi7OtAucj6dt48wu
+otQhBRa6VvSJIRwhIkotvgRTzMUYZ6xxMcj4QkspeKDxxRhEmUfIHWjAgQcijEDissuXvCyz
+zH7Q8YQURxDhUsn/bCInR3AELfTQZBRt9BBJkCGFFVhMwTNBlnBCSCGEIJQQIAklZMXWRBAR
+RRRWENHwRQEBADs="""
+
+
+s1 = tkinter.PhotoImage("search1", data=data, format="gif -index 0")
+s2 = tkinter.PhotoImage("search2", data=data, format="gif -index 1")
+
+style = ttk.Style()
+
+style.element_create("Search.field", "image", "search1",
+    ("focus", "search2"), border=[22, 7, 14], sticky="ew")
+
+style.layout("Search.entry", [
+    ("Search.field", {"sticky": "nswe", "border": 1, "children":
+        [("Entry.padding", {"sticky": "nswe", "children":
+            [("Entry.textarea", {"sticky": "nswe"})]
+        })]
+    })]
+)
+
+style.configure("Search.entry", background="#b2b2b2")
+
+root.configure(background="#b2b2b2")
+
+e1 = ttk.Entry(style="Search.entry", width=20)
+e2 = ttk.Entry(style="Search.entry", width=20)
+
+e1.grid(padx=10, pady=10)
+e2.grid(padx=10, pady=10)
+
+root.mainloop()
diff --git a/Demo/tkinter/ttk/notebook_closebtn.py b/Demo/tkinter/ttk/notebook_closebtn.py
new file mode 100644 (file)
index 0000000..6e65f09
--- /dev/null
@@ -0,0 +1,78 @@
+"""A Ttk Notebook with close buttons.
+
+Based on an example by patthoyts, http://paste.tclers.tk/896
+"""
+import os
+import tkinter
+from tkinter import ttk
+
+root = tkinter.Tk()
+
+imgdir = os.path.join(os.path.dirname(__file__), 'img')
+i1 = tkinter.PhotoImage("img_close", file=os.path.join(imgdir, 'close.gif'))
+i2 = tkinter.PhotoImage("img_closeactive",
+    file=os.path.join(imgdir, 'close_active.gif'))
+i3 = tkinter.PhotoImage("img_closepressed",
+    file=os.path.join(imgdir, 'close_pressed.gif'))
+
+style = ttk.Style()
+
+style.element_create("close", "image", "img_close",
+    ("active", "pressed", "!disabled", "img_closepressed"),
+    ("active", "!disabled", "img_closeactive"), border=8, sticky='')
+
+style.layout("ButtonNotebook", [("ButtonNotebook.client", {"sticky": "nswe"})])
+style.layout("ButtonNotebook.Tab", [
+    ("ButtonNotebook.tab", {"sticky": "nswe", "children":
+        [("ButtonNotebook.padding", {"side": "top", "sticky": "nswe",
+                                     "children":
+            [("ButtonNotebook.focus", {"side": "top", "sticky": "nswe",
+                                       "children":
+                [("ButtonNotebook.label", {"side": "left", "sticky": ''}),
+                 ("ButtonNotebook.close", {"side": "left", "sticky": ''})]
+            })]
+        })]
+    })]
+)
+
+def btn_press(event):
+    x, y, widget = event.x, event.y, event.widget
+    elem = widget.identify(x, y)
+    index = widget.index("@%d,%d" % (x, y))
+
+    if "close" in elem:
+        widget.state(['pressed'])
+        widget.pressed_index = index
+
+def btn_release(event):
+    x, y, widget = event.x, event.y, event.widget
+
+    if not widget.instate(['pressed']):
+        return
+
+    elem =  widget.identify(x, y)
+    index = widget.index("@%d,%d" % (x, y))
+
+    if "close" in elem and widget.pressed_index == index:
+        widget.forget(index)
+        widget.event_generate("<<NotebookClosedTab>>")
+
+    widget.state(["!pressed"])
+    widget.pressed_index = None
+
+
+root.bind_class("TNotebook", "<ButtonPress-1>", btn_press, True)
+root.bind_class("TNotebook", "<ButtonRelease-1>", btn_release)
+
+# create a ttk notebook with our custom style, and add some tabs to it
+nb = ttk.Notebook(width=200, height=200, style="ButtonNotebook")
+nb.pressed_index = None
+f1 = tkinter.Frame(nb, background="red")
+f2 = tkinter.Frame(nb, background="green")
+f3 = tkinter.Frame(nb, background="blue")
+nb.add(f1, text='Red', padding=3)
+nb.add(f2, text='Green', padding=3)
+nb.add(f3, text='Blue', padding=3)
+nb.pack(expand=1, fill='both')
+
+root.mainloop()
diff --git a/Demo/tkinter/ttk/plastik_theme.py b/Demo/tkinter/ttk/plastik_theme.py
new file mode 100644 (file)
index 0000000..9c68bb5
--- /dev/null
@@ -0,0 +1,268 @@
+"""This demonstrates good part of the syntax accepted by theme_create.
+
+This is a translation of plastik.tcl to python.
+You will need the images used by the plastik theme to test this. The
+images (and other tile themes) can be retrived by doing:
+
+$ cvs -z3 -d:pserver:anonymous@tktable.cvs.sourceforge.net:/cvsroot/tktable \
+  co tile-themes
+
+To test this module you should do, for example:
+
+import Tkinter
+import plastik_theme
+
+root = Tkinter.Tk()
+plastik_theme.install(plastik_image_dir)
+...
+
+Where plastik_image_dir contains the path to the images directory used by
+the plastik theme, something like: tile-themes/plastik/plastik
+"""
+import os
+import glob
+from tkinter import ttk, PhotoImage
+
+__all__ = ['install']
+
+colors = {
+    "frame": "#efefef",
+    "disabledfg": "#aaaaaa",
+    "selectbg": "#657a9e",
+    "selectfg": "#ffffff"
+    }
+
+imgs = {}
+def _load_imgs(imgdir):
+    imgdir = os.path.expanduser(imgdir)
+    if not os.path.isdir(imgdir):
+        raise Exception("%r is not a directory, can't load images" % imgdir)
+    for f in glob.glob("%s/*.gif" % imgdir):
+        img = os.path.split(f)[1]
+        name = img[:-4]
+        imgs[name] = PhotoImage(name, file=f, format="gif89")
+
+def install(imgdir):
+    _load_imgs(imgdir)
+    style = ttk.Style()
+    style.theme_create("plastik", "default", settings={
+        ".": {
+            "configure":
+                {"background": colors['frame'],
+                 "troughcolor": colors['frame'],
+                 "selectbackground": colors['selectbg'],
+                 "selectforeground": colors['selectfg'],
+                 "fieldbackground": colors['frame'],
+                 "font": "TkDefaultFont",
+                 "borderwidth": 1},
+            "map": {"foreground": [("disabled", colors['disabledfg'])]}
+        },
+
+        "Vertical.TScrollbar": {"layout": [
+            ("Vertical.Scrollbar.uparrow", {"side": "top", "sticky": ''}),
+            ("Vertical.Scrollbar.downarrow", {"side": "bottom", "sticky": ''}),
+            ("Vertical.Scrollbar.uparrow", {"side": "bottom", "sticky": ''}),
+            ("Vertical.Scrollbar.trough", {"sticky": "ns", "children":
+                [("Vertical.Scrollbar.thumb", {"expand": 1, "unit": 1,
+                    "children": [("Vertical.Scrollbar.grip", {"sticky": ''})]
+                })]
+            })]
+        },
+
+        "Horizontal.TScrollbar": {"layout": [
+            ("Horizontal.Scrollbar.leftarrow", {"side": "left", "sticky": ''}),
+            ("Horizontal.Scrollbar.rightarrow",
+                {"side": "right", "sticky": ''}),
+            ("Horizontal.Scrollbar.leftarrow",
+                {"side": "right", "sticky": ''}),
+            ("Horizontal.Scrollbar.trough", {"sticky": "ew", "children":
+                [("Horizontal.Scrollbar.thumb", {"expand": 1, "unit": 1,
+                    "children": [("Horizontal.Scrollbar.grip", {"sticky": ''})]
+                })]
+            })]
+        },
+
+        "TButton": {
+            "configure": {"width": 10, "anchor": "center"},
+            "layout": [
+                ("Button.button", {"children":
+                    [("Button.focus", {"children":
+                        [("Button.padding", {"children":
+                            [("Button.label", {"side": "left", "expand": 1})]
+                        })]
+                    })]
+                })
+            ]
+        },
+
+        "Toolbutton": {
+            "configure": {"anchor": "center"},
+            "layout": [
+                ("Toolbutton.border", {"children":
+                    [("Toolbutton.button", {"children":
+                        [("Toolbutton.padding", {"children":
+                            [("Toolbutton.label", {"side":"left", "expand":1})]
+                        })]
+                    })]
+                })
+            ]
+        },
+
+        "TMenubutton": {"layout": [
+            ("Menubutton.button", {"children":
+                [("Menubutton.indicator", {"side": "right"}),
+                 ("Menubutton.focus", {"children":
+                    [("Menubutton.padding", {"children":
+                        [("Menubutton.label", {"side": "left", "expand": 1})]
+                    })]
+                })]
+            })]
+        },
+
+        "TNotebook": {"configure": {"tabmargins": [0, 2, 0, 0]}},
+        "TNotebook.tab": {
+            "configure": {"padding": [6, 2, 6, 2], "expand": [0, 0, 2]},
+            "map": {"expand": [("selected", [1, 2, 4, 2])]}
+        },
+        "Treeview": {"configure": {"padding": 0}},
+
+        # elements
+        "Button.button": {"element create":
+            ("image", 'button-n',
+                ("pressed", 'button-p'), ("active", 'button-h'),
+                {"border": [4, 10], "padding": 4, "sticky":"ewns"}
+            )
+        },
+
+        "Toolbutton.button": {"element create":
+            ("image", 'tbutton-n',
+                ("selected", 'tbutton-p'), ("pressed", 'tbutton-p'),
+                ("active", 'tbutton-h'),
+                {"border": [4, 9], "padding": 3, "sticky": "news"}
+            )
+        },
+
+        "Checkbutton.indicator": {"element create":
+            ("image", 'check-nu',
+                ('active', 'selected', 'check-hc'),
+                ('pressed', 'selected', 'check-pc'),
+                ('active', 'check-hu'),
+                ("selected", 'check-nc'),
+                {"sticky": ''}
+            )
+        },
+
+        "Radiobutton.indicator": {"element create":
+            ("image", 'radio-nu',
+                ('active', 'selected', 'radio-hc'),
+                ('pressed', 'selected', 'radio-pc'),
+                ('active', 'radio-hu'), ('selected', 'radio-nc'),
+                {"sticky": ''}
+            )
+        },
+
+        "Horizontal.Scrollbar.thumb": {"element create":
+            ("image", 'hsb-n', {"border": 3, "sticky": "ew"})
+        },
+
+        "Horizontal.Scrollbar.grip": {"element create": ("image", 'hsb-g')},
+        "Horizontal.Scrollbar.trough": {"element create": ("image", 'hsb-t')},
+        "Vertical.Scrollbar.thumb": {"element create":
+            ("image", 'vsb-n', {"border": 3, "sticky": "ns"})
+        },
+        "Vertical.Scrollbar.grip": {"element create": ("image", 'vsb-g')},
+        "Vertical.Scrollbar.trough": {"element create": ("image", 'vsb-t')},
+        "Scrollbar.uparrow": {"element create":
+            ("image", 'arrowup-n', ("pressed", 'arrowup-p'), {"sticky": ''})
+        },
+        "Scrollbar.downarrow": {"element create":
+            ("image", 'arrowdown-n',
+            ("pressed", 'arrowdown-p'), {'sticky': ''})
+        },
+        "Scrollbar.leftarrow": {"element create":
+            ("image", 'arrowleft-n',
+            ("pressed", 'arrowleft-p'), {'sticky': ''})
+        },
+        "Scrollbar.rightarrow": {"element create":
+            ("image", 'arrowright-n', ("pressed", 'arrowright-p'),
+            {'sticky': ''})
+        },
+
+        "Horizontal.Scale.slider": {"element create":
+            ("image", 'hslider-n', {'sticky': ''})
+        },
+        "Horizontal.Scale.trough": {"element create":
+            ("image", 'hslider-t', {'border': 1, 'padding': 0})
+        },
+        "Vertical.Scale.slider": {"element create":
+            ("image", 'vslider-n', {'sticky': ''})
+        },
+        "Vertical.Scale.trough": {"element create":
+            ("image", 'vslider-t', {'border': 1, 'padding': 0})
+        },
+
+        "Entry.field": {"element create":
+            ("image", 'entry-n',
+                ("focus", 'entry-f'),
+                {'border': 2, 'padding': [3, 4], 'sticky': 'news'}
+            )
+        },
+
+        "Labelframe.border": {"element create":
+            ("image", 'border', {'border': 4, 'padding': 4, 'sticky': 'news'})
+        },
+
+        "Menubutton.button": {"element create":
+            ("image", 'combo-r',
+                ('active', 'combo-ra'),
+                {'sticky': 'news', 'border': [4, 6, 24, 15],
+                 'padding': [4, 4, 5]}
+            )
+        },
+        "Menubutton.indicator": {"element create":
+            ("image", 'arrow-d', {"sticky": "e", "border": [15, 0, 0, 0]})
+        },
+
+        "Combobox.field": {"element create":
+            ("image", 'combo-n',
+                ('readonly', 'active', 'combo-ra'),
+                ('focus', 'active', 'combo-fa'),
+                ('active', 'combo-a'), ('!readonly', 'focus', 'combo-f'),
+                ('readonly', 'combo-r'),
+                {'border': [4, 6, 24, 15], 'padding': [4, 4, 5],
+                 'sticky': 'news'}
+            )
+        },
+        "Combobox.downarrow": {"element create":
+            ("image", 'arrow-d', {'sticky': 'e', 'border': [15, 0, 0, 0]})
+         },
+
+        "Notebook.client": {"element create":
+            ("image", 'notebook-c', {'border': 4})
+        },
+        "Notebook.tab": {"element create":
+            ("image", 'notebook-tn',
+                ("selected", 'notebook-ts'), ("active", 'notebook-ta'),
+                {'padding': [0, 2, 0, 0], 'border': [4, 10, 4, 10]}
+            )
+        },
+
+        "Progressbar.trough": {"element create":
+            ("image", 'hprogress-t', {'border': 2})
+        },
+        "Horizontal.Progressbar.pbar": {"element create":
+            ("image", 'hprogress-b', {'border': [2, 9]})
+        },
+        "Vertical.Progressbar.pbar": {"element create":
+            ("image", 'vprogress-b', {'border': [9, 2]})
+        },
+
+        "Treeheading.cell": {"element create":
+            ("image", 'tree-n',
+                ("pressed", 'tree-p'),
+                {'border': [4, 10], 'padding': 4, 'sticky': 'news'}
+            )
+        }
+
+    })
+    style.theme_use("plastik")
diff --git a/Demo/tkinter/ttk/roundframe.py b/Demo/tkinter/ttk/roundframe.py
new file mode 100644 (file)
index 0000000..ce3685a
--- /dev/null
@@ -0,0 +1,111 @@
+"""Ttk Frame with rounded corners.
+
+Based on an example by Bryan Oakley, found at: http://wiki.tcl.tk/20152"""
+import tkinter
+from tkinter import ttk
+
+root = tkinter.Tk()
+
+img1 = tkinter.PhotoImage("frameFocusBorder", data="""
+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
+Ry/99NIz//oGrZpUUEAAOw==""")
+
+img2 = tkinter.PhotoImage("frameBorder", data="""
+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""")
+
+style = ttk.Style()
+
+style.element_create("RoundedFrame", "image", "frameBorder",
+    ("focus", "frameFocusBorder"), border=16, sticky="nsew")
+
+style.layout("RoundedFrame", [("RoundedFrame", {"sticky": "nsew"})])
+style.configure("TEntry", borderwidth=0)
+
+frame = ttk.Frame(style="RoundedFrame", padding=10)
+frame.pack(fill='x')
+
+frame2 = ttk.Frame(style="RoundedFrame", padding=10)
+frame2.pack(fill='both', expand=1)
+
+entry = ttk.Entry(frame, text='Test')
+entry.pack(fill='x')
+entry.bind("<FocusIn>", lambda evt: frame.state(["focus"]))
+entry.bind("<FocusOut>", lambda evt: frame.state(["!focus"]))
+
+text = tkinter.Text(frame2, borderwidth=0, bg="white", highlightthickness=0)
+text.pack(fill='both', expand=1)
+text.bind("<FocusIn>", lambda evt: frame2.state(["focus"]))
+text.bind("<FocusOut>", lambda evt: frame2.state(["!focus"]))
+
+root.mainloop()
diff --git a/Demo/tkinter/ttk/theme_selector.py b/Demo/tkinter/ttk/theme_selector.py
new file mode 100644 (file)
index 0000000..09c5a72
--- /dev/null
@@ -0,0 +1,61 @@
+"""Ttk Theme Selector v2.
+
+This is an improvement from the other theme selector (themes_combo.py)
+since now you can notice theme changes in Ttk Combobox, Ttk Frame,
+Ttk Label and Ttk Button.
+"""
+import tkinter
+from tkinter import ttk
+
+class App(ttk.Frame):
+    def __init__(self):
+        ttk.Frame.__init__(self, borderwidth=3)
+
+        self.style = ttk.Style()
+
+        # XXX Ideally I wouldn't want to create a Tkinter.IntVar to make
+        #     it works with Checkbutton variable option.
+        self.theme_autochange = tkinter.IntVar(self, 0)
+        self._setup_widgets()
+
+    def _change_theme(self):
+        self.style.theme_use(self.themes_combo.get())
+
+    def _theme_sel_changed(self, widget):
+        if self.theme_autochange.get():
+            self._change_theme()
+
+    def _setup_widgets(self):
+        themes_lbl = ttk.Label(self, text="Themes")
+
+        themes = self.style.theme_names()
+        self.themes_combo = ttk.Combobox(self, values=themes, state="readonly")
+        self.themes_combo.set(themes[0])
+        self.themes_combo.bind("<<ComboboxSelected>>", self._theme_sel_changed)
+
+        change_btn = ttk.Button(self, text='Change Theme',
+            command=self._change_theme)
+
+        theme_change_checkbtn = ttk.Checkbutton(self,
+            text="Change themes when combobox item is activated",
+            variable=self.theme_autochange)
+
+        themes_lbl.grid(ipadx=6, sticky="w")
+        self.themes_combo.grid(row=0, column=1, padx=6, sticky="ew")
+        change_btn.grid(row=0, column=2, padx=6, sticky="e")
+        theme_change_checkbtn.grid(row=1, columnspan=3, sticky="w", pady=6)
+
+        top = self.winfo_toplevel()
+        top.rowconfigure(0, weight=1)
+        top.columnconfigure(0, weight=1)
+        self.columnconfigure(1, weight=1)
+        self.grid(row=0, column=0, sticky="nsew", columnspan=3, rowspan=2)
+
+
+def main():
+    app = App()
+    app.master.title("Theme Selector")
+    app.mainloop()
+
+if __name__ == "__main__":
+    main()
diff --git a/Demo/tkinter/ttk/treeview_multicolumn.py b/Demo/tkinter/ttk/treeview_multicolumn.py
new file mode 100644 (file)
index 0000000..be72237
--- /dev/null
@@ -0,0 +1,107 @@
+"""Demo based on the demo mclist included with tk source distribution."""
+import tkinter
+import tkinter.font
+from tkinter import ttk
+
+tree_columns = ("country", "capital", "currency")
+tree_data = [
+    ("Argentina",      "Buenos Aires",     "ARS"),
+    ("Australia",      "Canberra",         "AUD"),
+    ("Brazil",         "Brazilia",         "BRL"),
+    ("Canada",         "Ottawa",           "CAD"),
+    ("China",          "Beijing",          "CNY"),
+    ("France",         "Paris",            "EUR"),
+    ("Germany",        "Berlin",           "EUR"),
+    ("India",          "New Delhi",        "INR"),
+    ("Italy",          "Rome",             "EUR"),
+    ("Japan",          "Tokyo",            "JPY"),
+    ("Mexico",         "Mexico City",      "MXN"),
+    ("Russia",         "Moscow",           "RUB"),
+    ("South Africa",   "Pretoria",         "ZAR"),
+    ("United Kingdom", "London",           "GBP"),
+    ("United States",  "Washington, D.C.", "USD")
+    ]
+
+def sortby(tree, col, descending):
+    """Sort tree contents when a column is clicked on."""
+    # grab values to sort
+    data = [(tree.set(child, col), child) for child in tree.get_children('')]
+
+    # reorder data
+    data.sort(reverse=descending)
+    for indx, item in enumerate(data):
+        tree.move(item[1], '', indx)
+
+    # switch the heading so that it will sort in the opposite direction
+    tree.heading(col,
+        command=lambda col=col: sortby(tree, col, int(not descending)))
+
+class App(object):
+    def __init__(self):
+        self.tree = None
+        self._setup_widgets()
+        self._build_tree()
+
+    def _setup_widgets(self):
+        msg = ttk.Label(wraplength="4i", justify="left", anchor="n",
+            padding=(10, 2, 10, 6),
+            text=("Ttk is the new Tk themed widget set. One of the widgets it "
+                  "includes is a tree widget, which can be configured to "
+                  "display multiple columns of informational data without "
+                  "displaying the tree itself. This is a simple way to build "
+                  "a listbox that has multiple columns. Clicking on the "
+                  "heading for a column will sort the data by that column. "
+                  "You can also change the width of the columns by dragging "
+                  "the boundary between them."))
+        msg.pack(fill='x')
+
+        container = ttk.Frame()
+        container.pack(fill='both', expand=True)
+
+        # XXX Sounds like a good support class would be one for constructing
+        #     a treeview with scrollbars.
+        self.tree = ttk.Treeview(columns=tree_columns, show="headings")
+        vsb = ttk.Scrollbar(orient="vertical", command=self.tree.yview)
+        hsb = ttk.Scrollbar(orient="horizontal", command=self.tree.xview)
+        self.tree.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set)
+        self.tree.grid(column=0, row=0, sticky='nsew', in_=container)
+        vsb.grid(column=1, row=0, sticky='ns', in_=container)
+        hsb.grid(column=0, row=1, sticky='ew', in_=container)
+
+        container.grid_columnconfigure(0, weight=1)
+        container.grid_rowconfigure(0, weight=1)
+
+    def _build_tree(self):
+        for col in tree_columns:
+            self.tree.heading(col, text=col.title(),
+                command=lambda c=col: sortby(self.tree, c, 0))
+            # XXX tkFont.Font().measure expected args are incorrect according
+            #     to the Tk docs
+            self.tree.column(col, width=tkinter.font.Font().measure(col.title()))
+
+        for item in tree_data:
+            self.tree.insert('', 'end', values=item)
+
+            # adjust columns lenghts if necessary
+            for indx, val in enumerate(item):
+                ilen = tkinter.font.Font().measure(val)
+                if self.tree.column(tree_columns[indx], width=None) < ilen:
+                    self.tree.column(tree_columns[indx], width=ilen)
+
+def main():
+    root = tkinter.Tk()
+    root.wm_title("Multi-Column List")
+    root.wm_iconname("mclist")
+
+    import plastik_theme
+    try:
+        plastik_theme.install('~/tile-themes/plastik/plastik')
+    except Exception:
+        import warnings
+        warnings.warn("plastik theme being used without images")
+
+    app = App()
+    root.mainloop()
+
+if __name__ == "__main__":
+    main()
diff --git a/Demo/tkinter/ttk/ttkcalendar.py b/Demo/tkinter/ttk/ttkcalendar.py
new file mode 100644 (file)
index 0000000..8e35f1f
--- /dev/null
@@ -0,0 +1,231 @@
+"""
+Simple calendar using ttk Treeview together with calendar and datetime
+classes.
+"""
+import calendar
+import tkinter
+import tkinter.font
+from tkinter import ttk
+
+def get_calendar(locale, fwday):
+    # instantiate proper calendar class
+    if locale is None:
+        return calendar.TextCalendar(fwday)
+    else:
+        return calendar.LocaleTextCalendar(fwday, locale)
+
+class Calendar(ttk.Frame):
+    # XXX ToDo: cget and configure
+
+    datetime = calendar.datetime.datetime
+    timedelta = calendar.datetime.timedelta
+
+    def __init__(self, master=None, **kw):
+        """
+        WIDGET-SPECIFIC OPTIONS
+
+            locale, firstweekday, year, month, selectbackground,
+            selectforeground
+        """
+        # remove custom options from kw before initializating ttk.Frame
+        fwday = kw.pop('firstweekday', calendar.MONDAY)
+        year = kw.pop('year', self.datetime.now().year)
+        month = kw.pop('month', self.datetime.now().month)
+        locale = kw.pop('locale', None)
+        sel_bg = kw.pop('selectbackground', '#ecffc4')
+        sel_fg = kw.pop('selectforeground', '#05640e')
+
+        self._date = self.datetime(year, month, 1)
+        self._selection = None # no date selected
+
+        ttk.Frame.__init__(self, master, **kw)
+
+        self._cal = get_calendar(locale, fwday)
+
+        self.__setup_styles()       # creates custom styles
+        self.__place_widgets()      # pack/grid used widgets
+        self.__config_calendar()    # adjust calendar columns and setup tags
+        # configure a canvas, and proper bindings, for selecting dates
+        self.__setup_selection(sel_bg, sel_fg)
+
+        # store items ids, used for insertion later
+        self._items = [self._calendar.insert('', 'end', values='')
+                            for _ in range(6)]
+        # insert dates in the currently empty calendar
+        self._build_calendar()
+
+        # set the minimal size for the widget
+        self._calendar.bind('<Map>', self.__minsize)
+
+    def __setitem__(self, item, value):
+        if item in ('year', 'month'):
+            raise AttributeError("attribute '%s' is not writeable" % item)
+        elif item == 'selectbackground':
+            self._canvas['background'] = value
+        elif item == 'selectforeground':
+            self._canvas.itemconfigure(self._canvas.text, item=value)
+        else:
+            ttk.Frame.__setitem__(self, item, value)
+
+    def __getitem__(self, item):
+        if item in ('year', 'month'):
+            return getattr(self._date, item)
+        elif item == 'selectbackground':
+            return self._canvas['background']
+        elif item == 'selectforeground':
+            return self._canvas.itemcget(self._canvas.text, 'fill')
+        else:
+            r = ttk.tclobjs_to_py({item: ttk.Frame.__getitem__(self, item)})
+            return r[item]
+
+    def __setup_styles(self):
+        # custom ttk styles
+        style = ttk.Style(self.master)
+        arrow_layout = lambda dir: (
+            [('Button.focus', {'children': [('Button.%sarrow' % dir, None)]})]
+        )
+        style.layout('L.TButton', arrow_layout('left'))
+        style.layout('R.TButton', arrow_layout('right'))
+
+    def __place_widgets(self):
+        # header frame and its widgets
+        hframe = ttk.Frame(self)
+        lbtn = ttk.Button(hframe, style='L.TButton', command=self._prev_month)
+        rbtn = ttk.Button(hframe, style='R.TButton', command=self._next_month)
+        self._header = ttk.Label(hframe, width=15, anchor='center')
+        # the calendar
+        self._calendar = ttk.Treeview(show='', selectmode='none', height=7)
+
+        # pack the widgets
+        hframe.pack(in_=self, side='top', pady=4, anchor='center')
+        lbtn.grid(in_=hframe)
+        self._header.grid(in_=hframe, column=1, row=0, padx=12)
+        rbtn.grid(in_=hframe, column=2, row=0)
+        self._calendar.pack(in_=self, expand=1, fill='both', side='bottom')
+
+    def __config_calendar(self):
+        cols = self._cal.formatweekheader(3).split()
+        self._calendar['columns'] = cols
+        self._calendar.tag_configure('header', background='grey90')
+        self._calendar.insert('', 'end', values=cols, tag='header')
+        # adjust its columns width
+        font = tkinter.font.Font()
+        maxwidth = max(font.measure(col) for col in cols)
+        for col in cols:
+            self._calendar.column(col, width=maxwidth, minwidth=maxwidth,
+                anchor='e')
+
+    def __setup_selection(self, sel_bg, sel_fg):
+        self._font = tkinter.font.Font()
+        self._canvas = canvas = tkinter.Canvas(self._calendar,
+            background=sel_bg, borderwidth=0, highlightthickness=0)
+        canvas.text = canvas.create_text(0, 0, fill=sel_fg, anchor='w')
+
+        canvas.bind('<ButtonPress-1>', lambda evt: canvas.place_forget())
+        self._calendar.bind('<Configure>', lambda evt: canvas.place_forget())
+        self._calendar.bind('<ButtonPress-1>', self._pressed)
+
+    def __minsize(self, evt):
+        width, height = self._calendar.master.geometry().split('x')
+        height = height[:height.index('+')]
+        self._calendar.master.minsize(width, height)
+
+    def _build_calendar(self):
+        year, month = self._date.year, self._date.month
+
+        # update header text (Month, YEAR)
+        header = self._cal.formatmonthname(year, month, 0)
+        self._header['text'] = header.title()
+
+        # update calendar shown dates
+        cal = self._cal.monthdayscalendar(year, month)
+        for indx, item in enumerate(self._items):
+            week = cal[indx] if indx < len(cal) else []
+            fmt_week = [('%02d' % day) if day else '' for day in week]
+            self._calendar.item(item, values=fmt_week)
+
+    def _show_selection(self, text, bbox):
+        """Configure canvas for a new selection."""
+        x, y, width, height = bbox
+
+        textw = self._font.measure(text)
+
+        canvas = self._canvas
+        canvas.configure(width=width, height=height)
+        canvas.coords(canvas.text, width - textw, height / 2 - 1)
+        canvas.itemconfigure(canvas.text, text=text)
+        canvas.place(in_=self._calendar, x=x, y=y)
+
+    # Callbacks
+
+    def _pressed(self, evt):
+        """Clicked somewhere in the calendar."""
+        x, y, widget = evt.x, evt.y, evt.widget
+        item = widget.identify_row(y)
+        column = widget.identify_column(x)
+
+        if not column or not item in self._items:
+            # clicked in the weekdays row or just outside the columns
+            return
+
+        item_values = widget.item(item)['values']
+        if not len(item_values): # row is empty for this month
+            return
+
+        text = item_values[int(column[1]) - 1]
+        if not text: # date is empty
+            return
+
+        bbox = widget.bbox(item, column)
+        if not bbox: # calendar not visible yet
+            return
+
+        # update and then show selection
+        text = '%02d' % text
+        self._selection = (text, item, column)
+        self._show_selection(text, bbox)
+
+    def _prev_month(self):
+        """Updated calendar to show the previous month."""
+        self._canvas.place_forget()
+
+        self._date = self._date - self.timedelta(days=1)
+        self._date = self.datetime(self._date.year, self._date.month, 1)
+        self._build_calendar() # reconstuct calendar
+
+    def _next_month(self):
+        """Update calendar to show the next month."""
+        self._canvas.place_forget()
+
+        year, month = self._date.year, self._date.month
+        self._date = self._date + self.timedelta(
+            days=calendar.monthrange(year, month)[1] + 1)
+        self._date = self.datetime(self._date.year, self._date.month, 1)
+        self._build_calendar() # reconstruct calendar
+
+    # Properties
+
+    @property
+    def selection(self):
+        """Return a datetime representing the current selected date."""
+        if not self._selection:
+            return None
+
+        year, month = self._date.year, self._date.month
+        return self.datetime(year, month, int(self._selection[0]))
+
+def test():
+    import sys
+    root = tkinter.Tk()
+    root.title('Ttk Calendar')
+    ttkcal = Calendar(firstweekday=calendar.SUNDAY)
+    ttkcal.pack(expand=1, fill='both')
+
+    if 'win' not in sys.platform:
+        style = ttk.Style()
+        style.theme_use('clam')
+
+    root.mainloop()
+
+if __name__ == '__main__':
+    test()
diff --git a/Demo/tkinter/ttk/widget_state.py b/Demo/tkinter/ttk/widget_state.py
new file mode 100644 (file)
index 0000000..b68f13b
--- /dev/null
@@ -0,0 +1,83 @@
+"""Sample demo showing widget states and some font styling."""
+from tkinter import ttk
+
+states = ['active', 'disabled', 'focus', 'pressed', 'selected',
+          'background', 'readonly', 'alternate', 'invalid']
+
+for state in states[:]:
+    states.append("!" + state)
+
+def reset_state(widget):
+    nostate = states[len(states) // 2:]
+    widget.state(nostate)
+
+class App(ttk.Frame):
+    def __init__(self, title=None):
+        ttk.Frame.__init__(self, borderwidth=6)
+        self.master.title(title)
+
+        self.style = ttk.Style()
+
+        # get default font size and family
+        btn_font = self.style.lookup("TButton", "font")
+        fsize = str(self.tk.eval("font configure %s -size" % btn_font))
+        self.font_family = self.tk.eval("font configure %s -family" % btn_font)
+        if ' ' in self.font_family:
+            self.font_family = '{%s}' % self.font_family
+        self.fsize_prefix = fsize[0] if fsize[0] == '-' else ''
+        self.base_fsize = int(fsize[1 if fsize[0] == '-' else 0:])
+
+        # a list to hold all the widgets that will have their states changed
+        self.update_widgets = []
+
+        self._setup_widgets()
+
+    def _set_font(self, extra=0):
+        self.style.configure("TButton", font="%s %s%d" % (self.font_family,
+            self.fsize_prefix, self.base_fsize + extra))
+
+    def _new_state(self, widget, newtext):
+        widget = self.nametowidget(widget)
+
+        if not newtext:
+            goodstates = ["disabled"]
+            font_extra = 0
+        else:
+            # set widget state according to what has been entered in the entry
+            newstates = set(newtext.split()) # eliminate duplicates
+
+            # keep only the valid states
+            goodstates = [state for state in newstates if state in states]
+            # define a new font size based on amount of states
+            font_extra = 2 * len(goodstates)
+
+        # set new widget state
+        for widget in self.update_widgets:
+            reset_state(widget) # remove any previous state from the widget
+            widget.state(goodstates)
+
+        # update Ttk Button font size
+        self._set_font(font_extra)
+        return 1
+
+    def _setup_widgets(self):
+        btn = ttk.Button(self, text='Enter states and watch')
+
+        entry = ttk.Entry(self, cursor='xterm', validate="key")
+        entry['validatecommand'] = (self.register(self._new_state), '%W', '%P')
+        entry.focus()
+
+        self.update_widgets.append(btn)
+        entry.validate()
+
+        entry.pack(fill='x', padx=6)
+        btn.pack(side='left', pady=6, padx=6, anchor='n')
+        self.pack(fill='both', expand=1)
+
+
+def main():
+    app = App("Widget State Tester")
+    app.mainloop()
+
+if __name__ == "__main__":
+    main()
index 85df35462cc55c06648aa315b770a9354f8622ea..04d55fef1fa56472824a726cfc6596d8f9cf97f9 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -351,6 +351,8 @@ Library
 Tools/Demos
 -----------
 
+- Ttk demos added in Demo/tkinter/ttk/
+
 - Issue #4677: add two list comprehension tests to pybench.