[openbox] theme question

Mikael Magnusson mangosoft at comhem.se
Wed Jan 21 01:17:49 EST 2004


On Tue, 20 Jan 2004, Jonathan Daugherty wrote:

> # there is a pretty good theme builder written by katanalynx in python+gtk2
> # but it uses a bit outdated theme format, just some minor changes i think.
> # maybe you could hack a bit on that if you want?
> 
> I've looked all over (and even emailed katanalynx, to no end) and
> cannot find this program -- its name or its location.  Can you give
> me any advice / pointers?
> 
> Thanks!

heh, i cant find it anywhere either, but i still have it installed.. maybe 
someone else has a source archive, but here are the files it needs i 
think, the .py files go in /usr/(local/)?lib/stylebox and the other one in 
/usr/(local/)?/bin

--
Mikael Magnusson
-------------- next part --------------
# StyleBox: a style/theme editor for Openbox 2.
# Copyright (C) 2003  Ava Arachne Jarvis <ajar at katanalynx.dyndns.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

from Xlib import Xatom
from Xlib.display import Display
from Xlib.rdb import ResourceDB
import gtk, gobject, pango

import sys, os, os.path, signal
from Theme import *

import string

# Convenience GUI widget classes. 
class StyleOptionMenu(gtk.OptionMenu):
    """
    An OptionMenu with default callbacks.

    Instance Variables:
        items : list *      Any python object that can be str'd.
                            Supporting __eq__ is probably not a bad 
                            idea either.
        callback            A function that takes an argument of the 
                            list item type.  Needed so the callback 
                            reference doesn't disappear.
    """
    def __init__(self, items, selected_item = None, callback = None, rwidget = None):
        """
        Parameters:
            items : list *           - See instance variables: items.
            selected_item : *        - An item of the same type as those in items, 
                                       indicating which should be initially selected
                                       by the option menu.
            callback                   See instance variables: callback.
            rwidget : ResourceWidget - so that we can add the connection ID of the 
                                       callback.
        """
        gtk.OptionMenu.__init__(self)
        self.items = items

        menu = gtk.Menu()
        for i in items: menu.append(gtk.MenuItem(str(i)))
        self.set_menu(menu)
        if selected_item: self.set_history(items.index(selected_item))
        if callback: 
            self.callback = callback
            if rwidget:
                rwidget.connectWidget(self, "changed", self.doCallback)
            else:
                self.connect("changed", self.doCallback)
    def doCallback(self, w):
        self.callback(self.items[self.get_history()])
    def showItem(self, item):
        self.set_history(self.items.index(item))
class StyleTable(gtk.Table):
    """
    A Table layout widget that takes care of keeping track of its 
    own current row/column to simplify child packing.

    Instance Variables:
        current_row : int       - current row for next widget placement
        current_col : int       - current column for next widget placement
        max_cols : int          - maximum number of columns
    """
    def __init__(self, maxcols = 4):
        gtk.Table.__init__(self)
        self.set_border_width(5)
        self.set_row_spacings(5)
        self.set_col_spacings(5)
        self.set_homogeneous(1)

        self.current_row = 0
        self.current_col = 0
        self.max_cols = maxcols
    def nextRow(self, advance=1):
        """Advance to the next row even if the current row isn't full."""
        self.current_col = 0
        self.current_row += advance
    def pack(self, widget, colspan=1, rowspan=1):
        """
        Packs widget in the current row and current column.
        Parameters:
            widget : gtk.Widget - widget to pack
            colspan : int       - number of columns widget should span
            rowspan : int       - number of rows widget should span
        """
        oldcol = self.current_col
        self.current_col += colspan
        self.attach(widget, oldcol, self.current_col, 
                    self.current_row, self.current_row + rowspan, yoptions = gtk.FILL)
        if self.current_col >= self.max_cols: self.nextRow(rowspan)
    def packLabeled(self, *lwidgets):
        """
        Packs a list of LabeledResourceWidgets.  If the last item in 
        the argument list is an integer, it is taken as the column span 
        of the main widget of each LabeledResourceWidget.
        """
        wcolspan = 1
        if type(lwidgets[-1]) == int: 
            wcolspan = lwidgets[-1]
            lwidgets = lwidgets[:-1]
        for w in lwidgets: self.pack(w.label); self.pack(w.widget, wcolspan)

# Classes for working with an undo/redo stack.
class UndoStack:
    class Item:
        def __init__(self, type, old_value, new_value):
            self.type = type
            self.old_value = old_value
            self.new_value = new_value
        def __repr__(self):
            return '<%s: %s -> %s>' % (self.type,self.old_value,self.new_value)
    def __init__(self):
        self.undo_stack = []
        self.redo_stack = []
    def add(self, type, old_value, new_value):
        self.undo_stack.append(UndoStack.Item(type, old_value, new_value))
        if self.redo_stack: self.redo_stack = []
    def undo(self):
        if self.undo_stack:
            item = self.undo_stack.pop()
            self.redo_stack.append(item)
            return item
    def redo(self):
        if self.redo_stack:
            item = self.redo_stack.pop()
            self.undo_stack.append(item)
            return item
    def __repr__(self):
        s = '<' + `self.undo_stack` + ' '
        self.redo_stack.reverse()
        s += `self.redo_stack` + '>'
        self.redo_stack.reverse()
        return s

# Classes for mucking around with Resources. 
class ResourceWidget:
    """
    An abstract class that provides a few basic methods for widgets 
    that deal with theme resources.

    Instance Variables:
        resname  : str      - name of the resource this widget handles
        stylebox : StyleBox - the stylebox main window (for calling 
                              dialogs and accessing global theme variables
                              and methods).
        callbacks : list    - list of pairs: (widget, callbackid), for blocking signals
                              when widgets are reset.
    """
    def __init__(self, resname, stylebox):
        self.resname = resname
        self.stylebox = stylebox
        self.callbacks = []
        self.stylebox.widgets.append(self)
    def getValue(self):
        """
        Returns the value sitting behind the resource.  This value can 
        have individual fields modified and the like.
        """
        return self.stylebox.theme[self.resname].value
    def setValue(self, val):
        """
        Sets the entire value sitting behind the resource to a new value.
        """
        if self.stylebox.theme[self.resname].value != val:
            self.stylebox.theme[self.resname].value = val
            self.stylebox.setChanged()
    def connectWidget(self, widget, signal, callback):
        self.callbacks.append((widget, widget.connect(signal, callback)))
    def reset(self):
        """Resets to show the values of the current theme in stylebox."""
        for w, cid in self.callbacks: w.handler_block(cid)
        self.resetComponents()
        for w, cid in self.callbacks: w.handler_unblock(cid)
    def resetComponents(self):
        """
        Used by reset() to reset the appropriate widgets.  Don't worry 
        about blocking signals here, which is done by reset().  Also, don't 
        worry about any ResourceWidget children you might have, because 
        the reset() call will be made on *all* ResourceWidgets of a StyleBox 
        window.
        """
        pass
    def undo(self, item):
        """Processes an undo request, where item is an UndoItem."""
        self.processUndo(item.type, item.old_value)
        self.reset()
    def redo(self, item):
        """Processes an redo request, where item is an UndoItem."""
        self.processUndo(item.type, item.new_value)
        self.reset()
    def processUndo(self, type, value):
        """
        Given an undo type and a value, set the appropriate value 
        on the resource this widget models.  This should be overridden 
        by child classes.
        """
        self.setValue(value)
