Source code for windowObjects

from Graphics import *
from Myro import *
import sys
import os
portal_root = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")
if portal_root not in sys.path: sys.path.append(portal_root)
#sys.path.append(os.path.dirname(os.path.realpath(__file__))+"/..")
from CommonFunctions import utilityFunctions as util
from CommonFunctions.makeShapeFunctions import *
import base64           # Import base64 for basic encryption
import copy             # Import copy for copying drag-and-drop objects
import GLib             # Import graphics library to allow for scheduling timed events
import Cairo            # Import Cairo to create efficient backgrounds
import Gdk              # Import Gdk to set efficient backgrounds
import Gtk              # Import Gtk to add timeout functions for animations
import time             # Import time for a sleep that will always be in seconds
import warnings         # Import warnings to disable some useless ones


# JH: There is a default DeprecationWarning that
# "object.__new__() takes no parameters"
# when doing multiple inhertance on Shapes,
# probably due to some c# ironpython interactions
# Reading more about it, it felt safe to ignore this warning
warnings.filterwarnings("ignore",category=DeprecationWarning)


[docs]class Layout(object): """ Base class meant to help organize buttons and other interface objects. """
[docs] def __init__(self): """ Constructs a layout object. """ self.frame = Frame((0,0)) self.width = 0 self.height = 0 self.parent = None self.window = None self.shapes = [] self.mouseUpShapes = [] self.mouseDownShapes = []
[docs] def reset(self): """ Resets the width an height of the layout object. The basic layout object does not have any other attributes, but objects that extend the basic layout object may have a more involved reset. """ self.width = 0 self.height = 0
[docs] def add(self, shape, clickable=True): """ Adds a shape to this layout. Args: shape(:class:`~.Shape`): The shape to add to the layout. clickable(bool): If true, this shape can be returned when picked. """ if clickable: self.shapes.append(shape) if isinstance(shape, Layout): shape.parent=self shape.window=self.window shape.frame.draw(self.frame) else: shape.draw(self.frame) shape.window=getWindow() self.updateSizeAndPosition(shape) if self.parent is not None: self.parent.resetSizeAndPosition()
[docs] def draw(self, window): """ Draws the layout to a window. Args: window(:class:`~.Window`): The window to draw to. """ window.draw(self.frame) self.setWindow(window)
[docs] def updateSizeAndPosition(self, shape): """ Updates the size and position of the indicated shape. Does nothing in the basic layout. Args: shape: The shape to update. """ pass
[docs] def resetSizeAndPosition(self): """ Resets this size and position of this layout. In contrast to reset, also resets all clickable shapes, as well as its parents. """ self.height = 0 self.width = 0 self.reset() for shape in self.shapes: self.updateSizeAndPosition(shape) if self.parent is not None: self.parent.resetSizeAndPosition()
[docs] def moveTo(self, x, y): """ Moves this frame to the indicated coordinates. Args: x(float): The new x coordinate of the layout. y(float): The new y coordinate of the layout. """ self.frame.moveTo(x, y)
[docs] def pick(self, x, y): """ Returns the clickable shape at a queried position. Pick is generally used for on click events, adn will thus ignore all shapes that are not clickable. Args: x(float): The x coordinate of the point to be queried. y(float): The y coordinate of the point to be queried. Return: The clickable shape at the queried location, or None if there was no clickable shape a the queried location. """ for shape in self.shapes: if shape.hit(x, y): if hasattr(shape, "pick"): return shape.pick(x, y) else: return shape return None
[docs] def hit(self, x, y): """ Indicates whether the queried position hits this layout. Because the basic layout has no bounding box, this function will always return true. Args: x(float): The x coordinate of the point to be queried. y(float): The y coordinate of the point to be queried. Return: Always returns True. """ return True
[docs] def setWindow(self, window): """ Sets the window for this layout, and all child layouts. The main purpose of this function is to allow you to add any number of layouts to this layout before drawing it to the window, and still ensure that all child layouts have a valid reference to the main window. Args: window(:class:`~.Window`): The window to set for this layout. """ self.window = window for shape in self.shapes: shape.setWindow(window)
[docs]class HorizontalLayout(Layout): """ Layout meant to organize objects horizontally. """
[docs] def __init__(self): """ Constructs a HorizontalLayout object. """ Layout.__init__(self) self.reset() self.spacing = 5
[docs] def reset(self): """ Resets this layout. For the HorizontalLayout this simply means that the x position, used to position all objects, is reset to 0. """ self.x=0
[docs] def hit(self, x, y): """ Indicates whether this layout was hit. The HorizontalLayout has a width, height and origin, and this function simply checks whether the indicated coordinate lies within this bounding bos. Args: x(float): The x coordinate of the point to be queried. y(float): The y coordinate of the point to be queried. Return: True if the coordinate (x,y) lies within the bounding-box of this layout, False otherwise. """ corner = self.frame.getTrueScreenPoint((0,0)) if x < corner.x or x > (corner.x + self.width): return False if y < corner.y or y > (corner.y + self.height): return False return True
[docs] def updateSizeAndPosition(self, shape): """ Updates the size and position of the indicated shape. Positions the shape right next to the other shapes. Args: shape(:class:`~.Shape` or :class:`~.Layout`): The object to update. """ w = util.getWidth(shape) h = util.getHeight(shape) # Most shapes are anchored around their center, but layouts are anchored # around their top left if isinstance(shape, Layout): shape.moveTo(self.x, 0) else: shape.moveTo(self.x + w/2, h/2) if h > self.height: self.height = h self.x += w + self.spacing self.width += w if len(self.shapes) > 0: self.width += self.spacing
[docs]class VerticalLayout(Layout): """ Layout meant to organize objects vertically. """
[docs] def __init__(self): """ Constructs a VerticalLayout object. """ Layout.__init__(self) self.y=0 self.spacing = 5
[docs] def reset(self): """ Resets this layout. For the VerticalLayout this simply means that the y position, used to position all objects, is reset to 0. """ self.y=0
[docs] def hit(self, x, y): """ Indicates whether this layout was hit. The VerticalLayout has a width, height and origin, and this function simply checks whether the indicated coordinate lies within this bounding bos. Args: x(float): The x coordinate of the point to be queried. y(float): The y coordinate of the point to be queried. Return: True if the coordinate (x,y) lies within the bounding-box of this layout, False otherwise. """ corner = self.frame.getTrueScreenPoint((0,0)) if x < corner.x or x > (corner.x + self.width): return False if y < corner.y or y > (corner.y + self.height): return False return True
[docs] def updateSizeAndPosition(self, shape): """ Updates the size and position of the indicated shape. Positions the shape right below the other shapes. Args: shape(:class:`~.Shape` or :class:`~.Layout`): The object to update. """ w = util.getWidth(shape) h = util.getHeight(shape) # Most shapes are anchored around their center, but layouts are anchored # around their top left if isinstance(shape, Layout): shape.moveTo(0, self.y) else: shape.moveTo(w/2, self.y + h/2) if w > self.width: self.width = w self.y += h + self.spacing self.height += h if len(self.shapes) > 0: self.height += self.spacing
[docs]class ScrolledLayout(Layout): """ Layout that allows scrolling in different directions. """
[docs] def __init__(self, width=500, height=500): """ Constructs a ScrolledLayout object. Args: width(float): The width of the scrolled layout. height(float): The height of the scrolled layout. """ Layout.__init__(self) self.frame = Rectangle((0,0), (width,height), fill=Color(0,0,0,0)) self.outerFrame = ClippedFrame((0,0), (width,height)) self.outerFrame.moveTo(-width/2,-height/2) self.innerFrame = Frame((0,0)) self.fixedWidth = width self.fixedHeight = height self.xOffset = 5 self.yOffset = 5 self.innerFrame.moveTo(self.xOffset, self.yOffset) self.outerFrame.draw(self.frame) self.innerFrame.draw(self.outerFrame) # Optional attributes for discrete scrolling self.page = 0 self.pageWidth = 0 self.nrOfFrames = 0 self.maxPages = 0 # Attributes used to keep track of animations self.scrollDirection = 1 self.currentFrame = 0
[docs] def moveTo(self, x, y): """ Moves this frame to the indicated coordinates. Overwrite of the basic :class:`~.Layout` class because the scrolled layout is drawn on a :class:`~.Rectangle`, rather than a :class:`~.frame`, yet we want its behavior to match that of a frame. Args: x(float): The new x coordinate of the layout. y(float): The new y coordinate of the layout. """ self.frame.moveTo(x + self.frame.width/2, y + self.frame.height/2)
[docs] def scrollX(self, distance): """ Scroll the indicated distance in the horizontal direction. Args: distance(float): The distance to scroll. """ self.innerFrame.move(distance, 0)
[docs] def scrollY(self, distance): """ Scroll the indicated distance in the vertical direction. Args: distance(float): The distance to scroll. """ self.innerFrame.move(0, distance)
[docs] def hit(self, x, y): """ Indicates whether this layout was hit. The ScrolledLayout is drawn on a :class:`~.Rectangle`, and this function simply checks whether that rectangle was hit. Args: x(float): The x coordinate of the point to be queried. y(float): The y coordinate of the point to be queried. Return: True if the coordinate (x,y) lies within the bounding-box of this layout, False otherwise. """ return self.frame.hit(x, y)
[docs] def add(self, shape): """ Adds a shape to this layout. Overwrites the basic :class:`~.Layout` because objects need to be added to the innerFrame, rather than to the outer frame. Args: shape(:class:`~.Shape`): The shape to add to the layout. """ self.shapes.append(shape) if isinstance(shape, Layout): shape.parent=self shape.frame.draw(self.innerFrame) else: shape.draw(self.innerFrame) self.updateSizeAndPosition(shape) if self.parent is not None: self.parent.resetSizeAndPosition()
[docs] def scrollBack(self): """ Scrolls a descrete distance backward in the horizontal direction. """ if self.page <= 0: return self.page -= 1 self.currentFrame = self.nrOfFrames self.scrollDirection = -1 e=TimeoutEvent(self.nrOfFrames, self.scrollBackStep) Gtk.Timeout.Add(10,e)
[docs] def scrollNext(self): """ Scrolls a descrete distance forward in the horizontal direction. """ if self.page >= self.maxPages: return self.page += 1 self.currentFrame = self.nrOfFrames self.scrollDirection = 1 e=TimeoutEvent(self.nrOfFrames, self.scrollBackStep) GLib.Timeout.Add(10,e)
[docs] def scrollBackStep(self): """ Callback for scrolling in the horizontal direction. """ if self.currentFrame > 0: self.currentFrame -= 1 increment = self.scrollDirection*(self.pageWidth/self.nrOfFrames) prevX = self.page*self.pageWidth x = self.xOffset - prevX + self.currentFrame*increment self.innerFrame.moveTo(x, self.yOffset) self.window.QueueDraw()
[docs]class TextP(Text): """ Text object with different default values. We should probably use the makeText function instead, because TextP does not seem to have any attributes that would require a separate class. """
[docs] def __init__(self,point1,text): self.fontSize=18 self.fill=Color("black") self.yJustification="bottom" self.xJustification="left"
def configText(self,newText,color=Color("black"),fontSize=18): self.setText(newText) if fontSize != self.fontSize: self.fontSize=fontSize self.setFontSize(self.fontSize) self.fill=color #TODO:doesn't Text already have a default copy? RV def copyAttributes(self, other): self.fontSize = other.fontSize self.fill = other.fill self.yJustification = other.yJustification self.xJustification = other.xJustification def __copy__(self): ret=TextP(self.getP1(), self.text) ret.copyAttributes(self) return ret
[docs]class ButtonBase(object): """ A base object for all buttons. Designed to be extended so the button will actually have some body. """
[docs] def __init__(self): # Size self.width=abs(self.getP1()[0]-self.getP2()[0]) self.height=abs(self.getP1()[1]-self.getP2()[1]) self.oX=self.width/2 self.oY=self.height/2 self.cX=self.center[0] self.cY=self.center[1] # Padding self.xPadding = 5 self.yPadding = 5 # Colors self.defaultColor = Color("white") self.defaultTextColor = Color("black") self.highlightColor = Color("yellow") self.highlightTextColor = Color("black") self.disabledColor = Color("lightgrey") self.disabledTextColor = Color("grey") # State self.enabled = True self.isHighlighted = False # Text #TODO: Can we get rid of TextP, it doesn't do anything special does it? RV #JG: Agreed, replaced TextP with regular text. #self.text=TextP((-self.oX+self.xPadding, self.yPadding)," ") self.text=makeText(pos=(-self.oX+self.xPadding, self.yPadding), size=18) self.text.draw(self) # Apply colors self.applyColor() # Mouse over text self.mouseOverText = " " # Action for this button self.action = None # Set the window that this object has been drawn to self.windowWrapper = None
[docs] def setWindowWrapper(self, windowWrapper): """ Sets the window-wrapper of this button. The window wrapper has a similar role as the Calico main window, but allows us to register mouse enter, and mouse leave events. Args: windowWrapper(:class:`~.Someting`): The window-wrapper to set. """ self.windowWrapper = windowWrapper self.windowWrapper.connectMouseOverEnter(self, self.onMouseEnter) self.windowWrapper.connectMouseOverLeave(self, self.onMouseLeave) self.window = self.windowWrapper.mainWindow
[docs] def applyColor(self): """ Applies a color to this button, depending on whether it is enabled or highlighted. """ if self.enabled and not self.isHighlighted: self.enable() elif self.enabled and self.isHighlighted: self.enable() self.highlight() else: self.disable(self)
[docs] def onMouseEnter(self, win, event): """ Callback for mouse enter which highlights this button. Args: win(:class:`~.Window`): Reference to the Calico window object. event(:class:`~.Event`): The event triggering this callback. """ self.highlight() if self.windowWrapper: if hasattr(self.windowWrapper, "setDescription"): self.windowWrapper.setDescription(self.mouseOverText)
[docs] def onMouseLeave(self, win, event): """ Callback for the mouse leave which removes the highlight. Args: win(:class:`~.Window`): Reference to the Calico window object. event(:class:`~.Event`): The event triggering this callback. """ self.unhighlight() if self.windowWrapper: if hasattr(self.windowWrapper, "hideDescription"): self.windowWrapper.hideDescription()
[docs] def mouseDown(self, win, event): """ Callback for if this button is clicked on. Args: win(:class:`~.Window`): Reference to the Calico window object. event(:class:`~.Event`): The event triggering this callback. """ if self.action is not None and self.enabled: self.action(self)
[docs] def mouseDownAlt(self, win, event): """ Callback for mouse down events. In contrast to :meth:`~.mouseDown`, this function first checks if this button was actually clicked on, meaning it can be used without a :class:`~.Layout`. Args: win(:class:`~.Window`): Reference to the Calico window object. event(:class:`~.Event`): The event triggering this callback. """ if self.hit(event.x,event.y): if self.action is not None and hasattr(self.action, '__call__'): self.action()
[docs] def enable(self): """ Enable this button. """ self.enabled = True self.fill = self.defaultColor self.text.color = self.defaultTextColor
[docs] def disable(self): """ Disable this button. """ self.enabled = False self.fill = self.disabledColor self.text.color = self.disabledTextColor
[docs] def highlight(self): """ Highlight this button. """ if not self.enabled: return self.isHighlighted = True self.fill = self.highlightColor
[docs] def unhighlight(self): """ Return to default color after highlight. """ self.isHighlighted = False if self.enabled: self.enable() else: self.disable()
[docs] def copyAttributes(self, other): """ Copies all attributes of the other button to this button. Args: other(:class:`~.ButtonBase`): Button to copy attributes from. """ self.width = other.width self.height = other.height self.oX = other.oX self.oY = other.oY self.cX = other.cX self.cY = other.cY self.xPadding = other.xPadding self.yPadding = other.yPadding self.defaultColor = other.defaultColor self.defaultTextColor = other.defaultTextColor self.highlightColor = other.highlightColor self.highlightTextColor = other.highlightTextColor self.disabledColor = other.disabledColor self.disabledTextColor = other.disabledTextColor self.enabled = other.enabled self.text = copy.copy(other.text) self.text.draw(self)
[docs]class ButtonP(RoundedRectangle): """ Old style button that directly extends the shape of the button itself. Should probably be deprecated. """
[docs] def __init__(self,point1,point2,rad, xMargin=5, yMargin=5):#,avatar=None): self.width=abs(self.getP1()[0]-self.getP2()[0]) self.height=abs(self.getP1()[1]-self.getP2()[1]) self.oX=self.width/2 self.oY=self.height/2 self.cX=self.center[0] self.cY=self.center[1] self.action=None self.xMargin=xMargin self.yMargin=yMargin self.defaultColor = Color("white") self.activeColor = Color("yellow") self.disabledColor = Color("lightgrey") self.disabledTextColor = Color("grey") self.textColor = Color("black") self.enabled = True self.avatar = None # Mouse over text self.mouseOverText = "" self.function = None self.winHandle=None
#if avatar!=None: # self.avatar=Picture(avatar) # self.avatar.draw(self) #else: # self.avatar=None def setText(self,newText,color=Color("black"),fontSize=18): self.text.setText(newText) if fontSize != self.text.fontSize: self.text.setFontSize(fontSize) #self.fontSize: # self.fontSize=fontSize #self.text.setFontSize(self.fontSize) self.text.fill=color
[docs] def init(self, centerText=False, text=None): """ Draws all elements of the button to the button. Because this object inherets from a C# object, it can not take extra parameters to its constructor. Thus, this object now effectively has two constructors: the real constructor, and this constructor. """ if self.avatar: self.avatar.draw(self) #TODO: Can we get rid of TextP, it doesn't do anything special does it? RV if centerText: self.text=TextP((self.xMargin,self.yMargin)," ") self.text.xJustification="center" else: self.text=TextP((-self.oX+self.xMargin,self.yMargin)," ") #self.text.yJustification="bottom" #self.text.xJustification="left" self.text.draw(self) if text!=None: self.setText(text) # Meta data text #TODO: Can we get rid of TextP, it doesn't do anything special does it? RV self.metaText=TextP((-self.oX + self.width - self.xMargin, self.yMargin)," ") #self.text.yJustification="bottom" self.metaText.xJustification="right" self.metaText.draw(self) self.enable()
def enable(self): self.enabled = True self.fill = self.defaultColor self.text.color = self.textColor self.metaText.color = self.textColor def disable(self): self.enabled = False self.fill = self.disabledColor self.text.color = self.disabledTextColor self.metaText.color = self.disabledTextColor def highlight(self): if not self.enabled : return self.fill = self.activeColor def unhighlight(self): if self.enabled: self.enable() else: self.disable() def setAction(self,a): if a!=None and hasattr(a, '__call__'): self.action=a else: print(a," is not a function and cannot be set as an action.") def clickAction(self,o,e): if self.hit(e.x,e.y): if self.action!=None and hasattr(self.action, '__call__'): self.action() def setup(self,win): self.winHandle=win self.winHandle.onMouseDown(self.clickAction)
[docs]class EntryBox(Rectangle): """ Entry box that allows the user to insert text. """
[docs] def __init__(self,p1,p2): """ Constructs an EntryBox. Args: p1(tuple): Top left corner of the box. p2(tuple): Bottom right corner of the box. """ cX=self.center[0] cY=self.center[1] fs=abs(p1[1]-p2[1]) self.focus=False self.editable=True #if false then the entry will only display visbleSymbol self.visibleText=True self.visibleSymbol="*" self.winHandle=None self.maxChars=5 self.justNumbers=True self.isBlinking=False pad=5 self.disp=Text((p2[0]-cX-pad,p1[1]-cY+self.height/2+fs/2-pad),"",xJustification="right",yJustification="bottom",color=Color("black"),fontSize=fs) self.disp.draw(self) self.trueText=self.disp.getText() self.cursor=Text((self.disp.x,self.disp.y),"|",xJustification="right",yJustification="bottom",color=Color("black"),fontSize=fs) self.cursor.visible = False self.cursor.draw(self) self.fill=Color("white") self.setWidth(1)
[docs] def setText(self,text): """ Sets the text of this entry box, which doubles as its current value. Args: text(str): The text to be set to this EntryBox. """ validKey=True if isinstance(text,int) or isinstance(text,float): #adds functionality to set ints manually to the entryText #also important this is the first check else the whole text[0:4] will crash #if not dealing with a string text=str(text) elif text=="BackSpace": self.disp.text=self.disp.text[0:-1] self.trueText=self.trueText[0:-1] self.winHandle.QueueDraw() return elif text[0:4]=="Key_": text=text[4:len(text)] elif text=="period": text="." elif text=="minus": text="-" elif text=="plus": text="+" elif text=="slash": text="/" elif text=="backslash": text="/" elif text=="semicolon": text=";" elif text=="colon": text=";" elif text=="bracketleft": text="[" elif text=="bracketright": text="]" elif text=="Return": self.setFocus(False) return else: if self.justNumbers or len(text)>1: validKey=False if validKey: if len(self.disp.text)<self.maxChars: if self.visibleText: self.disp.text+=text else: self.disp.text+=self.visibleSymbol self.trueText+=text self.winHandle.QueueDraw()
[docs] def keyPress(self, win, event): """ Keypress callback event that be directly registered to a window to make this EntryBox functional. Args: win(:class:`~.Window`): Reference to the Calico window object. event(:class:`~.Event`): The event triggering this callback. """ testEvent=event if self.focus and self.editable: self.setText(event.key) ''' if event.key=="BackSpace": self.disp.text=self.disp.text[0:-1] self.trueText=self.trueText[0:-1] self.winHandle.QueueDraw() return elif event.key[0:4]=="Key_": event.key=event.key[4:len(event.key)] elif event.key=="period": event.key="." elif event.key=="minus": event.key="-" elif event.key=="plus": event.key="+" elif event.key=="slash": event.key="/" elif event.key=="backslash": event.key="/" elif event.key=="semicolon": event.key=";" elif event.key=="colon": event.key=";" elif event.key=="bracketleft": event.key="[" elif event.key=="bracketright": event.key="]" elif event.key=="Return": self.setFocus(False) return else: if self.justNumbers or len(event.key)>1: validKey=False if validKey: if len(self.disp.text)<self.maxChars: if self.visibleText: self.disp.text+=event.key else: self.disp.text+=self.visibleSymbol self.trueText+=event.key self.winHandle.QueueDraw() '''
[docs] def mouseDown(self,win,event): """ Mousedown callback event that enables the focus of this entry-box. Args: win(:class:`~.Window`): Reference to the Calico window object. event(:class:`~.Event`): The event triggering this callback. """ if self.hit(event.x,event.y): self.setFocus(True) else: self.setFocus(False)
[docs] def setFocus(self, focus=True): """ Sets the focus of this EntryBox. Args: focus(bool): Whether to set (True), or unset (False) the focus. """ self.focus=focus self.updateBlink()
[docs] def setEditable(self, editable=True): """ Sets whether this EntryBox is currently editable. Args: focus(bool): Whether to set (True), or unset (False) the editable. """ self.editable = editable self.updateBlink()
[docs] def mouseUp(self,win,event): """ Mouseup callback event for this entry-box. Currently not used. Args: win(:class:`~.Window`): Reference to the Calico window object. event(:class:`~.Event`): The event triggering this callback. """ pass
[docs] def setup(self,win,editable=True,visible=True): """ Performs the setup of this EntryBox. Because the EntryBox extends a Calico shape, the constructor can not be normally changed. Thus, this setup function effectively functions as a secondary constructor. Args: win(:class:`~.Window`): Reference to the Calico object this EntryBox is drawn to. editable(bool): Indicates whether this EntryBox should startout editable. visible(bool): Indicates whether this EntryBox should startout visible. """ self.winHandle=win self.winHandle.onKeyPress(self.keyPress) self.winHandle.onMouseDown(self.mouseDown) self.winHandle.onMouseUp(self.mouseUp) self.setEditable(editable) self.visibleText=visible
#def copyAttributes(self,other): # self.setup(other.winHandle,other.editable,other.visible) #def __copy__(self): # ret=EntryBox(self.getP1(),self.getP2()) # ret.copyAttributes(self) # return ret
[docs]class RoundedButton(RoundedRectangle, ButtonBase): """ Creates a rounded, clickable button. """
[docs] def __init__(self, point1, point2, rad): """ Constructs a rounded button. Args: point1(tuple): The top left corner of the button. point2(tuple): The bottom right corner of the button. ra(float): The radius of the rounded corners of the button. """ RoundedRectangle.__init__(self, point1, point2, rad) ButtonBase.__init__(self)
[docs] def setup(self, text, desc=" ", fontSize=24): """ Performs a setup on the RoundedButton. Because the rounded button extend a Calico shape, we can not change the constructor. As such the setup function is effectively a secondary constructor. Args: text(str): The text to be shown on the button. desc(str): The description that should be shown on mouse-over. fonstSize(int): The font size of the text on the button. """ self.text.fontSize=fontSize self.text.setText(text) self.mouseOverText = desc
[docs] def copyAttributes(self, other): """ Copies all attributes of the other button to this button. Args: other(:class:`~.RoundedButton`): Button to copy attributes from. """ ButtonBase.copyAttributes(self, other) self.tag=other.tag self.window=other.window
def __copy__(self): """ Copy operator for this button. """ ret=RoundedButton(self.getP1(), self.getP2(), self.radius) ret.copyAttributes(self) return ret
[docs]def createRoundedButton(w=250, h=40, text=" ", action=None, desc=" ", window=None, fontSize=24): """ Function that creates a rounded button. """ button = RoundedButton((0,0), (w,h), 5) button.setup(text, desc, fontSize=fontSize) button.action = action if window is not None: button.setWindowWrapper(window) return button
[docs]class DragAndDropButton(Rectangle, ButtonBase): """ Class for drag-and-drop buttons. Probably a bit too specialized to the drag-and-drop control activity. """
[docs] def __init__(self,p1,p2): """ Constructs a drag-and-drop button. Args: point1(tuple): The top left corner of the button. point2(tuple): The bottom right corner of the button. """ Rectangle.__init__(self, p1, p2) ButtonBase.__init__(self) self.entryText=[] self.data={}
[docs] def setup(self,dictionaryData, fontSize=24): """ Performs a setup on the DragAndDropButton. Because the drag-and-drop button extends a Calico shape, we can not change the constructor. As such the setup function is effectively a secondary constructor. Args: dictionaryData(dict): Dictionary with data. fonstSize(int): The font size of the text on the button. """ self.data=copy.copy(dictionaryData) self.text.fontSize=fontSize self.text.setText(self.data["command"]) pad=5 self.pW=65 #width of entry text boxes self.pH=25 #height of entry text boxes #create the entry boxes at (0,0) now and move later for i in range(self.data["numParams"]): #hardcoding the widght of the entry box at 65 for now #cX=-self.width/2 +self.pW/2+pad+(self.width-2*self.pW)+i*(self.pW+pad) cX=(self.width/2)-(2*self.pW)+self.pW/2+i*(self.pW) cY=0 self.entryText.append(EntryBox((cX-self.pW/2,cY-self.pH/2),(cX+self.pW/2,cY+self.pH/2))) self.entryText[i].draw(self) self.entryText[i].setup(self.window,self.data["editable"])
#move entry boxes to their proper location #for i in range(len(self.entryText)): # #self.entryText[i].moveTo(self.text.width+i*self.pW,self.cY-self.height/2) # self.entryText[i].moveTo(-self.width/2 +self.pW/2+pad+(self.width-2*self.pW)+i*(self.pW+pad),0)
[docs] def setDefaultValue(self): """ Sets all default values. """ if "defaultParams" in self.data: for i in range(self.data["numParams"]): if len(self.entryText)==len(self.data["defaultParams"]): self.entryText[i].setText(self.data["defaultParams"][i]) else: print("Error!!! Mismatch in number of parameters and default values of parameters in setDefaultValue")
[docs] def makeEditable(self): """ Makes all EntryBoxes on this button editable. """ for e in self.entryText: e.setEditable(True)
[docs] def hitEntry(self,testX,testY): """ Tests whether the provided coordinates hits one of the entry boxes. Args: textX(float): The x coordinate. textY(float): The y coordinate. Return: True if an EntryBox is at the indicated position, False otherwise. """ for e in self.entryText: if e.hit(testX,testY): return True return False
[docs] def getEntryValues(self): """ Retrieves the EntryBox values of this button. Return: List containing the values of each EntryBox. """ ret=[] for e in self.entryText: ret.append(e.trueText) return ret
[docs] def copyAttributes(self, other): """ Copies all attributes of the other button to this button. Args: other(:class:`~.RoundedButton`): Button to copy attributes from. """ ButtonBase.copyAttributes(self, other) #self.data=copy.copy(other.data) self.tag=other.tag self.window=other.window #necessary b/c the entryText in setup need this self.setup(other.data)
#for i in range(len(other.entryText)): # e=other.entryText[i] # #self.entryText.append(EntryBox(e.getP1(),e.getP1())) # self.entryText.append(EntryBox((0,0),(50,50))) # self.entryText[i].draw(self) # self.entryText[i].setup(other.window,other.data["editable"]) def __copy__(self): """ Copy operator for this button. """ ret=DragAndDropButton(self.getP1(),self.getP2()) ret.copyAttributes(self) return ret
[docs]def createPanelButton(panel,x,y,w,h,text,tag,avatar=None, buttonColor=Color("white"),textColor=Color("black"),fS=18): """ Creates a button to add to a panel. """ #text is drawn to the center of the panel so it must be #offset to the upper left corner (0,0) oX=panel.width/2 oY=panel.height/2 #radius of rounded rectangle rad=2 button=RoundedRectangle((x-oX,y-oY),(x-oX+w,y-oY+h),rad,color=buttonColor) button.outline=Color("black") button.tag=tag #very important to tag this object button.draw(panel) if avatar!=None: pic=Picture(avatar) pic.draw(button) #button.move(oX-infoW,oY-2*infoH) textObj=Text((0,5),text,color=textColor,fontSize=fS) textObj.yJustification = "bottom" textObj.draw(button) button.draw(panel) return [button,textObj]
[docs]def createPanelHeader(panel,x,y,text,textColor=Color("black"),fS=30): """ Creates a header to add to a panel. """ #text is drawn to the center of the panel so it must be #offset to the upper left corner (0,0) oX=panel.width/2 oY=panel.height/2 #button.move(oX-infoW,oY-2*infoH) textObj=Text((x-oX,y-oY), text, color=textColor) textObj.yJustification = "bottom" textObj.xJustification = "left" textObj.fontFace = "Helvetica Neue Light" textObj.fontSize = fS textObj.draw(panel) return textObj
[docs]def createButton(width, height, action, text, fillColor=Color("white"), textColor=Color("black"),fontSize=18, desc="",avatar=None, radius=5): """ Creates a ButtonP object. Should probably be deprecated. """ button = ButtonP((0,0), (width,height), radius) button.avatar=avatar button.fill=fillColor button.outline=Color("black") button.tag=text button.init() button.text.configText(text,textColor,fontSize) button.action=action return button
[docs]def createHeader(text,textColor=Color("black"),fS=30): """ Creates a general text objects with settings appropriate for a header. """ #text is drawn to the center of the panel so it must be #offset to the upper left corner (0,0) textObj=Text((0,0), text, color=textColor) textObj.yJustification = "center" textObj.xJustification = "center" textObj.fontFace = "Helvetica Neue Light" textObj.fontSize = fS return textObj
[docs]class Panel(Rectangle): """ Panel object to draw buttons and other items to. Maybe conflicts with the idea of layouts. """
[docs] def __init__(self, point1, point2): #offset of the panel and needed to transform any coordinates to top left corner self.oX=self.width/2 self.oY=self.height/2 #center of the panel needed to register clicks x and y properly self.cX=self.center[0] self.cY=self.center[1] self.pad=5 self.button=[] self.buttonRadius=5 #TODO: Can we get rid of TextP, it doesn't do anything special does it? RV self.title=TextP((-self.oX+self.pad,-self.oY+4*self.pad)," ") self.title.draw(self) self.text=[]
#would like to connect the panelClick here, within Panel, but #it has to be done in WindowLRC AFTER the panel is drawn. RV #self.connect("click",self.panelClick) def addButton(self, x, y, width, height, action, text, fillColor=Color("white"),textColor=Color("black"),fontSize=18, desc="",avatar=None):#,avatar=None): b = createButton(width, height, action, text, fillColor, textColor, fontSize, desc, avatar, self.buttonRadius) b.moveTo(x - self.oX + width/2, y - self.oY + height/2) self.button.append(b) ## self.button.append(ButtonP(Point(x-self.oX,y-self.oY),Point(x+width-self.oX,y+height-self.oY),self.buttonRadius))#,avatar=avatar)) ## self.button[-1].avatar=avatar ## self.button[-1].fill=fillColor ## self.button[-1].outline=Color("black") ## self.button[-1].tag=text ## self.button[-1].init() ## self.button[-1].text.configText(text,textColor,fontSize) ## self.button[-1].action=action b.draw(self) return self.button[-1] def panelClick(self, o, e): if self.visible: for b in self.button: if b.hit(e.x,e.y): if b.action != None: b.action(b.text.getText()) def setVisible(self,vis): self.visible=vis self.update() def addText(self,loc,t,**kwargs): self.text.append(Text((loc[0]-self.oX,loc[1]-self.oY),t)) self.text[-1].draw(self) if "fontSize" in kwargs: self.text[-1].setFontSize(kwargs["fontSize"]) if "fontColor" in kwargs: self.text[-1].fill=kwargs["fontColor"] if "xJustification" in kwargs: self.text[-1].setXJustification(kwargs["xJustification"]) if "yJustification" in kwargs: self.text[-1].setYJustification(kwargs["yJustification"]) if "fontType" in kwargs: if kwargs["fontType"]=="Helvetica Neue Light": self.text[-1].fontFace="Helvetica Neue Light" else: #default self.text[-1].fontFace="Sans Seriff"
#should probably add functionality to change font type, weight, and slant
[docs]def adminPanel(foo): """ Enables an admin panel? """ bar=ask(base64.b64decode('RW50ZXIgTFJDIFBhc3N3b3Jk')) if base64.b64encode(bar)=='MzA3c2NyaWJieQ==': a=int(ask(base64.b64decode('RW50ZXIgaGlnaGVzdCBzdGFnZQ=='))) b=int(ask(base64.b64decode('RW50ZXIgaGlnaGVzdCBsZXZlbA=='))) foo.gameData[0].highestStage=a foo.gameData[0].highestLevel=b print(foo.gameData[0].highetLevel)
[docs]class WindowLRC():
[docs] def __init__(self,title,width,height,tag=" ", reuseWindow=False): self.windowFrame = Frame(0,0) try: window = getWindow() except Exception: window = None if window and window.IsRealized and reuseWindow: try: # First, try my new blocking version of reset window.resetBlocking(False) except: # Reset is asynchronous, hopefully 0.4 seconds is enough time # for it to finish its work window.reset() time.sleep(0.4) self.win = window else: self.win=Window(title,width,height) self.win.setBackground(Color("white")) self.win.onMouseDown(self.mouseDown) self.win.onKeyPress(self.keyPressed) self.windowFrame.draw(self.win) #self.win.tag=tag self.panel=[] self.parent=None #window that launched this window self.child=None #window launched from this window self.onShow=None
##############Utility Functions############## def addPanel(self, title, x, y, width, height,color=Color("white")): self.panel.append(Panel(Point(x,y),Point(x+width,y+height))) self.panel[-1].fill=color self.panel[-1].title.setText(title) self.panel[-1].draw(self.windowFrame) #the panel click has to be connected AFTER the panel is drawn. RV self.panel[-1].connect("click",self.panel[-1].panelClick) return self.panel[-1] ##############Callbacks##################### def mouseDown(self,obj,event): pass #print(event.x,event.y) def keyPressed(self,obj,event): if str(event.key)=="q" or str(event.key)=="Q": self.quit() def hideWindow(self): self.win.Visible=False def showWindow(self): self.win.Visible=True if self.onShow is not None: self.onShow() def Destroy(self): self.win.Destroy() def quit(self): if self.parent != None: self.parent.mainWindow.showWindow() #eventually this will need to be implemented when levelWindow is refactored #if self.child != None: self.win.Destroy() def GetPosition(self): return self.win.GetPosition() def GetWidth(self): return self.win.width def GetHeight(self): return self.win.height def Move(self,x,y): self.win.Move(x,y) def isVisible(self): return self.win.Visible
[docs]class statObject():
[docs] def __init__(self,name,score,elapseTime,win,winTime,lose,restart): self.levelName=name self.score=score self.elapseTime=elapseTime self.win=win self.winTime=winTime self.lose=lose self.restart=restart
def update(self, newStat): self.score+=newStat.score self.elapseTime+=newStat.elapseTime self.win+=newStat.win if newStat.winTime<self.winTime: self.winTime=newStat.winTime self.lose+=newStat.lose self.restart+=newStat.restart def __str__(self): toWrite="\n\tLevel:"+str(self.levelName)+"\n" toWrite+="\tWins:"+str(self.win)+"\n" toWrite+="\tWinTimes:"+str(self.winTime)+"\n" toWrite+="\tElapseTime:"+str(self.elapseTime)+"\n" toWrite+="\tRestarts:"+str(self.restart)+"\n" toWrite+="\tLoses:"+str(self.lose)+"\n" toWrite+="\n" return toWrite
[docs]class GameData():
[docs] def __init__(self,ver): if ver=="0.1": self.ver=ver self.highestStage=0 self.highestLevel=0 self.data={} self.data["stats"]={}
def updateStats(self,levelName,stats): if self.ver=="0.1": if not levelName in self.data["stats"]: self.data["stats"][levelName]=[] self.data["stats"][levelName].append(stats) def __str__(self): if self.ver=="0.1": print("Version number is ",self.ver) print("Highest Stage is ",self.highestStage) print("Highest Level is ",self.highestLevel) print("Recorded Levels and Stats:") stats=self.data["stats"] for key,value in stats.items(): print("Level ",key) for v in value: print(v) ''' ,newStat): if len(self.stats)==0: self.stats.append(newStat) else: foundExistingStat=False for s in self.stats: if s.levelName==newStat.levelName: foundExistingStat=True s.update(newStat) break if not foundExistingStat: self.stats.append(newStat) '''
[docs]class BackgroundWrapper(object):
[docs] def __init__(self, window): self.window = window self.background = None self.backWidth = None self.backHeight = None self.backOffsetX = None self.backOffsetY = None self.backCenter = None self.frameOffsetX = None self.frameOffsetY = None self.preferedWidth = 800 self.preferedHeight = 800
def setBackgroundImage(self, image, width=3000, height=1500, xOffset=0, yOffset=0, center=False): self.background = Cairo.ImageSurface(image) self.backWidth = width self.backHeight = height self.backOffsetX = xOffset self.backOffsetY = yOffset self.backCenter = center self._calcFrameOffset() self._paintBackground() def _calcFrameOffset(self): prevOffsetX = self.frameOffsetX prevOffsetY = self.frameOffsetY self.frameOffsetX = (self.window.width - self.preferedWidth) / 2 self.frameOffsetY = (self.window.height - self.preferedHeight) / 2 if self.frameOffsetX < 0: self.frameOffsetX = 0 if self.frameOffsetY < 0: self.frameOffsetY = 0 return not(self.frameOffsetX == prevOffsetX and self.frameOffsetY == prevOffsetY) def _paintBackground(self): if self.background: pixmap = Gdk.Pixmap(None, self.backWidth, self.backHeight, 24) with Gdk.CairoHelper.Create(pixmap) as g: # The -1 here is to fix a bug where the left most column of pixels # would not be painted g.Translate(self.backOffsetX, self.backOffsetY) g.SetSourceSurface(self.background, -1 + self.frameOffsetX, self.frameOffsetY) g.Paint() self.window.canvas.BinWindow.SetBackPixmap(pixmap, False)
[docs]class WindowBase(object):
[docs] def __init__(self, name="", width=800, height=800, reuseWindow = False, parent=None): self.parent = parent try: window = getWindow() except Exception: window = None if window and window.IsRealized and reuseWindow: window.reset() self.mainWindow = window else: self.mainWindow = Window(name,width,height) self.mouseOverEnterCallbacks = {} self.mouseOverLeaveCallbacks = {} self.mouseOverShapes = [] # Background variables self.background = None self.backWidth = None self.backHeight = None self.backOffsetX = None self.backOffsetY = None self.backCenter = None self.frameOffsetX = None self.frameOffsetY = None # Dimensions to aim for self.preferedWidth = width self.preferedHeight = height # Layout self.layout = Layout() self.layout.draw(self.mainWindow)
def setup(self): self.mainWindow.onMouseDown(self.mouseDown) self.mainWindow.onMouseUp(self.mouseUp) self.mainWindow.onMouseMovement(self.mouseMove) self.mainWindow.onConfigure(self.onResize) def showWindow(self): self.mainWindow.showWindow() def hideWindow(self): self.mainWindow.hideWindow() def quit(self): if self.parent != None: self.parent.mainWindow.showWindow() self.mainWindow.Destroy() def isVisible(self): return self.mainWindow.Visible def getTitle(self): return self.mainWindow.title def draw(self, shape): self.layout.add(shape) #shape.draw(self.mainWindow) def drawDecor(self, shape): self.layout.add(shape, False) def setBackgroundImage(self, image, width=3000, height=1500, xOffset=0, yOffset=0, center=False): self.background = Cairo.ImageSurface(image) self.backWidth = width self.backHeight = height self.backOffsetX = xOffset self.backOffsetY = yOffset self.backCenter = center self._calcFrameOffset() self._paintBackground() def onResize(self, args): if not self._calcFrameOffset(): return self.layout.frame.moveTo(self.frameOffsetX, self.frameOffsetY) self._paintBackground() def connectMouseOverEnter(self, shape, function): if shape not in self.mouseOverEnterCallbacks: self.mouseOverEnterCallbacks[shape]=[function] else: if function not in self.mouseOverEnterCallbacks[shape]: self.mouseOverEnterCallbacks[shape].append(function) def connectMouseOverLeave(self, shape, function): if shape not in self.mouseOverLeaveCallbacks: self.mouseOverLeaveCallbacks[shape]=[function] else: if function not in self.mouseOverLeaveCallbacks[shape]: self.mouseOverLeaveCallbacks[shape].append(function) def disconnect(self, shape): if shape in self.mouseOverEnterCallbacks: del self.mouseOverEnterCallbacks[shape] if shape in self.mouseOverLeaveCallbacks: del self.mouseOverLeaveCallbacks[shape] if shape in self.mouseOverShapes: self.mouseOverShapes.remove(shape) def mouseUp(self, o, e): shape = self.layout.pick(e.x, e.y) if hasattr(shape, "mouseUp"): shape.mouseUp(o, e) def mouseDown(self, o, e): shape = self.layout.pick(e.x, e.y) if hasattr(shape, "mouseDown"): shape.mouseDown(o, e) def mouseMove(self, o, e): # First, handle leave events toRemove = [] for shape in self.mouseOverShapes: if not shape.hit(e.x, e.y): for callback in self.mouseOverLeaveCallbacks[shape]: callback(shape, e) toRemove.append(shape) for shape in toRemove: self.mouseOverShapes.remove(shape) # Now, handle enter events for shape, callbacks in self.mouseOverEnterCallbacks.items(): if shape.hit(e.x, e.y): for callback in callbacks: callback(shape, e) if shape not in self.mouseOverShapes: self.mouseOverShapes.append(shape) # Redraw the screen self.mainWindow.QueueDraw() def addIdleEvent(self, event): GLib.Idle.Add(event) def addTimedEvent(self, event, time=33): GLib.Timeout.Add(time, event) def _calcFrameOffset(self): prevOffsetX = self.frameOffsetX prevOffsetY = self.frameOffsetY self.frameOffsetX = (self.mainWindow.width - self.preferedWidth) / 2 self.frameOffsetY = (self.mainWindow.height - self.preferedHeight) / 2 if self.frameOffsetX < 0: self.frameOffsetX = 0 if self.frameOffsetY < 0: self.frameOffsetY = 0 return not(self.frameOffsetX == prevOffsetX and self.frameOffsetY == prevOffsetY) def _paintBackground(self): if self.background and self.backCenter: pixmap = Gdk.Pixmap(None, self.backWidth, self.backHeight, 24) with Gdk.CairoHelper.Create(pixmap) as g: # The -1 here is to fix a bug where the left most column of pixels # would not be painted g.Translate(self.backOffsetX, self.backOffsetY) g.SetSourceSurface(self.background, -1 + self.frameOffsetX, self.frameOffsetY) g.Paint() self.mainWindow.canvas.BinWindow.SetBackPixmap(pixmap, False) #Miscellaneous functions that are always useful def canCastToNumber(self,v): try: int(v) except: try: return float(v) except: return False else: return True else: return True def castToNumber(self,v): try: return int(v) except: try: return float(v) except: return v
[docs]class TimeoutEvent:
[docs] def __init__(self, iterations, action): self.iterations = iterations self.action = action
def __call__(self): self.action() self.iterations-=1 if self.iterations <= 0: return False else: return True