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

guilherme.polo python-checkins at python.org
Wed Jul 30 04:24:37 CEST 2008


Author: guilherme.polo
Date: Wed Jul 30 04:24:37 2008
New Revision: 65294

Log:
Added the possibility to save and load changes. I have dropped the idea to edit elements given ttk itself doesn't support it

Modified:
   sandbox/trunk/ttk-gsoc/samples/theming.py

Modified: sandbox/trunk/ttk-gsoc/samples/theming.py
==============================================================================
--- sandbox/trunk/ttk-gsoc/samples/theming.py	(original)
+++ sandbox/trunk/ttk-gsoc/samples/theming.py	Wed Jul 30 04:24:37 2008
@@ -4,19 +4,16 @@
 -- Guilherme Polo, 2008.
 """
 
-# XXX ToDo List:
-#   * Save/Load style changes, maybe.
-#   * Add a way to edit elements.
-#   * Add pre-defined elements for the current theme:
-#       - Just after editing elements feature is added.
-
 import os
 import ttk
 import pprint
+import inspect
 import Tkinter
+import cPickle
 import cStringIO
+import ConfigParser
 from tkSimpleDialog import Dialog
-from tkFileDialog import askopenfilename
+from tkFileDialog import askopenfilename, asksaveasfilename
 from tkMessageBox import showwarning, showerror
 
 IMAGE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "img")
@@ -134,6 +131,57 @@
     return w
 
 
+class ThemeFile(ConfigParser.ConfigParser):
+    def __init__(self, fileobj, style=None, readfrom=False):
+        ConfigParser.ConfigParser.__init__(self)
+        self._fobj = fileobj
+        self._style = style
+        if readfrom:
+            self.readfp(fileobj)
+
+    def optionxform(self, optionstr):
+        """This method overrides the ConfigParser.RawConfigParser's one."""
+        caller = inspect.stack()[1] # caller's record
+        caller_func_name = caller[3]
+        if caller_func_name in ('set', '_read'):
+            # don't change option's case for these functions
+            return optionstr
+        return optionstr.lower()
+
+    def load_configure(self, items):
+        self._style_load(items, self._style.configure)
+
+    def load_map(self, items):
+        self._style_load(items, self._style.map)
+
+    def load_layout(self, items):
+        self._style_load(items, self._style.layout, False)
+
+    def _style_load(self, items, method, dict_unpack=True):
+        if not items:
+            return
+
+        for layout, data in items:
+            # XXX Warning: pickle usage!
+            data = cPickle.loads(data)
+            layout = "Custom.%s" % layout
+            if dict_unpack:
+                method(layout, **data)
+            else:
+                method(layout, data)
+
+    def add_theme_sections(self, theme):
+        sections = ('configure', 'map', 'layout')
+        for section in sections:
+            self.add_section('%s-%s' % (theme, section))
+
+    def save(self):
+        self.write(self._fobj)
+
+    def close(self):
+        self._fobj.close()
+
+
 class AutoScroll(object):
     """Configure the scrollbars for a widget."""
 
@@ -455,6 +503,7 @@
         self.master.minsize(width, height)
         self.master.title(title)
 
+        self._filename = None # no custom theme loaded
         self._style = ttk.Style(self.master)
         self._current_widget = {'layout': None, 'widget': None}
         self._images = {} # images created by the user
@@ -713,17 +762,115 @@
         for child in children:
             treeview.insert(parent, 'end', text=child)
 
+    def _open_theme(self, event=None):
+        """Open and load a theme saved in a file."""
+        def get_section_items(themeobj, secname):
+            if not themeobj.has_section(secname):
+                return None
+            return themeobj.items(secname)
+
+        fname = askopenfilename(parent=self.master)
+        if not fname:
+            return
+
+        try:
+            themeobj = ThemeFile(open(fname, 'rb'), self._style, readfrom=True)
+        except IOError, err:
+            showerror("Theme file couldn't be loaded",
+                "Couldn't open requested file.\n\n"
+                "Error: %s" % err, parent=self.master)
+            return
+        except ConfigParser.Error, err:
+            showerror("Theme file couldn't be loaded", err, parent=self.master)
+            return
+
+        for theme in self._style.theme_names():
+            themeobj.load_configure(get_section_items(themeobj,
+                '%s-configure' % theme))
+            themeobj.load_map(get_section_items(themeobj, '%s-map' % theme))
+            themeobj.load_layout(get_section_items(themeobj,
+                '%s-layout' % theme))
+
+        self._filename = fname
+        themeobj.close()
+
+    def _save_theme(self, event=None):
+        """Save current changes to the current file."""
+        if self._filename is None:
+            self._filename = self._save_theme_as(event)
+        else:
+            self.__save_changes(self._filename)
+
+    def _save_theme_as(self, event=None):
+        """Save current changes to a file."""
+        fname = asksaveasfilename()
+        if not fname:
+            return
+        self.__save_changes(fname)
+        return fname
+
+    def __save_changes(self, fname):
+        try:
+            themeobj = ThemeFile(open(fname, 'wb'))
+        except IOError, err:
+            showerror("Error on file creation",
+                "Theme won't be saved.\n\nError: %s" % err, parent=self.master)
+            return
+
+        current_theme = self._style.theme_use()
+
+        # traverse through all themes looking for things that changed
+        for theme in self._style.theme_names():
+            self._style.theme_use(theme)
+            themeobj.add_theme_sections(theme)
+
+            for parent in self._tv_widgets.get_children(''):
+                for child in self._tv_widgets.get_children(parent):
+                    layout = self._tv_widgets.item(child)['text']
+                    custom_layout = "Custom.%s" % layout
+                    data = {
+                        'configure': (
+                            self._style.configure(custom_layout),
+                            self._style.configure(layout)),
+                        'map': (
+                            self._style.map(custom_layout),
+                            self._style.map(layout)),
+                        'layout': (
+                            self._style.layout(custom_layout),
+                            self._style.layout(layout))
+                    }
+
+                    # save custom data
+                    for themeopt, (custom, orig) in data.iteritems():
+                        if custom and custom != orig:
+                            themeobj.set('%s-%s' % (theme, themeopt),
+                                layout, cPickle.dumps(custom, 2))
+
+        themeobj.save()
+        themeobj.close()
+        # restore previous theme in use
+        self._style.theme_use(current_theme)
+
     def __create_menu(self):
         menu = Tkinter.Menu()
         self.master['menu'] = menu
 
         file_menu = Tkinter.Menu(menu, tearoff=False)
+        file_menu.add_command(label="Open", underline=0, accelerator="Ctrl+o",
+            command=self._open_theme)
+        file_menu.add_command(label="Save", underline=0, accelerator="Ctrl+s",
+            command=self._save_theme)
+        file_menu.add_command(label="Save as..", accelerator="Ctrl+Shift+s",
+            command=self._save_theme_as)
         file_menu.add_separator()
-        file_menu.add_command(label="Exit", underline=1, accelerator="Ctrl-X",
+        file_menu.add_command(label="Quit", underline=1, accelerator="Ctrl+q",
             command=self.master.destroy)
 
         menu.add_cascade(menu=file_menu, label="File", underline=0)
-        self.master.bind('<Control-x>', 'exit')
+        self.master.bind('<Control-o>', self._open_theme)
+        self.master.bind('<Control-s>', self._save_theme)
+        self.master.bind('<Control-Shift-S>', self._save_theme_as)
+        self.master.bind('<Control-q>', 'exit')
 
     def __setup_widgets(self):
         """Create and layout widgets."""


More information about the Python-checkins mailing list