class LabeledResourceWidget(ResourceWidget):
    """
    A Resource widget with a single label and a widget.

    Instance Variables:
        label : gtk.Label       - label of this widget
        widget : gtk.Widget     - any kind of widget
    """
    def __init__(self, resname, stylebox, label = None):
        ResourceWidget.__init__(self, resname, stylebox)
        if not label:
            i = resname.rfind('.')
            if i < 0: label = resname
            else: label = resname[i+1:]
        self.label = gtk.Label(label)
        self.widget = None
    def defaultPack(self):
        """
        Returns a gtk.Widget with a default packing of label and widget.
        """
        hbox = gtk.HBox()
        hbox.pack_start(self.label, 0, 0, 5)
        hbox.pack_start(self.widget, 0, 0, 5)
        return hbox
    def setSensitive(self, sensitive):
        self.label.set_sensitive(sensitive)
        self.widget.set_sensitive(sensitive)

class StringResourceWidget(LabeledResourceWidget):
    """
    Handles any string-type resource.
    """
    def __init__(self, resname, stylebox, label = None):
        LabeledResourceWidget.__init__(self, resname, stylebox, label)
        self.widget = gtk.Entry()
        self.connectWidget(self.widget, "changed", 
            lambda w: self.setValue(w.get_text()))
        self.reset()
    def resetComponents(self):
        self.widget.set_text(self.getValue())
class JustifyResourceWidget(LabeledResourceWidget):
    """
    Handles a justify resource.
    """
    def __init__(self, resname, stylebox):
        LabeledResourceWidget.__init__(self, resname, stylebox)
        self.widget = StyleOptionMenu(JustifyResource.VALUES, 
                                      self.getValue(), self.setValue, self)
    def resetComponents(self):
        self.widget.showItem(self.getValue())
class FontResourceWidget(LabeledResourceWidget):
    """
    Handles font-type resources.  Multiple resources are handled 
    at the top level (xft.font, xft.flags, xft.size, etc).
    """
    FDG = gtk.FontSelectionDialog('Select Font')
    def getFont(cls, prev_font = None, title = 'Select Font'):
        """
        Queries the user for a font.

        Parameters:
            prev_font : str   - if given, sets the fdg font to this.
            title : str       - sets the title of the dialog.
        Returns:
            the font name if the user has selected one, None otherwise.
        """
        cls.FDG.set_title(title)
        font = None
        if prev_font: cls.FDG.set_font_name(prev_font)
        if cls.FDG.run() == gtk.RESPONSE_OK:
            font = cls.FDG.get_font_name()
        cls.FDG.hide()
        return font
    getFont = classmethod(getFont)

    def __init__(self, resname, stylebox):
        LabeledResourceWidget.__init__(self, resname, stylebox, 'font')

        self.entry = gtk.Entry()
        self.entry.set_editable(0)

        self.button = gtk.Button('...')
        self.connectWidget(self.button, "clicked", lambda w: self.setFont())

        self.shadow = gtk.CheckButton('Shadow')
        self.connectWidget(self.shadow, "toggled",
                           lambda w: self.setShadow(w.get_active()))

        self.shadow_offset = IntegerResourceWidget(resname+'.shadow.offset', stylebox)
        self.shadow_tint = IntegerResourceWidget(resname+'.shadow.tint', stylebox, 
                                                 min = -100, max = 100, inc_page = 20)

        self.reset()
    def setShadow(self, shadow): 
        font = self.getValue()
        if font.shadow != shadow:
            font.shadow = shadow
            self.stylebox.setChanged()
            self.setShadowSensitives()
    def setShadowSensitives(self):
        shadow = self.getValue().shadow
        self.shadow_offset.setSensitive(shadow)
        self.shadow_tint.setSensitive(shadow)
    def setFont(self):
        fontname = FontResourceWidget.getFont(self.entry.get_text(), self.resname)
        if fontname:
            self.entry.set_text(fontname)
            pfd = pango.FontDescription(fontname)
            font = self.getValue()
            font.family = pfd.get_family()
            font.size = pfd.get_size() / 1024 # PANGO_SCALE
            font.bold = pfd.get_weight() >= pango.WEIGHT_BOLD
            font.italic = pfd.get_style() == pango.STYLE_ITALIC
            self.stylebox.setChanged()
    def resetComponents(self):
        font = self.getValue()
        self.entry.set_text('%s %s %d' % \
            (font.family, font.bold and 'bold' or '', font.size))
        self.shadow.set_active(font.shadow)
        self.setShadowSensitives()
    def defaultPack(self):
        table = StyleTable(5)

        table.pack(self.label)
        hbox = gtk.HBox()
        hbox.pack_start(self.entry)
        hbox.pack_start(self.button, 0, 0)
        table.pack(hbox, 4)

        table.pack(self.shadow)
        table.packLabeled(self.shadow_offset, self.shadow_tint)

        return table

class ColorResourceWidget(LabeledResourceWidget):
    """
    Handles a color resource.
    """
    COLOR_DIALOG = gtk.ColorSelectionDialog('Select Color')
    COLOR_DIALOG.colorsel.set_has_palette(1)
    def getColor(cls, prev_color = None, title = 'Select Color'):
        """
        Queries the user for a color.

        Parameters:
            prev_color : gtk.gdk.Color  - if given, sets the cdg color to this.
            title : str                 - sets the title of the dialog.
        Returns:
            a gtk.gdk.Color indicating the selection of the user, or None 
            if the user hasn't selected anything.
        """
        cls.COLOR_DIALOG.set_title(title)
        if prev_color: cls.COLOR_DIALOG.colorsel.set_current_color(prev_color)
        color = None
        if cls.COLOR_DIALOG.run() == gtk.RESPONSE_OK:
            color = cls.COLOR_DIALOG.colorsel.get_current_color()
        cls.COLOR_DIALOG.hide()
        return color
    getColor = classmethod(getColor)

    def __init__(self, resname, stylebox, label = None):
        LabeledResourceWidget.__init__(self, resname, stylebox, label)
        self.widget = gtk.Button()
        self.widget.set_size_request(24, 20)
        self.connectWidget(self.widget, "clicked", lambda w: self.setColor())
        self.reset()
    def resetComponents(self):
        self.setBackground(self.getGdkColor())
    def setBackground(self, gdk_color):
        for state in [gtk.STATE_ACTIVE, gtk.STATE_NORMAL, 
                      gtk.STATE_PRELIGHT, gtk.STATE_SELECTED]: 
            self.widget.modify_bg(state, gdk_color)
    def getGdkColor(self):
        return gtk.gdk.color_parse(str(self.getValue()))
    def setColor(self):
        gdkcolor = ColorResourceWidget.getColor(self.getGdkColor(), self.resname)
        if gdkcolor:
            self.setBackground(gdkcolor)
            color = self.getValue()
            color.red   = round((gdkcolor.red   / 65535.0) * 255)
            color.green = round((gdkcolor.green / 65535.0) * 255)
            color.blue  = round((gdkcolor.blue  / 65535.0) * 255)
            self.stylebox.setChanged()
