diff --git a/Demo/tkinter/README b/Demo/tkinter/README index f245d163cdf..c9f18df39b1 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 00000000000..98b31643104 --- /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 <> 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("<>", 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 00000000000..b4f79bdddfb --- /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('<>', update_tree) +tree.bind('', 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 00000000000..18cf6c76032 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 index 00000000000..db7f39269f5 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 index 00000000000..561695495ce 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 index 00000000000..f2a56a4ed0a --- /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 00000000000..48d5aa94ea3 --- /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 00000000000..244168d33f2 --- /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("<>") + + widget.state(["!pressed"]) + widget.pressed_index = None + + +root.bind_class("TNotebook", "", btn_press, True) +root.bind_class("TNotebook", "", 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 00000000000..4922d9036be --- /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 00000000000..ed2147a4507 --- /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("", lambda evt: frame.state(["focus"])) +entry.bind("", lambda evt: frame.state(["!focus"])) + +text = Tkinter.Text(frame2, borderwidth=0, bg="white", highlightthickness=0) +text.pack(fill='both', expand=1) +text.bind("", lambda evt: frame2.state(["focus"])) +text.bind("", 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 00000000000..adb8472e639 --- /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("<>", 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 00000000000..e0edc4f2374 --- /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 00000000000..faa45c5b672 --- /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('', 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('', lambda evt: canvas.place_forget()) + self._calendar.bind('', lambda evt: canvas.place_forget()) + self._calendar.bind('', 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 00000000000..c92c28b09db --- /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 8ae45c9ca38..5866c4ade14 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