X-Git-Url: https://git.ucc.asn.au/?a=blobdiff_plain;f=research%2Ftransmission_spectroscopy%2Fsimulator%2Fpgu-0.18%2Fbuild%2Flib%2Fpgu%2Fgui%2Ftheme.py;fp=research%2Ftransmission_spectroscopy%2Fsimulator%2Fpgu-0.18%2Fbuild%2Flib%2Fpgu%2Fgui%2Ftheme.py;h=976676d4f7591979e9af6cbe7868e04d43a0914a;hb=70a96cca12cb006506461d26cd112bab179fe74c;hp=0000000000000000000000000000000000000000;hpb=8caf60af39689a3546074f0c68d14c3a2e28191e;p=matches%2Fhonours.git diff --git a/research/transmission_spectroscopy/simulator/pgu-0.18/build/lib/pgu/gui/theme.py b/research/transmission_spectroscopy/simulator/pgu-0.18/build/lib/pgu/gui/theme.py new file mode 100644 index 00000000..976676d4 --- /dev/null +++ b/research/transmission_spectroscopy/simulator/pgu-0.18/build/lib/pgu/gui/theme.py @@ -0,0 +1,509 @@ +# theme.py + +""" +""" +import os, re +import pygame + +try: + from configparser import ConfigParser +except: + from ConfigParser import ConfigParser + +from .const import * +from . import widget +from . import surface +from .errors import StyleError +from .basic import parse_color, is_color + +__file__ = os.path.abspath(__file__) + +class Theme: + """Theme interface. + + If you wish to create your own theme, create a class with this interface, and + pass it to gui.App via gui.App(theme=MyTheme()). + + """ + + # Image extensions automatically recognized by the theme class + image_extensions = (".gif", ".jpg", ".bmp", ".png", ".tga") + + def __init__(self,dirs='default'): + """Theme constructor. + + Keyword arguments: + dirs -- Name of the theme dir to load a theme from. May be an + absolute path to a theme, if pgu is not installed, or if you + created your own theme. May include several dirs in a list if + data is spread across several themes. + + Example: + theme = gui.Theme("default") + theme = gui.Theme(["mytheme","mytheme2"]) + + """ + self.config = {} + self._loaded = [] + self.cache = {} + self._preload(dirs) + pygame.font.init() + + def _preload(self,ds): + if not isinstance(ds, list): + ds = [ds] + for d in ds: + if d not in self._loaded: + self._load(d) + self._loaded.append(d) + + def _load(self, name): + #theme_dir = themes[name] + + #try to load the local dir, or absolute path + dnames = [name] + + #if the package isn't installed and people are just + #trying out the scripts or examples + dnames.append(os.path.join(os.path.dirname(__file__),"..","..","data","themes",name)) + + #if the package is installed, and the package is installed + #in /usr/lib/python2.3/site-packages/pgu/ + #or c:\python23\lib\site-packages\pgu\ + #the data is in ... lib/../share/ ... + dnames.append(os.path.join(os.path.dirname(__file__),"..","..","..","..","share","pgu","themes",name)) + dnames.append(os.path.join(os.path.dirname(__file__),"..","..","..","..","..","share","pgu","themes",name)) + dnames.append(os.path.join(os.path.dirname(__file__),"..","..","share","pgu","themes",name)) + for dname in dnames: + if os.path.isdir(dname): break + + if not os.path.isdir(dname): + raise Exception('could not find theme '+name) + + # Normalize the path to make it look nicer (gets rid of the ..'s) + dname = os.path.normpath(dname) + + # Try parsing the theme in the custom txt file format + fname = os.path.join(dname,"config.txt") + if os.path.isfile(fname): + try: + f = open(fname) + for line in f.readlines(): + args = line.strip().split() + + if len(args) < 3: + continue + + pcls = "" + (cls, attr, vals) = (args[0], args[1], args[2:]) + if (":" in cls): + (cls, pcls) = cls.split(":") + + self.config[cls, pcls, attr] = (dname, vals) + finally: + f.close() + return + + # Try parsing the theme data as an ini file + fname = os.path.join(dname,"style.ini") + if os.path.isfile(fname): + cfg = ConfigParser() + f = open(fname,'r') + cfg.readfp(f) + for section in cfg.sections(): + cls = section + pcls = '' + if cls.find(":")>=0: + cls,pcls = cls.split(":") + for attr in cfg.options(section): + vals = cfg.get(section,attr).strip().split() + self.config[cls,pcls,attr] = (dname, vals) + return + + # The folder probably doesn't contain a theme + raise IOError("Cannot load theme: missing style.ini or config.txt") + + def _get(self, cls, pcls, attr): + key = (cls, pcls, attr) + if not key in self.config: + return + + if key in self.cache: + # This property is already in the cache + return self.cache[key] + + (dname, vals) = self.config[key] + + if (os.path.splitext(vals[0].lower())[1] in self.image_extensions): + # This is an image attribute + v = pygame.image.load(os.path.join(dname, vals[0])) + + elif (attr == "color" or attr == "background"): + # This is a color value + v = parse_color(vals[0]) + + elif (attr == "font"): + # This is a font value + name = vals[0] + size = int(vals[1]) + if (name.endswith(".ttf")): + # Load the font from a file + v = pygame.font.Font(os.path.join(dname, name), size) + else: + # Must be a system font + v = pygame.font.SysFont(name, size) + + else: + try: + v = int(vals[0]) + except: + v = vals[0] + self.cache[key] = v + return v + + # TODO - obsolete, use 'getstyle' below instead + def get(self,cls,pcls,attr): + try: + return self.getstyle(cls, pcls, attr) + except StyleError: + return 0 + + # Returns the style information, given the class, sub-class and attribute names. + # This raises a StylError if the style isn't found. + def getstyle(self, cls, pcls, attr): + """Interface method -- get the value of a style attribute. + + Arguments: + cls -- class, for example "checkbox", "button", etc. + pcls -- pseudo class, for example "hover", "down", etc. + attr -- attribute, for example "image", "background", "font", "color", etc. + + This method is called from gui.style + + """ + + if not self._loaded: + # Load the default theme + self._preload("default") + + o = (cls, pcls, attr) + + v = self._get(cls, pcls, attr) + if v: + return v + + v = self._get(cls, "", attr) + if v: + return v + + v = self._get("default", "", attr) + if v: + return v + + # The style doesn't exist + self.cache[o] = 0 + raise StyleError("Style not defined: '%s', '%s', '%s'" % o) + + # Draws a box around the surface in the given style + def box(self, style, surf): + c = (0, 0, 0) + if style.border_color != 0: + c = style.border_color + w,h = surf.get_size() + + surf.fill(c,(0,0,w,style.border_top)) + surf.fill(c,(0,h-style.border_bottom,w,style.border_bottom)) + surf.fill(c,(0,0,style.border_left,h)) + surf.fill(c,(w-style.border_right,0,style.border_right,h)) + + def getspacing(self,w): + # return the top, right, bottom, left spacing around the widget + if not hasattr(w,'_spacing'): #HACK: assume spacing doesn't change re pcls + s = w.style + xt = s.margin_top+s.border_top+s.padding_top + xr = s.padding_right+s.border_right+s.margin_right + xb = s.padding_bottom+s.border_bottom+s.margin_bottom + xl = s.margin_left+s.border_left+s.padding_left + w._spacing = xt,xr,xb,xl + return w._spacing + + + def resize(self,w,func): + # Returns the rectangle expanded in each direction + def expand_rect(rect, left, top, right, bottom): + return pygame.Rect(rect.x - left, + rect.y - top, + rect.w + left + right, + rect.h + top + bottom) + + def theme_resize(width=None,height=None): + s = w.style + + pt,pr,pb,pl = (s.padding_top,s.padding_right, + s.padding_bottom,s.padding_left) + bt,br,bb,bl = (s.border_top,s.border_right, + s.border_bottom,s.border_left) + mt,mr,mb,ml = (s.margin_top,s.margin_right, + s.margin_bottom,s.margin_left) + # Calculate the total space on each side + top = pt+bt+mt + right = pr+br+mr + bottom = pb+bb+mb + left = pl+bl+ml + ttw = left+right + tth = top+bottom + + tilew,tileh = None,None + if width != None: tilew = width-ttw + if height != None: tileh = height-tth + tilew,tileh = func(tilew,tileh) + + if width == None: width = tilew + if height == None: height = tileh + + #if the widget hasn't respected the style.width, + #style height, we'll add in the space for it... + width = max(width-ttw, tilew, w.style.width) + height = max(height-tth, tileh, w.style.height) + + #width = max(tilew,w.style.width-tw) + #height = max(tileh,w.style.height-th) + + r = pygame.Rect(left,top,width,height) + + w._rect_padding = expand_rect(r, pl, pt, pr, pb) + w._rect_border = expand_rect(w._rect_padding, bl, bt, br, bb) + w._rect_margin = expand_rect(w._rect_border, ml, mt, mr, mb) + + # align it within it's zone of power. + rect = pygame.Rect(left, top, tilew, tileh) + dx = width-rect.w + dy = height-rect.h + rect.x += (w.style.align+1)*dx/2 + rect.y += (w.style.valign+1)*dy/2 + + w._rect_content = rect + + return (w._rect_margin.w, w._rect_margin.h) + return theme_resize + + + def paint(self,w,func): + # The function that renders the widget according to the theme, then calls the + # widget's own paint function. + def theme_paint(s): +# if w.disabled: +# if not hasattr(w,'_disabled_bkgr'): +# w._disabled_bkgr = s.convert() +# orig = s +# s = w._disabled_bkgr.convert() + +# if not hasattr(w,'_theme_paint_bkgr'): +# w._theme_paint_bkgr = s.convert() +# else: +# s.blit(w._theme_paint_bkgr,(0,0)) +# +# if w.disabled: +# orig = s +# s = w._theme_paint_bkgr.convert() + + if w.disabled: + if (not (hasattr(w,'_theme_bkgr') and + w._theme_bkgr.get_width() == s.get_width() and + w._theme_bkgr.get_height() == s.get_height())): + w._theme_bkgr = s.copy() + orig = s + s = w._theme_bkgr + s.fill((0,0,0,0)) + s.blit(orig,(0,0)) + + if w.background: + w.background.paint(surface.subsurface(s,w._rect_border)) + + self.box(w.style, surface.subsurface(s,w._rect_border)) + r = func(surface.subsurface(s,w._rect_content)) + + if w.disabled: + s.set_alpha(128) + orig.blit(s,(0,0)) + + w._painted = True + return r + return theme_paint + + def event(self,w,func): + def theme_event(e): + rect = w._rect_content + if (not rect): + # This should never be the case, but it sometimes happens that _rect_content isn't + # set before a mouse event is received. In this case we'll ignore the event. + return func(e) + + if e.type == MOUSEBUTTONUP or e.type == MOUSEBUTTONDOWN: + sub = pygame.event.Event(e.type,{ + 'button':e.button, + 'pos':(e.pos[0]-rect.x,e.pos[1]-rect.y)}) + elif e.type == CLICK: + sub = pygame.event.Event(e.type,{ + 'button':e.button, + 'pos':(e.pos[0]-rect.x,e.pos[1]-rect.y)}) + elif e.type == MOUSEMOTION: + sub = pygame.event.Event(e.type,{ + 'buttons':e.buttons, + 'pos':(e.pos[0]-rect.x,e.pos[1]-rect.y), + 'rel':e.rel}) + else: + sub = e + return func(sub) + + return theme_event + + def update(self,w,func): + def theme_update(s): + if w.disabled: return [] + r = func(surface.subsurface(s,w._rect_content)) + if type(r) == list: + dx,dy = w._rect_content.topleft + for rr in r: + rr.x,rr.y = rr.x+dx,rr.y+dy + return r + return theme_update + + def open(self,w,func): + def theme_open(widget=None,x=None,y=None): + if not hasattr(w,'_rect_content'): + # HACK: so that container.open won't resize again! + w.rect.w,w.rect.h = w.resize() + rect = w._rect_content + ##print w.__class__.__name__, rect + if x != None: x += rect.x + if y != None: y += rect.y + return func(widget,x,y) + return theme_open + + def decorate(self,widget,level): + """Interface method -- decorate a widget. + + The theme system is given the opportunity to decorate a widget + methods at the end of the Widget initializer. + + Arguments: + widget -- the widget to be decorated + level -- the amount of decoration to do, False for none, True for + normal amount, 'app' for special treatment of App objects. + + """ + + w = widget + if level == False: return + + if type(w.style.background) != int: + w.background = Background(w,self) + + if level == 'app': return + + for k,v in list(w.style.__dict__.items()): + if k in ('border','margin','padding'): + for kk in ('top','bottom','left','right'): + setattr(w.style,'%s_%s'%(k,kk),v) + + w.paint = self.paint(w,w.paint) + w.event = self.event(w,w.event) + w.update = self.update(w,w.update) + w.resize = self.resize(w,w.resize) + w.open = self.open(w,w.open) + + def render(self,surf,box,r,size=None,offset=None): + """Renders a box using an image. + + Arguments: + surf -- the target pygame surface + box -- pygame surface or color + r -- pygame rect describing the size of the image to render + + If 'box' is a surface, it is interpreted as a 3x3 grid of tiles. The + corner tiles are rendered in the corners of the box. The side tiles + are used to fill the top, bottom and sides of the box. The centre tile + is used to fill the interior of the box. + """ + + if box == 0: return + + if is_color(box): + surf.fill(box,r) + return + + x,y,w,h=r.x,r.y,r.w,r.h + + if (size and offset): + pass +# destx = x +# desty = y + + # Calculate the size of each tile + tilew, tileh = int(box.get_width()/3), int(box.get_height()/3) + xx, yy = x+w, y+h + src = pygame.rect.Rect(0, 0, tilew, tileh) + dest = pygame.rect.Rect(0, 0, tilew, tileh) + + # Render the interior of the box + surf.set_clip(pygame.Rect(x+tilew, y+tileh, w-tilew*2, h-tileh*2)) + src.x,src.y = tilew,tileh + for dest.y in range(y+tileh,yy-tileh,tileh): + for dest.x in range(x+tilew,xx-tilew,tilew): + surf.blit(box,dest,src) + + # Render the top side of the box + surf.set_clip(pygame.Rect(x+tilew,y,w-tilew*2,tileh)) + src.x,src.y,dest.y = tilew,0,y + for dest.x in range(x+tilew, xx-tilew*2+tilew, tilew): + surf.blit(box,dest,src) + + # Render the bottom side + surf.set_clip(pygame.Rect(x+tilew,yy-tileh,w-tilew*2,tileh)) + src.x,src.y,dest.y = tilew,tileh*2,yy-tileh + for dest.x in range(x+tilew,xx-tilew*2+tilew,tilew): + surf.blit(box,dest,src) + + # Render the left side + surf.set_clip(pygame.Rect(x,y+tileh,xx,h-tileh*2)) + src.y,src.x,dest.x = tileh,0,x + for dest.y in range(y+tileh,yy-tileh*2+tileh,tileh): + surf.blit(box,dest,src) + + # Render the right side + surf.set_clip(pygame.Rect(xx-tilew,y+tileh,xx,h-tileh*2)) + src.y,src.x,dest.x=tileh,tilew*2,xx-tilew + for dest.y in range(y+tileh,yy-tileh*2+tileh,tileh): + surf.blit(box,dest,src) + + # Render the upper-left corner + surf.set_clip() + src.x,src.y,dest.x,dest.y = 0,0,x,y + surf.blit(box,dest,src) + + # Render the upper-right corner + src.x,src.y,dest.x,dest.y = tilew*2,0,xx-tilew,y + surf.blit(box,dest,src) + + # Render the lower-left corner + src.x,src.y,dest.x,dest.y = 0,tileh*2,x,yy-tileh + surf.blit(box,dest,src) + + # Render the lower-right corner + src.x,src.y,dest.x,dest.y = tilew*2,tileh*2,xx-tilew,yy-tileh + surf.blit(box,dest,src) + + +class Background(widget.Widget): + def __init__(self,value,theme,**params): + params['decorate'] = False + widget.Widget.__init__(self,**params) + self.value = value + self.theme = theme + + def paint(self, s, size=None, offset=None): + r = pygame.Rect(0,0,s.get_width(),s.get_height()) + v = self.value.style.background + self.theme.render(s,v,r, size=size, offset=offset) +