class TextureWidget(StyleTable, ResourceWidget):
    """
    Handles a texture resource and two colors.  Even though this class 
    handles multiple resources, it doesn't handle multiple ones at its 
    own level -- colors are passed off to ColorResourceWidgets.

    Instance Variables:
        reliefs : gtk.OptionMenu    - available texture reliefs.
        border : gtk.CheckBox       - active iff reliefs is set to "flat".
        gradient : gtk.CheckBox     - whether texture is gradient or solid.
        types : gtk.OptionMenu      - available gradient types.  active iff
                                      gradient is active.
        interlaced : gtk.CheckBox   
        bevels : gtk.OptionMenu    
        color, colorTo, borderColor : ColorResourceWidget

        row   : int                 - keeps track of current row.  Each row 
                                      can have up to two widgets.
    """
    def __init__(self, basename, stylebox, parentrelative = 1):
        """
        Parameters of note:
            parentrelative : bool   - if false, no parentrelative stuff is 
                                      shown in this widget.
        """
        StyleTable.__init__(self, 4)
        ResourceWidget.__init__(self, basename, stylebox)

        if parentrelative:
            self.parentrelative = self.createCheckBox('parentrelative', 
                                                      self.setParentRelative, 4)

        self.reliefs = self.createOption(Texture.RELIEFS, self.setRelief, 2)
        self.border = self.createCheckBox('Border', self.setBorder, 2)
        self.setReliefSensitives()

        self.gradient = self.createCheckBox('Gradient', self.setGradient, 2)
        self.types = self.createOption(Texture.TYPES, self.setType, 2)
        self.setGradientSensitives()

        self.interlaced = self.createCheckBox('Interlaced', self.setInterlaced, 2)
        self.bevels = self.createOption(Texture.BEVELS, self.setBevel, 2)

        self.color = ColorResourceWidget(basename + '.color', stylebox)
        self.colorTo = ColorResourceWidget(basename + '.colorTo', stylebox)
        self.borderColor = ColorResourceWidget(basename + '.borderColor', stylebox)
        self.packLabeled(self.color, self.colorTo)
        self.packLabeled(self.borderColor)

        self.reset()
    def resetComponents(self):
        for opmenu in [self.reliefs, self.types, self.bevels]:
            opmenu.set_history(0)
        for cbox in [self.border, self.gradient, self.interlaced]:
            cbox.set_active(0)

        texture = self.getValue()
        self.reliefs.set_history(texture.relief)
        self.border.set_active(texture.border)
        self.setReliefSensitives()
        self.setBorderSensitives()

        self.gradient.set_active(texture.gradient)
        self.types.set_history(texture.type)
        self.setGradientSensitives()

        self.bevels.set_history(texture.bevel)
        self.interlaced.set_active(texture.interlaced)

        if hasattr(self, 'parentrelative'):
            self.parentrelative.set_active(texture.parentrelative)
            self.setParentRelativeSensitives()

    # Signal methods 
    def setParentRelative(self, w):
        self.getValue().parentrelative = w.get_active()
        self.setParentRelativeSensitives()
        self.stylebox.setChanged()
    def setParentRelativeSensitives(self):
        for w in [self.reliefs, self.border, self.gradient, self.types, 
                  self.interlaced, self.bevels, 
                  self.color.label, self.color.widget, 
                  self.colorTo.label, self.colorTo.widget]:
            w.set_sensitive(not self.parentrelative.get_active())
        # ParentRelative covers multiple widgets which may have "sensitive"
        # relations with each other, so if we activate them all, we'll need 
        # to check the rest to make sure we didn't screw up any other 
        # relationships.
        if not self.parentrelative.get_active():
            self.setReliefSensitives()
            self.setGradientSensitives()
    def setRelief(self, w):
        self.getValue().relief = w.get_history()
        self.setReliefSensitives()
        self.stylebox.setChanged()
    def setReliefSensitives(self):
        self.border.set_sensitive(
            Texture.RELIEFS[self.getValue().relief] == 'flat')
    def setBorder(self, w):
        self.getValue().border = w.get_active()
        self.setBorderSensitives()
        self.stylebox.setChanged()
    def setBorderSensitives(self):
        self.borderColor.setSensitive(self.getValue().border)
    def setGradient(self, w):
        self.getValue().gradient = w.get_active()
        self.setGradientSensitives()
        self.stylebox.setChanged()
    def setType(self, w):
        self.getValue().type = w.get_history()
        self.stylebox.setChanged()
    def setGradientSensitives(self):
        self.types.set_sensitive(self.getValue().gradient)
    def setInterlaced(self, w):
        self.getValue().interlaced = w.get_active()
        self.stylebox.setChanged()
    def setBevel(self, w):
        self.getValue().bevel = w.get_history()
        self.stylebox.setChanged()
    
    # Widget creation methods 
    def createOption(self, items, callback, numcols = 2):
        """
        Parameters:
            items : str list    - items to be put into the option menu's menu
            callback            - a gtk callback for the "changed" signal.
            left : int          - left attach of widget
            right : int         - right attach of widget
        Returns:
            an option menu
        """
        opmenu = StyleOptionMenu(items)
        self.connectWidget(opmenu, "changed", callback)
        self.pack(opmenu, numcols)
        return opmenu
    def createCheckBox(self, name, callback, numcols):
        """
        Parameters:
            name : str      - label of this check box.
            callback        - a gtk callback for the "toggled" signal
            left : int      - left attach of widget
            right : int     - right attach of widget
        Returns:
            a check button.
        """
        cbutton = gtk.CheckButton(name)
        self.connectWidget(cbutton, "toggled", callback)
        self.pack(cbutton, numcols)
        return cbutton
class ButtonTextureWidget(TextureWidget):
    """
    Handles a texture resource with an additional picColor.
    """
    def __init__(self, basename, stylebox, parentrelative=1):
        TextureWidget.__init__(self, basename, stylebox, parentrelative)
        self.picColor = ColorResourceWidget(basename + '.picColor', stylebox)
        self.packLabeled(self.picColor)
class TextTextureWidget(TextureWidget):
    """
    Handles a texture resource with an additional textColor.
    """
    def __init__(self, basename, stylebox, parentrelative=1):
        TextureWidget.__init__(self, basename, stylebox, parentrelative)
        self.textColor = ColorResourceWidget(basename + '.textColor', stylebox)
        self.packLabeled(self.textColor)
class FontTextTextureWidget(TextTextureWidget):
    """
    Handles a texture resource with a textColor and font settings.
    """
    def __init__(self, basename, stylebox, parentrelative=1):
        TextTextureWidget.__init__(self, basename, stylebox, parentrelative)
        self.nextRow()
        self.font = FontResourceWidget(basename + '.xft', stylebox)
        self.pack(self.font.defaultPack(), 4, 2)
        self.justify = JustifyResourceWidget(basename+'.justify', stylebox)
        self.packLabeled(self.justify)
