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 updateBlink(self):
"""
Registers a callback that causes the cursor of this EntryBox to blink.
"""
if self.focus and self.editable:
if not self.isBlinking:
self.isBlinking = True
GLib.Timeout.Add(500, self.blink)
self.cursor.visible = True
self.winHandle.QueueDraw()
else:
# Note: we do not update the status of isBlinking here
# Only the timeout should to that, such that we can
# never have mutiple "blinking" events registered at the
# same time.
self.cursor.visible = False
self.winHandle.QueueDraw()
[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
[docs] def blink(self):
"""
Callback that enables the cursor to blink.
"""
if not self.winHandle or not self.winHandle.IsRealized or not self.focus or not self.editable:
self.cursor.visible = False
if self.winHandle:
self.winHandle.QueueDraw()
self.isBlinking = False
return False
self.cursor.visible = not self.cursor.visible
self.winHandle.QueueDraw()
return True
#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 MenuWindow():
[docs] def __init__(self, parent=None, name="", header="", reuseWindow=False, options=True):
self.parent=parent
# 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
#create windows and panel
self.preferedWidth = 800
self.preferedHeight = 700
self.mainWindow=WindowLRC(name,self.preferedWidth,self.preferedHeight, reuseWindow=reuseWindow)
self.onResize(None)
self.mainWindow.parent=parent
if options:
self.optionPanel=self.mainWindow.addPanel("Options",600,0,200,700,Color("gray"))
mainPanelWidth = self.preferedWidth - self.optionPanel.width
else:
self.optionPanel=None
mainPanelWidth = self.preferedWidth
self.descriptionPanel=self.mainWindow.addPanel("Description:",0,600,mainPanelWidth,100,Color("lightblue"))
self.mainPanel=self.mainWindow.addPanel(header,0,0,mainPanelWidth,600,Color("white"))
if self.mainWindow.parent != None:
self.mainPanel.addButton(0,550,125,50,self.returnToPortalAction,"Back To Portal")
# Add mouse-over description to buttons
self.currentButton = None
self.mainWindow.win.onMouseMovement(self.onMouseMove)
self.mainWindow.win.onConfigure(self.onResize)
self.columnY = [150, 150]
self.columsX = [25, 325]
# Set the description text placeholder
self.decription=Text((-(self.descriptionPanel.width/2) + 5,-10), " ", color=Color("black"))
self.decription.yJustification = "bottom"
self.decription.xJustification = "left"
self.decription.fontFace = "Helvetica Neue Light"
self.decription.fontSize = 16
self.decription.draw(self.descriptionPanel)
self.decription.visible = False
def setParent(self,parent):
self.parent=parent
def returnToPortalAction(self,buttonName=None):
self.mainWindow.quit()
def openPDF(self, filename):
if sys.platform == 'linux2':
subprocess.call(["xdg-open", "Graphics01.pdf"])
else:
print("Opening: ", filename)
os.startfile(filename)
#####################
# Wrapper Functions #
#####################
def addKeyPress(self,func):
self.mainWindow.win.onKeyPress(func)
#TODO:should probably have a clearKeyPress function as well. RV
def showWindow(self):
self.beforeShow()
self.mainWindow.showWindow()
self.afterShow()
def hideWindow(self):
self.mainWindow.hideWindow()
def isVisible(self):
return self.mainWindow.win.Visible
def getTitle(self):
return self.mainWindow.win.title
def draw(self, shape):
shape.draw(self.mainWindow.windowFrame)
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()
######################
# Callback functions #
######################
[docs] def afterShow(self):
"""
Called right after the window is revealed.
This is a good place to start animations that should be
shown once the window is opened.
"""
pass
[docs] def beforeShow(self):
"""
Called right before the window is revealed.
This is a good place to painting of the window and other
updates that you don't want the user to see.
"""
self.update()
[docs] def update(self):
"""
Function should update the window to reflect the current state
of the window.
Should be called whenever the window needs redrawing. By default
it is called on beforeShow().
"""
pass
def launchActivity(self, buttonName=None):
pass
def onResize(self, args):
if not self._calcFrameOffset():
return
self.mainWindow.windowFrame.moveTo(self.frameOffsetX, self.frameOffsetY)
self._paintBackground()
def _calcFrameOffset(self):
prevOffsetX = self.frameOffsetX
prevOffsetY = self.frameOffsetY
self.frameOffsetX = (self.mainWindow.win.width - self.preferedWidth) / 2
self.frameOffsetY = (self.mainWindow.win.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.win.canvas.BinWindow.SetBackPixmap(pixmap, False)
###################
### Descriptions ##
###################
def createButton(self, text, desc):
button = createButton(250, 40, self.launchActivity, text)
button.mouseOverText = desc
return button
def addButton(self, text, desc, column=0):
button = self.mainPanel.addButton(self.columsX[column],self.columnY[column],250,40,self.launchActivity,text)
button.mouseOverText = desc
self.columnY[column] += 50
return button
def addHeader(self, text, column=0):
#add buttons to activity panel
createPanelHeader(self.mainPanel, self.columsX[column], self.columnY[column]+40, text)
self.columnY[column] += 50
def onMouseMove(self, o, e):
for button in self.mainPanel.button:
if button.hit(e.x, e.y):
if button == self.currentButton: return
if self.currentButton is not None:
self.currentButton.unhighlight()
self.currentButton = button
self.currentButton.highlight()
self.decription.text = self.currentButton.mouseOverText
self.decription.visible = True
self.mainWindow.win.QueueDraw()
return
if self.currentButton is not None:
self.decription.visible = False
self.currentButton.unhighlight()
self.currentButton = None
self.mainWindow.win.QueueDraw()
#############################
#Functions to Control Calico#
#############################
[docs] def burnEvidence(self):
"""
Method for clearing the output and history.
Useful when someone enters a password and you
want to clear you're tracks.
"""
#calico.clear_output()
calico.Output.Buffer.Text=""
calico.history.clear()
self.outputPrint("The Calico Project, Version 3.1.1\n",2)
[docs] def outputPrint(self,message,tagNo=0):
"""
Method for printing colored text to the output.
Args:
message: String containing the message to be displayed.
tagNo: Integer indicating in which of the predetermined colors the
text will be printed.
"""
printTags=[]
for t in calico.tags:
printTags.append(t)
calico.PrintLine(printTags[tagNo].Key, message)