Author: guilherme.polo
Date: Tue Jun 24 04:15:23 2008
New Revision: 64494
Log:
Started sample application. I intend to facilitate the process of understanding ttk theming, or at least make people waste some time playing with this.
Added:
sandbox/trunk/ttk-gsoc/samples/theming.py
Added: sandbox/trunk/ttk-gsoc/samples/theming.py
==============================================================================
--- (empty file)
+++ sandbox/trunk/ttk-gsoc/samples/theming.py Tue Jun 24 04:15:23 2008
@@ -0,0 +1,324 @@
+"""Sample application for playing with Ttk theming.
+A lot of features are missing for now.
+"""
+
+import sys
+import ttk
+import pprint
+import Tkinter
+import cStringIO
+
+def available_widgets():
+ """Returns a list of Ttk widgets."""
+ widgets = []
+ # will discard Style and extension classes
+ unwanted_names = ('Style', 'LabeledScale', 'OptionMenu')
+ # some widgets contain Vertical and Horizontal layouts
+ special = ('Progressbar', 'Scale', 'Scrollbar')
+
+ for name in ttk.__all__:
+ if name in unwanted_names:
+ continue
+
+ name = name.title()
+ if name in widgets:
+ # do not add aliases
+ continue
+
+ if name in special:
+ widgets.append((name, ('Horizontal', 'Vertical')))
+ else:
+ widgets.append(name)
+
+ return widgets
+
+
+class AutoScroll(object):
+ """Configure the scrollbars for a widget."""
+
+ def __init__(self, master):
+ vsb = ttk.Scrollbar(master, orient='vertical', command=self.yview)
+ hsb = ttk.Scrollbar(master, orient='horizontal', command=self.xview)
+
+ self.configure(yscrollcommand=self._autoscroll(vsb),
+ xscrollcommand=self._autoscroll(hsb))
+
+ self.grid(column=0, row=0, sticky='nsew', in_=master)
+ vsb.grid(column=1, row=0, sticky='ns', in_=master)
+ hsb.grid(column=0, row=1, sticky='ew', in_=master)
+
+ master.grid_columnconfigure(0, weight=1)
+ master.grid_rowconfigure(0, weight=1)
+
+ @staticmethod
+ def _autoscroll(sbar):
+ """Hide and show scrollbar as needed."""
+ def wrapped(first, last):
+ first, last = float(first), float(last)
+
+ if first <= 0 and last >= 1:
+ sbar.grid_remove()
+ else:
+ sbar.grid()
+
+ sbar.set(first, last)
+
+ return wrapped
+
+
+def _create_container(func):
+ """Creates a ttk Frame with a given master, and use this new frame to
+ place the scrollbars and the widget."""
+ def wrapped(cls, master, **kw):
+ container = ttk.Frame(master)
+ container.pack(fill='both', expand=True)
+ return func(cls, container, **kw)
+
+ return wrapped
+
+
+class ScrolledTreeview(AutoScroll, ttk.Treeview):
+ """A standard ttk Treeview widget with scrollbars that will
+ automatically show/hide as needed."""
+ @_create_container
+ def __init__(self, master, **kw):
+ ttk.Treeview.__init__(self, master, **kw)
+ AutoScroll.__init__(self, master)
+
+
+class ScrolledText(AutoScroll, Tkinter.Text):
+ """A standard Tkinter Text widget with scrollbars that will
+ automatically show/hide as needed."""
+ @_create_container
+ def __init__(self, master, **kw):
+ Tkinter.Text.__init__(self, master, **kw)
+ AutoScroll.__init__(self, master)
+
+
+class MainWindow(object):
+ def __init__(self, master, title=None):
+ frame = ttk.Frame(master)
+ self.master = frame.master
+
+ width = 640
+ height = width * 3 / 4
+ self.master.geometry('%dx%d' % (width, height))
+ self.master.minsize(width, height)
+ self.master.title(title)
+
+ self._style = ttk.Style(self.master)
+ self._current_widget = {'layout': None, 'widget': None}
+ self._current_options = None
+
+ self.__create_menu()
+ self.__setup_widgets()
+ self.__fill_treeview()
+
+ def _change_preview(self, event):
+ """New treeview selection, update preview area."""
+ treeview = getattr(event, 'widget', event)
+ if not treeview.selection():
+ return
+
+ tv_item = treeview.item(treeview.selection()[0])
+ widget_name = tv_item['text']
+ widget = self._widget[widget_name]
+ parent = widget['tv_item']
+ if treeview.get_children(parent):
+ return
+
+ complement = ''
+ opts = {}
+ if '.' in widget_name: # horizontal/vertical layout
+ complement, widget_name = widget_name.split('.')
+ opts = {'orient': complement.lower()}
+
+ # create a sample widget
+ sample = getattr(ttk, widget_name)(self._preview_area, **opts)
+ if widget['class'] is None:
+ widget['class'] = "%s%s%s" % (complement,
+ '.' if complement else '', sample.winfo_class())
+ sample['style'] = 'Custom.%s' % widget['class']
+ if self._current_widget['widget'] is not None:
+ self._current_widget['widget'].pack_forget()
+ sample.pack()
+
+ self._current_widget['layout'] = sample['style']
+ self._current_widget['widget'] = sample
+ self._update_layout_text()
+ self._update_style_configframe()
+
+ def _update_layout_text(self):
+ """Update the layout text for the current widget."""
+ layout_name = self._current_widget['layout']
+ output = cStringIO.StringIO()
+ layout = pprint.pprint(self._style.layout(layout_name), stream=output)
+ layout = output.getvalue()
+ output.close()
+ self.layouttext.delete('1.0', 'end') # clear current text
+ self.layouttext.insert('1.0', layout) # set new text
+
+ def _update_style_configframe(self):
+ """Update the configure frame for the current widget."""
+ def change_opt(option, text):
+ # XXX Warning: eval usage!
+ try:
+ self._style.configure(layout_name, **{option: eval(text)})
+ except SyntaxError:
+ pass
+ except NameError:
+ self._style.configure(layout_name, **{option: text})
+ return 1
+
+ widget = self._current_widget
+ layout_name = widget['layout']
+ raw_name = layout_name[layout_name.find('.', 1) + 1:]
+ options = self._style.configure(raw_name)
+ frame = self._configframe
+
+ self._current_options = self._current_options or []
+ # remove previous widgets
+ for widget in self._current_options:
+ widget.pack_forget()
+ # insert new widgets
+ for opt_name, opt_value in options.iteritems():
+ lbl = ttk.Label(frame, text=opt_name)
+ entry = ttk.Entry(frame)
+ entry.insert(0, opt_value)
+ entry.configure(validate='key',
+ validatecommand=(
+ self.master.register(change_opt), opt_name, '%P'))
+ lbl.pack(side='top', anchor='w')
+ entry.pack(side='top', fill='x', pady=3)
+ self._current_options.extend([lbl, entry])
+
+ def _change_theme(self, event):
+ """New theme selected at themes combobox, change current theme."""
+ widget = event.widget
+ self._style.theme_use(widget.get())
+
+ treeview = self._tv_widgets
+ self._change_preview(treeview)
+
+ def _apply_layout(self):
+ """Apply the supposed new layout for the current selected widget."""
+ layout = self._current_widget['layout']
+ if not layout: # nothing to do
+ return
+
+ text = self.layouttext.get('1.0', 'end')
+ # XXX Warning, eval usage!
+ self._style.layout(layout, eval(text))
+
+ def _reset_layout(self):
+ """Reset the layout for current selected widget."""
+ widget = self._current_widget
+ self._style.layout(widget['layout'],
+ self._style.layout(widget['widget'].winfo_class()))
+ self._update_layout_text()
+
+ def __create_menu(self):
+ menu = Tkinter.Menu()
+ self.master['menu'] = menu
+
+ file_menu = Tkinter.Menu(menu, tearoff=False)
+ file_menu.add_command(label="Save", underline=0)
+ file_menu.add_separator()
+ file_menu.add_command(label="Exit", underline=1,
+ command=self.master.destroy)
+
+ menu.add('cascade', menu=file_menu, label="File", underline=0)
+
+ def __setup_widgets(self):
+ """Create and layout widgets."""
+ paned = ttk.Panedwindow()
+
+ # top frame
+ top = ttk.Frame(paned, padding=[0, 0, 0, 12])
+ top.pack(side='top', fill='both', expand=True)
+
+ # top left frame (widget listing)
+ left = ttk.Frame(top)
+ left.pack(side='left', fill='y')
+ tframe = ttk.Frame(left)
+ tframe.pack(side='top', fill='y', expand=True)
+ self._tv_widgets = treeview = ScrolledTreeview(tframe,
+ selectmode='browse')
+ treeview.heading('#0', text="Widgets")
+ treeview.bind('<<TreeviewSelect>>', self._change_preview)
+
+ # top right frame (preview, style conf, style map, images, themes)
+ topright = ttk.Frame(top)
+ topright.pack(side='top', fill='both', expand=True, padx=6)
+
+ # preview area
+ self._preview_area = ttk.Frame(topright, width=200, padding=12)
+ self._preview_area.pack(anchor='center', side='left', expand=True)
+
+ # options, images and themes
+ frames = ttk.Frame(topright)
+ frames.pack(side='right', anchor='n')
+ # style configure frame
+ self._configframe = configframe = ttk.Labelframe(frames,
+ text="Style configure", padding=6, height=42)
+ configframe.pack(fill='x')
+ # style map frame (XXX not done)
+ self._mapframe = mapframe = ttk.Labelframe(frames, text="Style map",
+ padding=6, height=42)
+ mapframe.pack(fill='x', pady=12)
+ # images frame (XXX not done)
+ imagesframe = ttk.Labelframe(frames, text="Images", padding=6,
+ height=42)
+ imagesframe.pack(fill='x')
+ # themes frame
+ themeframe = ttk.Labelframe(frames, text="Themes", padding=6)
+ themes = ttk.Combobox(themeframe, values=self._style.theme_names(),
+ state='readonly')
+ themes.set("Pick one")
+ themes.bind('<<ComboboxSelected>>', self._change_theme)
+ themes.pack(fill='x')
+ themeframe.pack(fill='x', pady=12)
+
+ # bottom frame (layout)
+ bottom = ttk.Frame(paned, padding=[0, 0, 6])
+ bottom.pack(side='bottom', fill='both')
+ layoutframe = ttk.Labelframe(bottom, text="Layout", padding=6)
+ layoutframe.pack(fill='both', expand=True)
+ self.layouttext = ScrolledText(layoutframe, width=80, height=10)
+ apply_btn = ttk.Button(layoutframe, text="Apply",
+ command=self._apply_layout)
+ reset_btn = ttk.Button(layoutframe, text="Reset",
+ command=self._reset_layout)
+ apply_btn.pack(side='right')
+ reset_btn.pack(side='right', padx=6)
+
+ paned.add(top)
+ paned.add(bottom)
+ paned.pack(fill='both', expand=True)
+
+ def __fill_treeview(self):
+ """Insert available widgets to the treeview."""
+ self._widget = {}
+ widgets = available_widgets()
+ for widget in widgets:
+ if isinstance(widget, tuple): # horizontal/vertical layout
+ widget, children = widget
+ else:
+ children = ()
+
+ parent = self._tv_widgets.insert('', 'end', text=widget)
+ self._widget[widget] = {'tv_item': parent, 'class': None}
+
+ for child in children:
+ child_name = '%s.%s' % (child, widget)
+ item = self._tv_widgets.insert(parent, 'end', text=child_name)
+ self._widget[child_name] = {'tv_item': item, 'class': None}
+
+
+def main(args=None):
+ root = Tkinter.Tk()
+ app = MainWindow(root, 'Ttk Theming')
+ root.mainloop()
+
+if __name__ == "__main__":
+ main(sys.argv)