class MaskResourceWidget(LabeledResourceWidget):
    BUTTON_DIR = os.path.expanduser('~/.openbox/buttons')
    FILE_SELECTOR = gtk.FileSelection('Select XBM Mask')
    FILE_SELECTOR.set_filename(BUTTON_DIR + '/')
    FILE_SELECTOR.set_select_multiple(0)
    FILE_SELECTOR.hide_fileop_buttons()
    def getFilename(cls, title = 'Select XBM Mask'):
        cls.FILE_SELECTOR.set_title(title)
        cls.FILE_SELECTOR.complete('*.xbm')
        filename = None
        if cls.FILE_SELECTOR.run() == gtk.RESPONSE_OK:
            filename = cls.FILE_SELECTOR.get_filename()
            if cls.BUTTON_DIR == os.path.dirname(filename):
                filename = os.path.basename(filename)
        cls.FILE_SELECTOR.hide()
        return filename
    getFilename = classmethod(getFilename)

    def __init__(self, resname, stylebox, label = None):
        LabeledResourceWidget.__init__(self, resname, stylebox, label)
        self.widget = gtk.HBox()

        self.entry = gtk.Entry()
        self.connectWidget(self.entry, "changed", 
                           lambda w: self.setValue(w.get_text()))
        self.widget.pack_start(self.entry)

        button = gtk.Button('...')
        self.connectWidget(button, "clicked", self.setMaskFile)
        self.widget.pack_start(button, 0, 0)

        self.reset()
    def resetComponents(self):
        self.entry.set_text(self.getValue())
    def setMaskFile(self, w):
        filename = MaskResourceWidget.getFilename(self.resname)
        if filename:
            self.setValue(filename)
            self.entry.set_text(filename)
            self.stylebox.setChanged()
class BulletMenuResourceWidget(LabeledResourceWidget):
    def __init__(self, resname, stylebox):
        LabeledResourceWidget.__init__(self, resname, stylebox, 'type')
        self.widget = StyleOptionMenu(BulletResource.VALUES, 
                                      self.getValue(), self.setValue, self)
    def resetComponents(self):
        self.widget.showItem(self.getValue())
class BulletPositionResourceWidget(LabeledResourceWidget):
    def __init__(self, resname, stylebox):
        LabeledResourceWidget.__init__(self, resname, stylebox, 'position')
        self.widget = StyleOptionMenu(BulletPositionResource.VALUES, 
                                      self.getValue(), self.setValue, self)
    def resetComponents(self):
        self.widget.showItem(self.getValue())
class IntegerResourceWidget(LabeledResourceWidget):
    def __init__(self, resname, stylebox, label = None, 
                 min = 1, max = 20, inc_step = 1, inc_page = 5):
        LabeledResourceWidget.__init__(self, resname, stylebox, label)

        self.widget = gtk.SpinButton()
        self.widget.set_numeric(1)
        self.widget.set_range(min, max)
        self.widget.set_increments(inc_step, inc_page)
        self.connectWidget(self.widget, "value_changed", 
                           lambda w: self.setValue(w.get_value_as_int()))
        self.reset()
    def resetComponents(self):
        self.widget.set_value(self.getValue())

