[Python-checkins] r64494 - sandbox/trunk/ttk-gsoc/samples/theming.py

guilherme.polo python-checkins at python.org
Tue Jun 24 04:15:24 CEST 2008


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)


More information about the Python-checkins mailing list