summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Demo/tkinter/README1
-rw-r--r--Demo/tkinter/ttk/combo_themes.py46
-rw-r--r--Demo/tkinter/ttk/dirbrowser.py93
-rw-r--r--Demo/tkinter/ttk/img/close.gifbin0 -> 101 bytes
-rw-r--r--Demo/tkinter/ttk/img/close_active.gifbin0 -> 80 bytes
-rw-r--r--Demo/tkinter/ttk/img/close_pressed.gifbin0 -> 101 bytes
-rw-r--r--Demo/tkinter/ttk/listbox_scrollcmd.py37
-rw-r--r--Demo/tkinter/ttk/mac_searchentry.py78
-rw-r--r--Demo/tkinter/ttk/notebook_closebtn.py78
-rw-r--r--Demo/tkinter/ttk/plastik_theme.py269
-rw-r--r--Demo/tkinter/ttk/roundframe.py111
-rw-r--r--Demo/tkinter/ttk/theme_selector.py61
-rw-r--r--Demo/tkinter/ttk/treeview_multicolumn.py107
-rw-r--r--Demo/tkinter/ttk/ttkcalendar.py231
-rw-r--r--Demo/tkinter/ttk/widget_state.py83
-rw-r--r--Misc/NEWS2
16 files changed, 1197 insertions, 0 deletions
diff --git a/Demo/tkinter/README b/Demo/tkinter/README
index f245d16..c9f18df 100644
--- a/Demo/tkinter/README
+++ b/Demo/tkinter/README
@@ -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
index 0000000..98b3164
--- /dev/null
+++ b/Demo/tkinter/ttk/combo_themes.py
@@ -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.
+"""
+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
index 0000000..b4f79bd
--- /dev/null
+++ b/Demo/tkinter/ttk/dirbrowser.py
@@ -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
+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
index 0000000..18cf6c7
--- /dev/null
+++ b/Demo/tkinter/ttk/img/close.gif
Binary files differ
diff --git a/Demo/tkinter/ttk/img/close_active.gif b/Demo/tkinter/ttk/img/close_active.gif
new file mode 100644
index 0000000..db7f392
--- /dev/null
+++ b/Demo/tkinter/ttk/img/close_active.gif
Binary files differ
diff --git a/Demo/tkinter/ttk/img/close_pressed.gif b/Demo/tkinter/ttk/img/close_pressed.gif
new file mode 100644
index 0000000..5616954
--- /dev/null
+++ b/Demo/tkinter/ttk/img/close_pressed.gif
Binary files differ
diff --git a/Demo/tkinter/ttk/listbox_scrollcmd.py b/Demo/tkinter/ttk/listbox_scrollcmd.py
new file mode 100644
index 0000000..f2a56a4
--- /dev/null
+++ b/Demo/tkinter/ttk/listbox_scrollcmd.py
@@ -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
+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
index 0000000..48d5aa9
--- /dev/null
+++ b/Demo/tkinter/ttk/mac_searchentry.py
@@ -0,0 +1,78 @@
+"""Mac style search widget
+
+Translated from Tcl code by Schelte Bron, http://wiki.tcl.tk/18188
+"""
+import 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
index 0000000..244168d
--- /dev/null
+++ b/Demo/tkinter/ttk/notebook_closebtn.py
@@ -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
+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
index 0000000..4922d90
--- /dev/null
+++ b/Demo/tkinter/ttk/plastik_theme.py
@@ -0,0 +1,269 @@
+"""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
+import ttk
+from Tkinter import 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
index 0000000..ed2147a
--- /dev/null
+++ b/Demo/tkinter/ttk/roundframe.py
@@ -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
+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
index 0000000..adb8472
--- /dev/null
+++ b/Demo/tkinter/ttk/theme_selector.py
@@ -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
+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
index 0000000..e0edc4f
--- /dev/null
+++ b/Demo/tkinter/ttk/treeview_multicolumn.py
@@ -0,0 +1,107 @@
+"""Demo based on the demo mclist included with tk source distribution."""
+import Tkinter
+import tkFont
+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=tkFont.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 = tkFont.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
index 0000000..faa45c5
--- /dev/null
+++ b/Demo/tkinter/ttk/ttkcalendar.py
@@ -0,0 +1,231 @@
+"""
+Simple calendar using ttk Treeview together with calendar and datetime
+classes.
+"""
+import calendar
+import Tkinter
+import tkFont
+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 = tkFont.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 = tkFont.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
index 0000000..c92c28b
--- /dev/null
+++ b/Demo/tkinter/ttk/widget_state.py
@@ -0,0 +1,83 @@
+"""Sample demo showing widget states and some font styling."""
+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()
diff --git a/Misc/NEWS b/Misc/NEWS
index 8ae45c9..5866c4a 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -379,6 +379,8 @@ Library
Tools/Demos
-----------
+- Ttk demos added in Demo/tkinter/ttk/
+
- Issue #4677: add two list comprehension tests to pybench.
Build