class StyleBox(gtk.Window):
    """
    Main window of the StyleBox application.  Currently, it can only handle 
    one theme at a time.

    Instance Variables:
        theme : Theme           - current theme being edited.
        notebook : gtk.NoteBook - keeps track of separate pages. 
        tree : gtk.TreeStore    - tree for hierarchy of notebook pages
        itemfactory : gtk.ItemFactory - factory for menu items.  must keep
                                        a reference to.
        root : Xlib.Display     - current X display; needed to get information 
                                  about currently running openbox
        checkpoint : int        - current checkpoint number
    """
    OPENBOX_RC = os.environ['HOME'] + '/.openbox/rc'
    FRONTIS = 'Stylebox version 0.3, Copyright (C) 2003  Ava Arachne Jarvis.\n' + \
            'Stylebox comes with ABSOLUTELY NO WARRANTY; for details see LICENSE.' +\
            'This is free software, and you are welcome to redistribute it' + \
            'under certain conditions; see LICENSE for details.'

    def __init__(self):
        gtk.Window.__init__(self)
        self.connect("destroy", self.quit)

        self.changed = 0
        self.theme = None
        self.display = Display()

        self.widgets = []

        vbox = gtk.VBox()
        self.add(vbox)
        vbox.pack_start(self.createMenuBar(), 0, 0)
        hbox = gtk.HBox()
        vbox.pack_start(hbox)
        vbox2 = gtk.VBox()
        vbox2.pack_start(self.createNotebook())
        vbox2.pack_start(self.createButtonArea(), 0, 0)
        hbox.pack_start(self.createTreeView())
        hbox.pack_start(vbox2)
        self.statusbar = gtk.Statusbar()
        vbox.pack_start(self.statusbar, 0, 0)

        self.getCurrentTheme()
        self.createPanels()
        self.createDialogs()
    def setTitle(self):
        self.set_title('Stylebox: ' + (self.theme.filename or '*new*') + \
                        (self.changed and ' [changed]' or ''))
    def setChanged(self):
        if not self.changed:
            self.changed = 1
            self.setTitle()
    def getCurrentTheme(self):
        rc_rdb = ResourceDB(StyleBox.OPENBOX_RC)
        filename = rc_rdb.get('session.styleFile', 'session.styleFile')
        self.openTheme(filename)
        self.setStatus('Openbox loaded current style')
    def setCurrentTheme(self, filename):
        rc_rdb = ResourceDB(StyleBox.OPENBOX_RC)
        rc_rdb.insert('session.styleFile', filename)
        rdb_file = open(StyleBox.OPENBOX_RC, 'w')
        rdb_file.write(rc_rdb.output())
        rdb_file.close()
        self.reconfigureOpenbox()
        self.setStatus('Openbox loaded ' + filename)

    # Signal methods
    def quit(self, *rest):
        if self.changed:
            if not self.askUser("Theme changed and not saved.  Still quit?"):
                return
        gtk.main_quit()
    def showPageButtonPress(self, view, event):
        path = view.get_path_at_pos(event.x, event.y)[0]
        self.notebook.set_current_page(
            self.tree.get_value(self.tree.get_iter(path), 0))
        selection = view.get_selection()
        selection.select_path(path)
    def showPageKeyRelease(self, view, event):
        selection = view.get_selection()
        model, iter = selection.get_selected()
        keyname = gtk.gdk.keyval_name(event.keyval)
        if keyname in ['Down', 'Up']: 
            self.notebook.set_current_page(model.get_value(iter, 0))
        elif keyname == 'Left':
            def collapse_parent():
                parent_iter = model.iter_parent(iter)
                if parent_iter:
                    path = model.get_path(parent_iter)
                    view.collapse_row(path)
                    selection.select_path(path)
            if model.iter_has_child(iter):
                path = model.get_path(iter)
                if view.row_expanded(path):
                    view.collapse_row(path)
                else:
                    collapse_parent()
            else:
                collapse_parent()
        elif keyname == 'Right':
            path = model.get_path(iter)
            view.expand_row(path, 0)
    def openTheme(self, filename = None):
        self.theme = Theme(filename)
        for w in self.widgets: w.reset()
        # set changed after resetting widgets, because they 
        # might falsely indicate change while reading new values
        self.changed = 0 
        self.setTitle()
        self.setStatus('Opened ' + (filename or '*new*'))
    def chooseTheme(self, *rest):
        if self.changed:
            if not self.askUser("Theme changes and not saved.  Still open?"):
                return
        filename = self.getFilename('Select Theme')
        if filename: 
            self.openTheme(filename)
            self.setCurrentTheme(filename)
    def saveTheme(self, filename = None):
        if filename:
            self.theme.save(filename)
            self.setStatus('Saved as ' + filename)
            self.changed = 0
            self.setTitle()
            self.setCurrentTheme(filename)
        else:
            if not self.theme.filename:
                self.saveThemeAs()
            else:
                self.theme.save()
                self.changed = 0
                self.setTitle()
                self.setStatus('Saved.')
                self.reconfigureOpenbox()
    def saveThemeAs(self, *rest):
        filename = self.getFilename('Save Theme As...')
        if not filename: return
        if os.path.exists(filename):
            if not self.askUser('%s already exists.  Overwrite?' % filename):
                return
        self.saveTheme(filename)
    def setStatus(self, message):
        self.statusbar.push(self.statusbar.get_context_id('Main Window'), message)
    def reconfigureOpenbox(self, *rest):
        root = self.display.screen().root
        blackbox_pid_atom = self.display.display.get_atom('_BLACKBOX_PID')
        prop = root.get_property(blackbox_pid_atom, Xatom.CARDINAL, 0, 1)
        if prop:
            ob_pid = int(prop.value.tolist()[0])
            os.kill(ob_pid, signal.SIGHUP)
            self.setStatus('Openbox reconfigured.')

    # Dialogs
    def getFilename(self, title):
        self.fdg.set_title(title)
        filename = None
        if self.fdg.run() == gtk.RESPONSE_OK:
            filename = self.fdg.get_filename()
        self.fdg.hide()
        return filename
    def askUser(self, message):
        ask_dialog = gtk.MessageDialog(self, gtk.DIALOG_MODAL, 
            gtk.MESSAGE_WARNING, gtk.BUTTONS_YES_NO, message)
        response = ask_dialog.run()
        ask_dialog.destroy()
        return (response == gtk.RESPONSE_YES)
    def showAbout(self, *rest):
        md = gtk.MessageDialog(self, buttons=gtk.BUTTONS_OK, 
                               message_format=StyleBox.FRONTIS)
        md.set_title('About Stylebox')
        md.run()
        md.destroy()

    # Widget-creating methods
    def createMenuBar(self):
        menu_items = ( 
            ('/_File', None, None, 0, '<Branch>'),
            ('/File/_New', '<control>N', lambda *w: self.openTheme(), 0, 
                            '<StockItem>', gtk.STOCK_NEW), 
            ('/File/_Open', '<control>O', self.chooseTheme, 0, 
                            '<StockItem>', gtk.STOCK_OPEN), 
            ('/File/_Save', '<control>S', lambda *w: self.saveTheme(), 0, 
                            '<StockItem>', gtk.STOCK_SAVE),
            ('/File/Save _As...', '<control>A', self.saveThemeAs, 0, 
                            '<StockItem>', gtk.STOCK_SAVE_AS), 
            ('/File/sep1', None, None, 0, '<Separator>'),
            ('/File/_Quit', '<control>Q', self.quit, 0, '<StockItem>', gtk.STOCK_QUIT),

            #('/_Edit', None, None, 0, '<Branch>'),
            #('/Edit/_Revert', '<control>R', None, 0, '<StockItem>',gtk.STOCK_REVERT_TO_SAVED),

            ('/Help', None, None, 0, '<LastBranch>'),
            ('/Help/About', None, self.showAbout, 0, '')
        )
        accelgroup = gtk.AccelGroup()
        self.add_accel_group(accelgroup)
        self.item_factory = gtk.ItemFactory(gtk.MenuBar, '<main>', accelgroup)
        self.item_factory.create_items(menu_items)
        return self.item_factory.get_widget('<main>')
    def createButtonArea(self):
        quit_button = gtk.Button(gtk.STOCK_QUIT)
        quit_button.connect("clicked", self.quit)
        save_button = gtk.Button(gtk.STOCK_SAVE)
        save_button.connect("clicked", lambda *w: self.saveTheme())
        saveas_button = gtk.Button(gtk.STOCK_SAVE_AS)
        saveas_button.connect("clicked", lambda *w: self.saveThemeAs())
        buttons = [save_button, saveas_button, quit_button]
        buttonarea = gtk.HButtonBox()
        buttonarea.set_layout(gtk.BUTTONBOX_END)
        for b in buttons:
            b.set_use_stock(1)
            buttonarea.pack_start(b)
        return buttonarea
    def createTreeView(self):
        self.tree = gtk.TreeStore(gobject.TYPE_INT, gobject.TYPE_STRING)
        treeview = gtk.TreeView(self.tree)
        treeview.set_headers_visible(0)
        treeview.append_column(
            gtk.TreeViewColumn('', gtk.CellRendererText(), text=1))
        treeview.connect("button_press_event", self.showPageButtonPress)
        treeview.connect("key_release_event", self.showPageKeyRelease)

        scrolled = gtk.ScrolledWindow()
        scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        scrolled.add_with_viewport(treeview)
        scrolled.set_size_request(175, 200)

        return scrolled
    def createNotebook(self):
        self.notebook = gtk.Notebook()
        self.notebook.set_show_tabs(0)
        self.notebook.set_show_border(0)
        return self.notebook
    def createDialogs(self):
        self.fdg = gtk.FileSelection('Select File')

    # Panel-creating functions
    def createPanel(self, name, parent = None):
        """
        Creates a panel in the main window's notebook.
        Parameters:
            name : str      - name of the panel
            parent : gtk.TreeIter - parent node of this panel
        Returns:
            a Panel and an iter into the main window's tree.
        """
        panel = gtk.Frame()
        panel.set_shadow_type(gtk.SHADOW_NONE)
        self.notebook.append_page_menu(panel, gtk.Label(name), gtk.Label(name))

        iter = self.tree.insert_before(parent, None)
        self.tree.set_value(iter, 0, self.page)
        self.tree.set_value(iter, 1, name)
        self.page += 1

        return panel, iter
    def createPanels(self):
        self.page = 0
        self.createInfoPanel()
        self.createToolbarPanel()
        self.createMenuPanel()
        self.createWindowPanel()
        self.createMiscPanel()
        del self.page
    def createInfoPanel(self):
        panel, iter = self.createPanel("style")
        table = StyleTable(4)
        panel.add(table)

        for s in ['name', 'author', 'date', 'credits', 'comments']:
            table.packLabeled(StringResourceWidget('style.' + s, self), 3)
    def createToolbarPanel(self):
        panel, toolbar_iter = self.createPanel("toolbar")
        t = TextureWidget('toolbar', self, parentrelative=0)
        t.nextRow()
        t.pack(FontResourceWidget('toolbar.xft', self).defaultPack(), 4, 2)
        t.packLabeled(JustifyResourceWidget('toolbar.justify',self))
        panel.add(t)

        for s in ['label', 'windowLabel', 'clock']:
            panel, iter = self.createPanel(s, toolbar_iter)
            panel.add(TextTextureWidget('toolbar.'+s, self))

        panel, button_iter = self.createPanel('button', toolbar_iter)
        t = ButtonTextureWidget('toolbar.button', self)
        t.nextRow()
        t.packLabeled(MaskResourceWidget('toolbar.button.left.mask', self, 'left'), 3)
        t.packLabeled(MaskResourceWidget('toolbar.button.right.mask',self,'right'), 3)
        panel.add(t)

        panel, iter = self.createPanel('pressed', button_iter)
        panel.add(TextureWidget('toolbar.button.pressed', self))
    def createMenuPanel(self):
        panel, menu_iter = self.createPanel('menu')
        table = StyleTable()
        table.pack(gtk.Label('Menu Masks'), 4)
        table.packLabeled(MaskResourceWidget('menu.arrow.mask', self, 'arrow'), 3)
        table.packLabeled(MaskResourceWidget('menu.selected.mask', self, 'selected'), 3)
        panel.add(table)

        panel, iter = self.createPanel('title', menu_iter)
        panel.add(FontTextTextureWidget('menu.title', self, parentrelative=0))

        panel, iter = self.createPanel('frame', menu_iter)
        t = TextureWidget('menu.frame', self, parentrelative=0)
        textColor = ColorResourceWidget('menu.frame.textColor', self)
        disableColor = ColorResourceWidget('menu.frame.disableColor', self)
        t.packLabeled(textColor, disableColor)
        t.nextRow()
        t.pack(FontResourceWidget('menu.frame.xft', self).defaultPack(), 4, 2)
        t.packLabeled(JustifyResourceWidget('menu.frame.justify', self))
        panel.add(t)

        panel, iter = self.createPanel('hilite', menu_iter)
        t = TextTextureWidget('menu.hilite', self)
        panel.add(t)

        panel, iter = self.createPanel('bullet', menu_iter)
        table = StyleTable(2)
        table.packLabeled(BulletMenuResourceWidget('menu.bullet', self), 
                          BulletPositionResourceWidget('menu.bullet.position', self))
        panel.add(table)
    def createWindowPanel(self):
        panel, window_iter = self.createPanel('window')
        vbox = gtk.VBox()
        vbox.pack_start(
            FontResourceWidget('window.xft', self).defaultPack(), 0, 0)
        vbox.pack_start(
            JustifyResourceWidget('window.justify', self).defaultPack(), 0, 0)
        panel.add(vbox)

        focus_list = ['focus', 'unfocus']

        for texture,wclass,parentrelative in [ ('title', TextureWidget, 0), 
                                               ('label', TextTextureWidget, 1), 
                                               ('button', ButtonTextureWidget, 1) ]:
            t_panel, t_iter = self.createPanel(texture, window_iter)
            for focus in focus_list:
                f_panel, f_iter = self.createPanel(focus, t_iter)
                f_panel.add(wclass('window.%s.%s' % (texture,focus), self, parentrelative))

        button_iter = self.tree.iter_parent(f_iter)
        button_panel = \
            self.notebook.get_nth_page(self.tree.get_value(button_iter, 0))
        table = StyleTable()
        table.pack(gtk.Label('Button Masks:'), 4)
        for b in ['close', 'max', 'stick', 'icon']:
            table.packLabeled(MaskResourceWidget('window.button.%s.mask' % b, self, b), 3)
        button_panel.add(table)

        pressed, pressed_iter = self.createPanel('pressed', button_iter)
        vbox = gtk.VBox()
        vbox.pack_start(gtk.Label('(Compatability with Blackbox)'), 0, 0, 10)
        vbox.pack_start(TextureWidget('window.button.pressed', self, parentrelative=0))
        pressed.add(vbox)

        for focus in focus_list:
            f_panel, f_iter = self.createPanel(focus, pressed_iter)
            f_panel.add(TextureWidget('window.button.pressed.'+focus,self,parentrelative=0))

        for texture,wclass, parentrelative in [ ('handle', TextureWidget, 0), 
                                                ('grip', TextureWidget, 1) ]:
            t_panel, t_iter = self.createPanel(texture, window_iter)
            for focus in focus_list:
                f_panel, f_iter = self.createPanel(focus, t_iter)
                f_panel.add(wclass('window.%s.%s' % (texture,focus), self, parentrelative))

        frame_panel, frame_iter = self.createPanel('frame', window_iter)
        table = StyleTable()
        for focus in focus_list:
            table.packLabeled(ColorResourceWidget('window.frame.%sColor' % focus, self))
        frame_panel.add(table)
    def createMiscPanel(self):
        panel, misc_iter = self.createPanel('Miscellaneous')
        table = StyleTable(4)
        for w in ['borderWidth', 'bevelWidth', 'handleWidth', 'frameWidth']:
            table.packLabeled(IntegerResourceWidget(w, self))
        table.packLabeled(ColorResourceWidget('borderColor', self))
        table.nextRow()
        table.packLabeled(StringResourceWidget('rootCommand', self), 3)
        panel.add(table)

# vim: set fdm=expr: 
-------------- next part --------------
# StyleBox: a style/theme editor for Openbox 2.
# Copyright (C) 2003  Ava Arachne Jarvis <ajar at katanalynx.dyndns.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

import string, os, copy, sys

# Basic data classes.  
# Only responsible for knowledge of somewhat weird details
# of the data type they represent.
class Color(object): 
    """
    Represents an 8-bit color in RGB space.
    A Color with a set name will ignore its red/green/blue components. 
    Setting red/green/blue on a Color will reset its name to None.

    Instance Variables:
        name  : str - a color name from rgb.txt.  
        red   : int - red component, from 0 to 255 inclusive.
        green : int - green component, from 0 to 255 inclusive.
        blue  : int - blue component, from 0 to 255 inclusive.
    """
    def parse(s): 
        """
        Parameters:
            s : str - in rgb:hex/hex/hex, #hexhexhex, or name format.
        Returns: 
            Color object.
        """
        color = None

        if s[0] == '#':
            clen = (len(s) - 1) / 3
            start = 1
            end = clen + 1
            color = [None, None, None]
            for i in range(3):
                color[i] = s[start:end]
                start = end
                end += clen
        elif s[0:4] == 'rgb:':
            color = s[4:].split('/')
        else:
            return Color(name = s)

        for i in range(3):
            clen = len(color[i])
            r = ''
            for j in range(clen):
                r += 'F'
            r = int(r, 16)
            color[i] = int((int(color[i], 16) / float(r)) * 255)

        return Color(color[0], color[1], color[2])
    parse = staticmethod(parse)

    def __init__(self, red = 0, green = 0, blue = 0, name = None): 
        """
        Parameters:
            red, green, blue : int - between 0 and 255.
            name : str - if given, red/green/blue are ignored.
        """
        self.name = name
        if not name:
            self.__red = red
            self.__green = green
            self.__blue = blue
    # Property methods 
    def setRed(self, red):
        if red < 0 or red > 255: 
            raise ValueError("red must be between 0 and 255!")
        self.__red = red
        self.name = None
    def setGreen(self, green):
        if green < 0 or green > 255: 
            raise ValueError("green must be between 0 and 255!")
        self.__green = green
        self.name = None
    def setBlue(self, blue):
        if blue < 0 or blue > 255: 
            raise ValueError("blue must be between 0 and 255!")
        self.__blue = blue
        self.name = None
    red = property(lambda c: c.__red, setRed)
    green = property(lambda c: c.__green, setGreen)
    blue = property(lambda c: c.__blue, setBlue)
    
    def __repr__(self): 
        if self.name:
            return '<Color: name = %s>' % self.name
        else:
            return '<Color: red = %s, green = %s, blue = %s>' % \
                    (`self.red`, `self.green`, `self.blue`)
    def __str__(self): 
        if self.name:
            return self.name
        else:
            str = '#'
            for c in [self.red, self.green, self.blue]:
                str += '%02x' % c
            return str
    def __eq__(self, other):
        if isinstance(other, Color):
            if other.name:
                return self.name and (self.name == other.name)
            elif self.name:
                return 0
            else:
                return self.red == other.red and \
                       self.blue == other.blue and \
                       self.green == other.green
        return 0
class Texture(object):
    """
    Represents an Openbox texture (as in the texture line, no colors).

    Class variables:
        RELIEFS : str list - Available reliefs.
        TYPES   : str list - Available gradient types.
        BEVELS  : str list - Available bevel render types.

    Instance variables:
        parentrelative : bool - if true, all other attributes are ignored.
        relief : int - index into RELIEFS.
        border : bool - ignored for any relief type except 'flat'.
        gradient : bool - if true, this is a gradient texture; 
                             false, a solid texture.  
        type : int - index into TYPES; only acknowledged if gradient is true.
        interlaced : bool - true if this texture is interlaced.
        bevel : int - index into BEVELS.
    """
    RELIEFS = ['flat', 'raised', 'sunken']
    TYPES = ['horizontal', 'vertical', 'diagonal', 'crossdiagonal', 
             'pipecross', 'elliptic', 'rectangle', 'pyramid']
    BEVELS = ['bevel1', 'bevel2']

    # keyword dictionaries for parsing 
    def makeListDict(list):
        dict = {}
        for i in range(len(list)): dict[list[i]] = i
        return dict
    dictRELIEFS = makeListDict(RELIEFS)
    dictTYPES = makeListDict(TYPES)
    dictBEVELS = makeListDict(BEVELS)
    del makeListDict
    
    def parse(s):
        words = s.split()
        t = Texture()
        for word in words:
            word = word.lower()
            if word in Texture.RELIEFS: 
                t.relief = Texture.dictRELIEFS[word]
            elif word == 'flatborder':
                t.relief = Texture.dictRELIEFS['flat']
                t.border = 1
            elif word in Texture.TYPES: 
                t.type = Texture.dictTYPES[word]
            elif word in Texture.BEVELS: 
                t.bevel = Texture.dictBEVELS[word]
            elif word == 'border': t.border = 1
            elif word == 'interlaced': t.interlaced = 1
            elif word == 'gradient': t.gradient = 1
            elif word == 'solid': t.gradient = 0
            elif word == 'parentrelative': t.parentrelative = 1
        return t
    parse = staticmethod(parse)

    def __init__(self):
        self.parentrelative = 0
        self.relief = 0
        self.border = 0
        self.gradient = 0
        self.type = 0
        self.interlaced = 0
        self.bevel = 0
    def __str__(self):
        if self.parentrelative: return 'parentrelative'

        s = Texture.RELIEFS[self.relief]
        if s == 'flat' and self.border:
            s += ' border'
        if self.gradient:
            s += ' gradient ' + Texture.TYPES[self.type]
        else:
            s += ' solid'
        if self.interlaced: s += ' interlaced'
        s += ' ' + Texture.BEVELS[self.bevel]
        return s
class XftFont(object):
    def __init__(self, family = None, size = 7, bold = 0, italic = 0, shadow = 0):
        self.family = family
        self.size = size
        self.bold = bold
        self.italic = italic
        self.shadow = shadow
    def parseFlags(self, s):
        bold, italic, shadow = 0, 0, 0
        for word in s.split():
            if word == 'bold': bold = 1
            elif word == 'normal': 
                bold = 0
                italic = 0
            elif word == 'shadow': shadow = 1
            elif word == 'italic': italic = 1
        self.bold = bold
        self.italic = italic
        self.shadow = shadow
    def strFlags(self):
        s = ''
        if self.bold and self.italic: s = 'bold italic'
        elif self.bold: s = 'bold'
        elif self.italic: s = 'italic'
        else: s = 'normal'

        if self.shadow: s += ' shadow'
        return s

# Resource classes.  
# Responsible for reading attributes from an 
# X resource database, and for returning strings suitable for 
# recreating the X resource database file.
from Xlib.rdb import ResourceDB
class Resource:
    """
    Represents an X resource line.

    Instance Variables:
        name  : str      - name of the resource
        value : *        - value of the resource.  Must implement __str__.
    """
    def __init__(self, name, value = ''):
        """
        Parameters:
            name : str      - name of the resource
            value : *       - default value of the resource
        """
        self.name = name
        self.value = value
    def parse(self, s):
        """
        Parses a string gotten from an X resource database.  This 
        should probably be overridden by child classes (they don't 
        have to override read()).
        """
        return s
    def read(self, rdb):
        """
        Retrives a value (if name is in the database) and sets value 
        to it via parse().
        Parameters:
            rdb : ResourceDB    - X resource database object
        """
        try:
            value = rdb.get(self.name, self.name)
        except:
            return
        if value != None: 
            try:
                self.value = self.parse(value)
            except:
                etype, evalue, traceback = sys.exc_info()
                print "Parse error occurred for resource %s:" % self.name
                print etype, ':', evalue
    def __str__(self):
        return '%s: %s\n' % (self.name, self.value)
class IntegerResource(Resource):
    def __init__(self, name, value = 0): Resource.__init__(self, name, value)
    def parse(self, s): return string.atoi(s)

class ColorResource(Resource): 
    def __init__(self, name, value = None):
        Resource.__init__(self, name, value or Color(0, 0, 0))
    def parse(self, s): return Color.parse(s)

class TextureResource(Resource): 
    def __init__(self, name, value = None):
        Resource.__init__(self, name, value or Texture())
    def parse(self, s): return Texture.parse(s)

class XftFontResource(Resource): 
    def __init__(self, name, value = None):
        Resource.__init__(self, name, value or XftFont())
    def read(self, rdb):
        vfont = rdb.get(self.name + '.font', self.name + '.font')
        vflags = rdb.get(self.name + '.flags', self.name + '.flags')
        vsize = rdb.get(self.name + '.size', self.name + '.size')
        # vfont and vflags can be '' and still valid
        if vfont != None: self.value.family = vfont
        if vflags != None: self.value.parseFlags(vflags)
        if vsize: self.value.size = string.atoi(vsize)
    def __str__(self):
        s = '%s.font: %s\n' % (self.name, self.value.family)
        s += '%s.size: %d\n' % (self.name, self.value.size)
        s += '%s.flags: %s\n' % (self.name, self.value.strFlags())
        return s
class JustifyResource(Resource):
    VALUES = ['left', 'center', 'right']
    def __init__(self, name, value = 'center'):
        Resource.__init__(self, name, value)
    def parse(self, s):
        s = s.lower()
        if s not in JustifyResource.VALUES: s = 'center'
        return s
class BulletResource(Resource):
    VALUES = ['empty', 'triangle', 'square', 'diamond']
    def __init__(self, name, value = 'triangle'):
        Resource.__init__(self, name, value)
    def parse(self, s): 
        s = s.lower()
        if s not in BulletResource.VALUES: s = 'triangle'
        return s
class BulletPositionResource(Resource):
    VALUES = ['left', 'right']
    def __init__(self, name, value = 'left'):
        Resource.__init__(self, name, value)
    def parse(self, s): 
        s = s.lower()
        if s not in BulletPositionResource.VALUES: s = 'left'
        return s

class Theme(object):
    """
    An Openbox theme.  Can be directly indexed via *full* resource names, 
    e.g. theme['toolbar.xft.font'].

    Instance Variables:
        resources : dict(str -> Resource)   - resources that make up the theme
        filename : str                      - name of the theme file
    """
    def __init__(self, filename = None):
        self.resources = {}
        self.filename = None
        self.groups = []
        if filename: self.read(filename)
        else: self.__initializeComponents()

    def addGroup(self, description = ''):
        self.groups.append({ 'desc': description, 'resources': [] })
    def addToGroup(self, resname):
        self.groups[-1]['resources'].append(resname)
    def read(self, filename):
        """
        Reads theme resources from a file.  Completely obliterates any 
        previous resource objects in resources.
        """
        self.__initializeComponents()
        self.filename = filename
        rdb = ResourceDB(self.filename)
        for k,r in self.resources.items(): r.read(rdb)

        # Initialize new resources from any old compatible ones
        compat_list = []
        for focus in ['focus', 'unfocus']:
            old_base = 'window.button.pressed'
            new_base = '%s.%s' % (old_base, focus)
            compat_list.append((new_base, old_base))
            for suffix in ['.color', '.colorTo']:
                compat_list.append((new_base+suffix, old_base+suffix))
        for r_new, r_old in compat_list:
            if not rdb.get(r_new, r_new):
                self[r_new].value = copy.deepcopy(self[r_old].value)
    def save(self, filename = None):
        """
        Saves theme resources to a file.

        Parameters:
            filename : str  - if given, saves to filename and uses that 
                              as the theme file for this object.
        """
        if filename:
            self.filename = filename
        f = open(self.filename, 'w')
        for group in self.groups:
            if group['desc']: 
                f.write('!! %s\n' % group['desc'])
            for r in group['resources']: 
                f.write(str(self[r]))
            f.write('\n')
        f.close()
    def __initializeComponents(self):
        self.addGroup('Theme information')
        for s in map(lambda s: 'style.'+s, ['name','author','date','credits','comments']):
            self.addResource(s)

        self.addGroup('Toolbar settings')
        self.addTexture('toolbar')
        self.addFont('toolbar')
        self.addGroup()
        for s in map(lambda s: 'toolbar.'+s, ['button','button.pressed']):
            self.addButtonTexture(s)
        for s in map(lambda s: 'toolbar.'+s, ['label','clock','windowLabel']):
            self.addGroup()
            self.addTextTexture(s)

        self.addGroup('Menu settings')
        self.addGroup()
        self.addFontTextTexture('menu.title')
        self.addGroup()
        self.addFontTextTexture('menu.frame')
        self.addResource('menu.frame.disableColor', ColorResource)
        self.addGroup()
        self.addTextTexture('menu.hilite')
        self.addGroup()
        self.addResource('menu.bullet', BulletResource)
        self.addResource('menu.bullet.position', BulletPositionResource)

        self.addGroup('General window settings')
        self.addTexture('window.button.pressed')
        self.addFont('window')

        for s in 'focus unfocus'.split():
            self.addGroup('%sed window settings' % s)
            self.addResource('window.frame.%sColor' % s, ColorResource)
            for t in map(lambda x: 'window.%s.%s' % (x, s), ['title', 'handle', 'grip']):
                self.addGroup()
                self.addTexture(t)
            self.addGroup()
            self.addTextTexture('window.label.%s' % s)
            self.addGroup()
            self.addButtonTexture('window.button.%s' % s)
            self.addGroup()
            self.addTexture('window.button.pressed.%s' % s)

        self.addGroup('Global width settings')
        for s in 'borderWidth bevelWidth handleWidth frameWidth'.split():
            self.addResourceWithDefault(s, 1, IntegerResource)

        self.addGroup('Miscellaneous settings')
        self.addResource('borderColor', ColorResource)
        self.addResource('rootCommand')

        self.addGroup('Bitmap button masks')
        self.addGroup()
        for s in 'close max stick icon'.split():
            self.addResource('window.button.%s.mask' % s)
        self.addGroup()
        for s in 'arrow selected'.split():
            self.addResource('menu.%s.mask' % s)
        self.addGroup()
        for s in 'left right'.split():
            self.addResource('toolbar.button.%s.mask' % s)
    # Mapping methods 
    def __getitem__(self, key):
        return self.resources[key]
    def __setitem__(self, key, val):
        self.resources[key] = val
    def __delitem__(self, key):
        del self.resources[key]
    def __contains__(self, key):
        return key in self.resources
    # Methods to work with adding resources in groups. 
    def addResource(self, name, rclass = Resource):
        """
        Adds to resources a single resource object.
        Parameters:
            name : str          - name of the resource
            rclass : class      - (Resource-derived) class of the resource
        """
        self[name] = rclass(name)
        self.addToGroup(name)
    def addResourceWithDefault(self, name, value, rclass = Resource):
        """
        Like addResource(), but with a default value.
        """
        self[name] = rclass(name, value)
        self.addToGroup(name)
    def addTexture(self, name):
        """
        Adds a texture and three color resources, using basename name.
        """
        self.addResource(name, TextureResource)
        self.addResource('%s.color' % name, ColorResource)
        self.addResource('%s.colorTo' % name, ColorResource)
        self.addResource('%s.borderColor' % name, ColorResource)
    def addButtonTexture(self, name):
        """
        Adds all things a "Texture" needs, plus a picColor.
        """
        self.addTexture(name)
        self.addResource('%s.picColor' % name, ColorResource)
    def addTextTexture(self, name):
        """
        Adds all things a "Texture" needs, plus a textColor.
        """
        self.addTexture(name)
        self.addResource('%s.textColor' % name, ColorResource)
    def addFont(self, name):
        """
        Adds font-specific resources (which include justify).
        """
        self.addResource('%s.justify' % name, JustifyResource)
        self.addResourceWithDefault('%s.font' % name, 'fixed')
        self.addResource('%s.xft' % name, XftFontResource)
        self.addResourceWithDefault('%s.xft.shadow.offset' % name, 1, IntegerResource)
        self.addResourceWithDefault('%s.xft.shadow.tint' % name, 25, IntegerResource)
    def addFontTextTexture(self, name):
        """
        Combines font-specific resources with a TextTexture.
        """
        self.addTextTexture(name)
        self.addFont(name)
    
# vim: set fdm=expr: 
-------------- next part --------------
#!/usr/bin/python
#
# StyleBox: a style/theme editor for Openbox 2.
# Copyright (C) 2003  Ava Arachne Jarvis <ajar at katanalynx.dyndns.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

import sys
sys.path.append('/usr/local/lib/stylebox')

import pygtk
pygtk.require('2.0')
import gtk

from StyleBox import StyleBox

print StyleBox.FRONTIS

s = StyleBox()
s.show_all()
gtk.main()


More information about the openbox mailing list