Beispiel #1
0
    def runCreator(self):
        # Set up Tkinter & pygame
        root = Tkinter.Tk()
        root.withdraw()
        self.tkon = True
        pg.init()
        sc = pg.display.set_mode(self.dims)
        sc.blit(self.draw(), (0, 0))
        pg.display.flip()

        self.curs = RGCursor()

        while True:
            for e in pg.event.get():
                act = None
                if e.type == QUIT: self.quit()

                elif e.type == MOUSEBUTTONDOWN:
                    act = self.doclick(pg.mouse.get_pos())
                elif e.type == KEYDOWN:
                    act = self.dokey()

                if act:
                    if act == 'resize':
                        sc = pg.display.set_mode(self.dims)
                    sc.blit(self.draw(), (0, 0))
                    pg.display.flip()
Beispiel #2
0
 def runCreator(self):
     # Set up Tkinter & pygame 
     root = Tkinter.Tk()
     root.withdraw()
     self.tkon = True
     pg.init()
     sc = pg.display.set_mode(self.dims)
     sc.blit(self.draw(), (0,0))
     pg.display.flip()
     
     self.curs = RGCursor()
     
     while True:
         for e in pg.event.get():
             act = None
             if e.type == QUIT: self.quit()
                 
             elif e.type == MOUSEBUTTONDOWN:
                 act = self.doclick(pg.mouse.get_pos())
             elif e.type == KEYDOWN: 
                 act = self.dokey()
 
             if act:
                 if act == 'resize':
                     sc = pg.display.set_mode(self.dims)
                 sc.blit(self.draw(), (0,0))
                 pg.display.flip()
Beispiel #3
0
class RGCreator(object):
    def __init__(self, tbdims, quitonquit=True):
        self.sizeScreen(tbdims)

        self.name = 'Untitled'
        self.qonq = quitonquit

        # Set up object lists
        self.ball = None
        self.rgoal = None
        self.ggoal = None
        self.walls = dict()
        self.occs = dict()
        self.wct = 0
        self.oct = 0

        self.curaction = None
        self.changed = False  # Tests if change since last save

        self.curs = None

        self.clk = pg.time.Clock()

        self.deffl = None

        self.selectedobj = None

        # To add later
        self.undostack = []
        self.redostack = []

        self.tkon = False

    # Runs the creator from scratch
    def runCreator(self):
        # Set up Tkinter & pygame
        root = Tkinter.Tk()
        root.withdraw()
        self.tkon = True
        pg.init()
        sc = pg.display.set_mode(self.dims)
        sc.blit(self.draw(), (0, 0))
        pg.display.flip()

        self.curs = RGCursor()

        while True:
            for e in pg.event.get():
                act = None
                if e.type == QUIT: self.quit()

                elif e.type == MOUSEBUTTONDOWN:
                    act = self.doclick(pg.mouse.get_pos())
                elif e.type == KEYDOWN:
                    act = self.dokey()

                if act:
                    if act == 'resize':
                        sc = pg.display.set_mode(self.dims)
                    sc.blit(self.draw(), (0, 0))
                    pg.display.flip()

    # Runs the table forward in its given state
    def play(self):
        tr = self.makeTrial(allowInfTime=True)
        if tr is None:
            #tkMessageBox.showerror('Trial error!', 'Trial is not a valid red/green trial. Needs the ball, each goal, and no overlaps between ball, goals, and wall')
            return None

        tb = tr.makeTable()
        self.menu.buttons['play'].setIcon('stop')
        self.menu.disableButtonsButOne('play')

        sc = pg.display.get_surface()
        sf = self.draw()
        sf.blit(tb.draw(), self.tbpos)
        sc.blit(sf, (0, 0))
        pg.display.flip()
        running = True

        while True:
            self.clk.tick(FPS)
            if running:
                if tb.step(1. / FPS): running = False
            for e in pg.event.get():
                if e.type == QUIT: self.quit()
                elif e.type == MOUSEBUTTONDOWN:
                    act = self.menu.checkClick(pg.mouse.get_pos())
                    if act == 'play':
                        self.menu.buttons['play'].setIcon('play')
                        self.menu.enableButtons()
                        if len(self.undostack) == 0:
                            self.menu.buttons['undo'].disable()
                        if len(self.redostack) == 0:
                            self.menu.buttons['redo'].disable()
                        return True
            sf.blit(tb.draw(), self.tbpos)
            sc.blit(sf, (0, 0))
            pg.display.flip()

    # Safe quitting from anywhere
    def quit(self):
        if self.changed:
            dosave = tkMessageBox.askyesno(
                'Save?', 'Would you like to save before quitting?')
            if dosave:
                sv = self.save()
                if sv is None: return False
        pg.quit()
        if self.qonq: sys.exit(0)

    # Figure out what action to do based on keyboard press
    def dokey(self):
        kp = pg.key.get_pressed()
        act = None
        if kp[MODKEYS[0]] or kp[MODKEYS[1]]:
            if kp[K_q]: self.quit()  # Quit
            if kp[K_z]:  # Undo
                if len(self.undostack) > 0:
                    self.implementAction(self.undostack.pop(), isundo=True)
                if len(self.undostack) == 0:
                    self.menu.buttons['undo'].disable()
                return True
            if kp[K_y]:  # Redo
                if len(self.redostack) > 0:
                    self.implementAction(self.redostack.pop(), isredo=True)
                if len(self.redostack) == 0:
                    self.menu.buttons['redo'].disable()
                return True
            if kp[K_s]:
                self.save()
                return True
            if kp[K_o]:
                self.load()
                return 'resize'

        # Delete action
        elif kp[K_BACKSPACE] or kp[K_DELETE]:
            if self.selectedobj == 'ball':
                act = BallAction('delete', self.ball)
            elif self.selectedobj == 'ggoal':
                act = Action('delete', 'ggoal', 'ggoal', self.ggoal)
            elif self.selectedobj == 'rgoal':
                act = Action('delete', 'rgoal', 'rgoal', self.rgoal)
            elif self.selectedobj[0] == 'w':
                act = Action('delete', 'wall', self.selectedobj,
                             self.walls[self.selectedobj])
            elif self.selectedobj[0] == 'o':
                act = Action('delete', 'occ', self.selectedobj,
                             self.occs[self.selectedobj])

        elif kp[K_UP]:
            if self.selectedobj == 'ball':
                act = BallAction('move',
                                 ((self.ball[0][0], self.ball[0][1] - 1),
                                  self.ball[1], self.ball[0], self.ball[1]))
            elif self.selectedobj:
                so = self.getObjRect(self.selectedobj)
                act = Action('move', objtype(self.selectedobj),
                             self.selectedobj,
                             ((so.left, so.top - 1), so.topleft))
        elif kp[K_DOWN]:
            if self.selectedobj == 'ball':
                act = BallAction('move',
                                 ((self.ball[0][0], self.ball[0][1] + 1),
                                  self.ball[1], self.ball[0], self.ball[1]))
            elif self.selectedobj:
                so = self.getObjRect(self.selectedobj)
                act = Action('move', objtype(self.selectedobj),
                             self.selectedobj,
                             ((so.left, so.top + 1), so.topleft))
        elif kp[K_RIGHT]:
            if self.selectedobj == 'ball':
                act = BallAction('move',
                                 ((self.ball[0][0] + 1, self.ball[0][1]),
                                  self.ball[1], self.ball[0], self.ball[1]))
            elif self.selectedobj:
                so = self.getObjRect(self.selectedobj)
                act = Action('move', objtype(self.selectedobj),
                             self.selectedobj,
                             ((so.left + 1, so.top), so.topleft))
        elif kp[K_LEFT]:
            if self.selectedobj == 'ball':
                act = BallAction('move',
                                 ((self.ball[0][0] - 1, self.ball[0][1]),
                                  self.ball[1], self.ball[0], self.ball[1]))
            elif self.selectedobj:
                so = self.getObjRect(self.selectedobj)
                act = Action('move', objtype(self.selectedobj),
                             self.selectedobj,
                             ((so.left - 1, so.top), so.topleft))

        if act:
            self.implementAction(act)
            return True
        else:
            return False

    # Performs actions arising from mouse clicks
    def doclick(self, mpos):
        # In menu territory
        if mpos[1] < 50:
            act = self.menu.checkClick(mpos)
            if act == 'load':
                self.load()
                return 'resize'
            elif act == 'save':
                self.save()
                return True
            elif act == 'saveas':
                self.save(saveas=True)
                return True
            elif act == 'undo':
                self.implementAction(self.undostack.pop(), isundo=True)
                if len(self.undostack) == 0:
                    self.menu.buttons['undo'].disable()
                return True
            elif act == 'redo':
                self.implementAction(self.redostack.pop(), isredo=True)
                if len(self.redostack) == 0:
                    self.menu.buttons['redo'].disable()
                return True
            elif act == 'play':
                self.play()
                return True
            elif act == 'record':
                self.record()
                return True
            elif act in ['cursor', 'ball', 'ggoal', 'rgoal', 'wall', 'occ']:
                self.curaction = act
                self.menu.clearAct(act)
                self.selectedobj = None
                return True

        # Otherwise in table territory
        elif mpos[0] > self.tbpos[0] and mpos[0] < (self.tbpos[0] +
                                                    self.tbdim[0]):
            # Adding rectangular objects
            if self.curaction in ['ggoal', 'rgoal', 'wall', 'occ']:
                nobj = self.findRect(mpos, defcols[self.curaction])
                if self.curaction == 'ggoal':
                    if self.ggoal:
                        act = Action('replace', 'ggoal', 'ggoal',
                                     nobj + self.ggoal)
                    else:
                        act = Action('add', 'ggoal', 'ggoal', nobj)
                elif self.curaction == 'rgoal':
                    if self.rgoal:
                        act = Action('replace', 'rgoal', 'rgoal',
                                     nobj + self.rgoal)
                    else:
                        act = Action('add', 'rgoal', 'rgoal', nobj)
                elif self.curaction == 'wall':
                    nm = 'w' + str(self.wct)
                    self.wct += 1
                    act = Action('add', 'wall', nm, nobj)
                elif self.curaction == 'occ':
                    nm = 'o' + str(self.oct)
                    self.oct += 1
                    act = Action('add', 'occ', nm, nobj)
                self.implementAction(act)
                return True

            # Adding the ball
            elif self.curaction == 'ball':
                vel = self.findBall(mpos, DEFBALLRAD)
                if self.ball:
                    act = BallAction('replace',
                                     (self.mouseontab(mpos), vel, DEFBALLRAD) +
                                     self.ball)
                else:
                    act = BallAction('add',
                                     (self.mouseontab(mpos), vel, DEFBALLRAD))
                self.implementAction(act)
                return True

            # Cursor selects then moves
            elif self.curaction == 'cursor':
                if self.selectedobj:
                    if self.getObjRect(self.selectedobj).collidepoint(
                            self.mouseontab(mpos)):
                        self.docursor(mpos)
                        return True

                if self.ball:
                    if self.getObjRect('ball').collidepoint(
                            self.mouseontab(mpos)):
                        self.selectedobj = 'ball'
                        self.docursor(mpos)
                        return True

                if self.rgoal:
                    if self.getObjRect('rgoal').collidepoint(
                            self.mouseontab(mpos)):
                        self.selectedobj = 'rgoal'
                        self.docursor(mpos)
                        return True

                if self.ggoal:
                    if self.getObjRect('ggoal').collidepoint(
                            self.mouseontab(mpos)):
                        self.selectedobj = 'ggoal'
                        self.docursor(mpos)
                        return True

                for wk in self.walls.keys():
                    if self.getObjRect(wk).collidepoint(self.mouseontab(mpos)):
                        self.selectedobj = wk
                        self.docursor(mpos)
                        return True

                for ok in self.occs.keys():
                    if self.getObjRect(ok).collidepoint(self.mouseontab(mpos)):
                        self.selectedobj = ok
                        self.docursor(mpos)
                        return True

        return None

    # Runs the movement or resizing of the selected object
    def docursor(self, mpos):

        # Make sure the button is held down for more than 200ms before any moving or resizing
        stime = time.time()
        while (time.time() - stime) < .2:
            if not any(pg.mouse.get_pressed()): return False

        orect = self.getObjRect(self.selectedobj)
        shr = orect.inflate(-.2 * orect.width, -.2 * orect.height)
        act = None
        # Replacing ball velocity
        if self.selectedobj == 'ball' and pg.mouse.get_pressed()[2]:
            self.curs.set('none')
            newvel = self.findBall(self.objonscreen(self.ball[0]),
                                   self.ball[2])
            act = BallAction(
                'move', (self.ball[0], newvel, self.ball[0], self.ball[1]))
            self.curs.set('default')

        # Moving objects - placeholder until test for resizing
        elif shr.collidepoint(mpos):
            self.curs.set('move')
            act = self.domove(mpos)
            self.curs.set('default')
        else:
            self.curs.set('resize')
            # Find the closest corner
            corn = getCorner(self.mouseontab(mpos), orect)
            orig = [orect.topleft, orect.bottomright]
            if corn == 'topleft':
                act = self.doresize(orect.bottomright, mpos, orig)
            elif corn == 'topright':
                act = self.doresize(orect.bottomleft, mpos, orig)
            elif corn == 'bottomleft':
                act = self.doresize(orect.topright, mpos, orig)
            elif corn == 'bottomright':
                act = self.doresize(orect.topleft, mpos, orig)
            self.curs.set('default')

        if act:
            self.implementAction(act)
            return True
        else:
            return False

    def doresize(self, ref, mpos, origobj):
        if self.selectedobj == 'ball':
            while True:
                nmpos = self.mouseontab(self.bindmouse(pg.mouse.get_pos()))
                bpos = self.ball[0]

                nrad = int(euclidDist(nmpos, bpos))
                sc = pg.display.get_surface()
                sf = self.draw([self.selectedobj])
                pg.draw.circle(sf, BLUE, self.objonscreen(bpos), nrad)
                sc.blit(sf, (0, 0))
                pg.display.flip()
                for e in pg.event.get():
                    if e.type == QUIT: self.quit()
                    if e.type == MOUSEBUTTONUP:
                        return BallAction('replace', [
                            self.ball[0], self.ball[1], nrad, self.ball[0],
                            self.ball[1], self.ball[2]
                        ])
                self.clk.tick(FPS)
        else:
            col = defcols[objtype(self.selectedobj)]
            while True:
                nmpos = self.mouseontab(self.bindmouse(pg.mouse.get_pos()))

                top = min(ref[1], nmpos[1])
                bottom = max(ref[1], nmpos[1])
                left = min(ref[0], nmpos[0])
                right = max(ref[0], nmpos[0])

                drect = pg.Rect(left + self.tbpos[0], top + self.tbpos[1],
                                right - left, bottom - top)
                sc = pg.display.get_surface()
                sf = self.draw([self.selectedobj])
                pg.draw.rect(sf, col, drect, 3)
                sc.blit(sf, (0, 0))
                pg.display.flip()
                for e in pg.event.get():
                    if e.type == QUIT: self.quit()
                    if e.type == MOUSEBUTTONUP:
                        return Action('replace', objtype(self.selectedobj),
                                      self.selectedobj,
                                      [(left, top), (right, bottom)] + origobj)
                self.clk.tick(FPS)

    def domove(self, mpos):
        if self.selectedobj == 'ball':
            bpos = self.ball[0]
            brad = self.ball[2]
            while True:
                nmpos = pg.mouse.get_pos()
                mpoff = (nmpos[0] - mpos[0], nmpos[1] - mpos[1])
                npos = (min(max(bpos[0] + mpoff[0], brad),
                            self.tbdim[0] - brad),
                        min(max(bpos[1] + mpoff[1], brad),
                            self.tbdim[1] - brad))
                sc = pg.display.get_surface()
                sf = self.draw([self.selectedobj])
                pg.draw.circle(
                    sf, BLUE,
                    (npos[0] + self.tbpos[0], npos[1] + self.tbpos[1]), brad)
                sc.blit(sf, (0, 0))
                pg.display.flip()

                for e in pg.event.get():
                    if e.type == QUIT: self.quit()
                    if e.type == MOUSEBUTTONUP:
                        return BallAction(
                            'move',
                            [npos, self.ball[1], self.ball[0], self.ball[1]])

                self.clk.tick(FPS)

        else:
            sorect = self.getObjRect(self.selectedobj)
            col = defcols[objtype(self.selectedobj)]
            while True:
                nmpos = pg.mouse.get_pos()
                mpoff = (nmpos[0] - mpos[0], nmpos[1] - mpos[1])
                nrect = sorect.move(mpoff)
                nrect.top = max(0, nrect.top)
                nrect.left = max(0, nrect.left)
                nrect.right = min(self.tbdim[0], nrect.right)
                nrect.bottom = min(self.tbdim[1], nrect.bottom)

                sc = pg.display.get_surface()
                sf = self.draw([self.selectedobj])
                pg.draw.rect(sf, col, nrect.move(self.tbpos))
                sc.blit(sf, (0, 0))
                pg.display.flip()

                for e in pg.event.get():
                    if e.type == QUIT: self.quit()
                    if e.type == MOUSEBUTTONUP:
                        return Action('move', objtype(self.selectedobj),
                                      self.selectedobj,
                                      [nrect.topleft, sorect.topleft])

                self.clk.tick(FPS)

    def getObjRect(self, oname):
        if oname == 'ball':
            rad = self.ball[2]
            r = ((self.ball[0][0] - rad, self.ball[0][1] - rad),
                 (self.ball[0][0] + rad, self.ball[0][1] + rad))
        elif oname == 'rgoal':
            r = self.rgoal
        elif oname == 'ggoal':
            r = self.ggoal
        elif oname[0] == 'w':
            r = self.walls[oname]
        elif oname[0] == 'o':
            r = self.occs[oname]
        else:
            print oname

        return pg.Rect(r[0], (r[1][0] - r[0][0], r[1][1] - r[0][1]))

    def findRect(self, origpos, col):
        # Goes until mouse up
        pg.mouse.set_visible(False)
        while True:
            mpos = self.bindmouse(pg.mouse.get_pos())
            l = min(mpos[0], origpos[0])
            r = max(mpos[0], origpos[0])
            u = min(mpos[1], origpos[1])
            b = max(mpos[1], origpos[1])
            sc = self.draw()
            pg.draw.rect(sc, col, pg.Rect((l, u), (r - l, b - u)), 3)
            pg.display.get_surface().blit(sc, (0, 0))
            pg.display.flip()
            for e in pg.event.get():
                if e.type == QUIT: self.quit()

                if e.type == MOUSEBUTTONUP:
                    pg.mouse.set_visible(True)
                    return (self.mouseontab((l, u)), self.mouseontab((r, b)))
            self.clk.tick(20)

    # Binds the mouse to within the physics table area
    def bindmouse(self, mpos):
        x = min(max(mpos[0], self.tbpos[0]), self.tbpos[0] + self.tbdim[0])
        y = min(max(mpos[1], self.tbpos[1]), self.tbpos[1] + self.tbdim[1])
        return (x, y)

    # Finds the velocity of the ball
    def findBall(self, origpos, rad):
        pg.mouse.set_visible(False)
        while True:
            mpos = self.bindmouse(pg.mouse.get_pos())
            sc = self.draw(['ball'])
            pg.draw.circle(sc, BLUE, origpos, rad, 3)
            pg.draw.line(sc, BLUE, origpos, mpos, 3)
            pg.display.get_surface().blit(sc, (0, 0))
            pg.display.flip()
            for e in pg.event.get():
                if e.type == QUIT: self.quit()
                if e.type == MOUSEBUTTONUP:
                    pg.mouse.set_visible(True)
                    return (mpos[0] - origpos[0], mpos[1] - origpos[1])
            self.clk.tick(20)

    # Takes an action object and makes it happen
    def implementAction(self, action, isundo=False, isredo=False):
        if action.acttype == 'add':
            if action.objtype == 'ball':
                self.ball = action.specifics
            elif action.objtype == 'rgoal':
                self.rgoal = action.specifics
            elif action.objtype == 'ggoal':
                self.ggoal = action.specifics
            elif action.objtype == 'wall':
                self.walls[action.objname] = action.specifics
            elif action.objtype == 'occ':
                self.occs[action.objname] = action.specifics
        elif action.acttype == 'delete':
            self.selectedobj = None
            if action.objtype == 'ball': self.ball = None
            elif action.objtype == 'rgoal': self.rgoal = None
            elif action.objtype == 'ggoal': self.ggoal = None
            elif action.objtype == 'wall': del self.walls[action.objname]
            elif action.objtype == 'occ': del self.occs[action.objname]
        elif action.acttype == 'replace':
            if action.objtype == 'ball':
                self.ball = action.specifics[:3]
            elif action.objtype == 'rgoal':
                self.rgoal = action.specifics[:2]
            elif action.objtype == 'ggoal':
                self.ggoal = action.specifics[:2]
            elif action.objtype == 'wall':
                self.walls[action.objname] = action.specifics[:2]
            elif action.objtype == 'occ':
                self.occs[action.objname] = action.specifics[:2]
        elif action.acttype == 'move':
            if action.objtype == 'ball':
                self.ball = (action.specifics[0], action.specifics[1],
                             self.ball[2])
            elif action.objtype == 'rgoal':
                self.rgoal = move(self.rgoal, action.specifics[0])
            elif action.objtype == 'ggoal':
                self.ggoal = move(self.ggoal, action.specifics[0])
            elif action.objtype == 'wall':
                w = self.walls[action.objname]
                self.walls[action.objname] = move(w, action.specifics[0])
            elif action.objtype == 'occ':
                o = self.occs[action.objname]
                self.occs[action.objname] = move(o, action.specifics[0])
        elif action.acttype == 'resize':
            if action.objtype == 'ball':
                self.ball = (self.ball[0], self.ball[1], action.specifics[0])
            elif action.objtype == 'rgoal':
                self.rgoal = resize(self.rgoal, action.specifics[0])
            elif action.objtype == 'ggoal':
                self.ggoal = resize(self.ggoal, action.specifics[0])
            elif action.objtype == 'wall':
                w = self.walls[action.objname]
                self.walls[action.objname] = resize(w, action.specifics[0])
            elif action.objtype == 'occ':
                o = self.occs[action.objname]
                self.occs[action.objname] = resize(o, action.specifics[0])
        if isundo:
            self.redostack.append(action.reverse())
            if len(self.redostack) > 100:
                self.redostack = self.redostack[1:]
            self.menu.buttons['redo'].enable()
        else:
            self.undostack.append(action.reverse())
            if len(self.undostack) > 100:
                self.undostack = self.undostack[1:]
            self.menu.buttons['undo'].enable()
            if not isredo:
                self.redostack = []
                self.menu.buttons['redo'].disable()

        self.changed = True

    # Sets the dimensions of the screen based on object size
    def sizeScreen(self, tbdims):
        self.tbdim = tbdims
        self.dims = (max(self.tbdim[0] + 4, 600), self.tbdim[1] + 52)
        self.menu = RGMenu(self.dims[0])
        self.tbpos = (int((self.dims[0] - self.tbdim[0]) / 2), 50)

    # Offset mouse clicks or objects
    def mouseontab(self, mpos):
        return (mpos[0] - self.tbpos[0], mpos[1] - self.tbpos[1])

    def objonscreen(self, pos):
        return (pos[0] + self.tbpos[0], pos[1] + self.tbpos[1])

    # Helper function for drawing rectangular items in native form
    def drawRectThing(self, sc, obj, color):
        ul = obj[0]
        lr = obj[1]
        w = lr[0] - ul[0]
        h = lr[1] - ul[1]
        pg.draw.rect(sc, color, pg.Rect(ul, (w, h)))

    def draw(self, excludeobjs=[]):
        surf = pg.Surface(self.dims)
        surf.blit(self.menu.draw(), (0, 0))

        # Draw the table
        tabsurf = pg.Surface(self.tbdim)
        tabsurf.fill(WHITE)

        if self.ball and 'ball' not in excludeobjs:
            pg.draw.circle(tabsurf, BLUE, self.ball[0], self.ball[2])
        for k in self.occs.keys():
            if k not in excludeobjs:
                self.drawRectThing(tabsurf, self.occs[k], GREY)
        for k in self.walls.keys():
            if k not in excludeobjs:
                self.drawRectThing(tabsurf, self.walls[k], BLACK)
        if self.rgoal and 'rgoal' not in excludeobjs:
            self.drawRectThing(tabsurf, self.rgoal, RED)
        if self.ggoal and 'ggoal' not in excludeobjs:
            self.drawRectThing(tabsurf, self.ggoal, GREEN)

        if self.selectedobj and self.selectedobj not in excludeobjs:
            rct = self.getObjRect(self.selectedobj)
            pg.draw.rect(tabsurf, PURPLE, rct.inflate(4, 4), 2)

        surf.blit(tabsurf, self.tbpos)
        return surf

    def record(self, movfl=None):

        tr = self.makeTrial()
        if tr is None: return None

        if movfl is None:
            movfl = tkFileDialog.asksaveasfilename(defaultextension='.mov',
                                                   initialfile=self.name +
                                                   '.mov')
            if movfl == '': return None

        movpath = os.path.dirname(movfl)
        movnm = os.path.basename(movfl)

        tb = tr.makeTable()
        with warnings.catch_warnings(record=True) as w:
            warnings.simplefilter('always')
            isgood = tb.makeMovie(movnm, movpath)
            if not isgood:
                tkMessageBox.showerror('Cannot make movie!', str(w[0].message))
                return None
            else:
                tkMessageBox.showinfo('Done!', 'Your movie has been created')
                return True

    def load(self, trpath=None):
        if trpath is None:
            if self.tkon:
                trpath = tkFileDialog.askopenfilename(
                    filetypes=[('PhysicsTrials', '*.ptr')])
                if trpath == '': return False
            else: return False

        try:
            tr = loadTrial(trpath)
        except:
            if self.tkon:
                tkMessageBox.showerror('File not found!', 'File not found!')
            else:
                warnings.warn("File not found!")
            return False
        if tr.__class__.__name__ != "RedGreenTrial":
            if self.tkon:
                tkMessageBox.showerror('Incorrect file type',
                                       'File is not a RedGreenTrial type')
            else:
                warnings.warn("File is not a RedGreenTrial type")
            return False

        for g in tr.goals:
            if g[2] not in [REDGOAL, GREENGOAL]:
                if self.tkon:
                    tkMessageBox.showerror(
                        'Goal error', 'Goal that is not red or green found')
                else:
                    warnings.warn("Goal that is not red or green found")
                return False

        self.sizeScreen(tr.dims)
        self.name = tr.name
        self.ball = (tr.ball[0], tr.ball[1], tr.ball[2])
        self.walls = dict()
        self.occs = dict()
        for g in tr.goals:
            if g[2] == REDGOAL: self.rgoal = g[0:2]
            elif g[2] == GREENGOAL: self.ggoal = g[0:2]
        self.wct = 0
        for w in tr.normwalls:
            self.walls['w' + str(self.wct)] = w[0:2]
            self.wct += 1
        self.oct = 0
        for o in tr.occs:
            self.occs['o' + str(self.oct)] = o[0:2]
            self.oct += 1
        if len(tr.abnormwalls) > 0:
            if self.tkon:
                tkMessageBox.showwarning(
                    'Feature not implemented yet!',
                    "Abnormal walls (polygons) not supported yet and will not be loaded"
                )
            else:
                warnings.warn(
                    "Abnormal walls (polygons) not supported yet and will not be loaded"
                )
        self.changed = False
        self.deffl = trpath

        return True

    def makeTrial(self, bvel=300, allowInfTime=False):
        if self.ball is None or self.rgoal is None or self.ggoal is None:
            tkMessageBox.showerror(
                'Missing object',
                'Cannot do this until you have a ball, red goal, and green goal'
            )
            return None
        tr = RedGreenTrial(self.name, self.tbdim, def_ball_vel=bvel)
        tr.addBall(self.ball[0], self.ball[1], self.ball[2])
        tr.addGoal(self.rgoal[0], self.rgoal[1], REDGOAL, RED)
        tr.addGoal(self.ggoal[0], self.ggoal[1], GREENGOAL, GREEN)
        for w in self.walls.values():
            tr.addWall(w[0], w[1])
        for o in self.occs.values():
            tr.addOcc(o[0], o[1])
        tr.normalizeVel()

        with warnings.catch_warnings(record=True) as w:
            warnings.simplefilter('always')
            consist = tr.checkConsistency(nochecktime=allowInfTime)
            if len(w) > 1:
                msg = "Multiple trial errors:"
                for wm in w:
                    msg += '\n' + str(wm.message)
                tkMessageBox.showerror('Trial consistency error!', msg)
            elif len(w) == 1:
                tkMessageBox.showerror('Trial consistency error!',
                                       str(w[0].message))
        if not consist: return None
        return tr

    def save(self, flnm=None, saveas=False):

        tr = self.makeTrial()
        if tr is None:
            #tkMessageBox.showerror('Trial error!', 'Trial is not a valid red/green trial. See console for details on what must be changed.')
            return None

        if flnm is None and (self.deffl is None or saveas):
            if self.deffl is None: nm = self.name + '.ptr'
            else: nm = os.path.basename(self.deffl)
            flnm = tkFileDialog.asksaveasfilename(defaultextension='.ptr',
                                                  initialfile=nm)
            if flnm == '': return False
        else: flnm = self.deffl

        tr.save(flnm, askoverwrite=False)
        self.deffl = flnm
        self.name = os.path.splitext(os.path.basename(flnm))[0]
        self.changed = False
        return True
Beispiel #4
0
class RGCreator(object):
    
    def __init__(self, tbdims, quitonquit = True):
        self.sizeScreen(tbdims)
        
        self.name = 'Untitled'
        self.qonq = quitonquit
        
        # Set up object lists
        self.ball = None
        self.rgoal = None
        self.ggoal = None
        self.walls = dict()
        self.occs = dict()
        self.wct = 0
        self.oct = 0
        
        self.curaction = None
        self.changed = False # Tests if change since last save
        
        self.curs = None
        
        self.clk = pg.time.Clock()
        
        self.deffl = None
        
        self.selectedobj = None
        
        # To add later
        self.undostack = []
        self.redostack = []
        
        self.tkon = False
    
    # Runs the creator from scratch
    def runCreator(self):
        # Set up Tkinter & pygame 
        root = Tkinter.Tk()
        root.withdraw()
        self.tkon = True
        pg.init()
        sc = pg.display.set_mode(self.dims)
        sc.blit(self.draw(), (0,0))
        pg.display.flip()
        
        self.curs = RGCursor()
        
        while True:
            for e in pg.event.get():
                act = None
                if e.type == QUIT: self.quit()
                    
                elif e.type == MOUSEBUTTONDOWN:
                    act = self.doclick(pg.mouse.get_pos())
                elif e.type == KEYDOWN: 
                    act = self.dokey()
    
                if act:
                    if act == 'resize':
                        sc = pg.display.set_mode(self.dims)
                    sc.blit(self.draw(), (0,0))
                    pg.display.flip()
                
    # Runs the table forward in its given state
    def play(self):
        tr = self.makeTrial(allowInfTime=True)
        if tr is None:
            #tkMessageBox.showerror('Trial error!', 'Trial is not a valid red/green trial. Needs the ball, each goal, and no overlaps between ball, goals, and wall')
            return None
        
        tb = tr.makeTable()
        self.menu.buttons['play'].setIcon('stop')
        self.menu.disableButtonsButOne('play')
        
        sc = pg.display.get_surface()
        sf = self.draw()
        sf.blit(tb.draw(), self.tbpos)
        sc.blit(sf, (0,0))
        pg.display.flip()
        running = True
        
        while True:
            self.clk.tick(FPS)
            if running:
                if tb.step( 1. / FPS ): running = False
            for e in pg.event.get():
                if e.type == QUIT: self.quit()
                elif e.type == MOUSEBUTTONDOWN:
                    act = self.menu.checkClick(pg.mouse.get_pos())
                    if act == 'play':
                        self.menu.buttons['play'].setIcon('play')
                        self.menu.enableButtons()
                        if len(self.undostack) == 0: self.menu.buttons['undo'].disable()
                        if len(self.redostack) == 0: self.menu.buttons['redo'].disable()
                        return True
            sf.blit(tb.draw(), self.tbpos)
            sc.blit(sf, (0,0))
            pg.display.flip()
            
    
    
    # Safe quitting from anywhere
    def quit(self):
        if self.changed:
            dosave = tkMessageBox.askyesno('Save?', 'Would you like to save before quitting?')
            if dosave:
                sv = self.save()
                if sv is None: return False
        pg.quit()
        if self.qonq: sys.exit(0)
    
    # Figure out what action to do based on keyboard press
    def dokey(self):
        kp = pg.key.get_pressed()
        act = None
        if kp[MODKEYS[0]] or kp[MODKEYS[1]]:
            if kp[K_q]: self.quit() # Quit
            if kp[K_z]: # Undo
                if len(self.undostack) > 0: self.implementAction(self.undostack.pop(),isundo=True)
                if len(self.undostack) == 0: self.menu.buttons['undo'].disable()
                return True
            if kp[K_y]: # Redo
                if len (self.redostack) > 0: self.implementAction(self.redostack.pop(),isredo = True)
                if len(self.redostack) == 0: self.menu.buttons['redo'].disable()
                return True
            if kp[K_s]: self.save(); return True
            if kp[K_o]: self.load(); return 'resize'
        
        # Delete action
        elif kp[K_BACKSPACE] or kp[K_DELETE]:
            if self.selectedobj == 'ball': act = BallAction('delete', self.ball)
            elif self.selectedobj == 'ggoal': act = Action('delete','ggoal','ggoal',self.ggoal)
            elif self.selectedobj == 'rgoal': act = Action('delete','rgoal','rgoal',self.rgoal)
            elif self.selectedobj[0] == 'w': act = Action('delete','wall',self.selectedobj,self.walls[self.selectedobj])
            elif self.selectedobj[0] == 'o': act = Action('delete','occ',self.selectedobj,self.occs[self.selectedobj])
                
        elif kp[K_UP]:
            if self.selectedobj == 'ball': act = BallAction('move', ((self.ball[0][0],self.ball[0][1]-1), self.ball[1], self.ball[0], self.ball[1]))
            elif self.selectedobj:
                so = self.getObjRect(self.selectedobj)
                act = Action('move',objtype(self.selectedobj),self.selectedobj, ((so.left, so.top-1), so.topleft))
        elif kp[K_DOWN]:
            if self.selectedobj == 'ball': act = BallAction('move', ((self.ball[0][0],self.ball[0][1]+1), self.ball[1], self.ball[0], self.ball[1]))
            elif self.selectedobj:
                so = self.getObjRect(self.selectedobj)
                act = Action('move',objtype(self.selectedobj),self.selectedobj, ((so.left, so.top+1), so.topleft))
        elif kp[K_RIGHT]:
            if self.selectedobj == 'ball': act = BallAction('move', ((self.ball[0][0]+1,self.ball[0][1]), self.ball[1], self.ball[0], self.ball[1]))
            elif self.selectedobj:
                so = self.getObjRect(self.selectedobj)
                act = Action('move',objtype(self.selectedobj),self.selectedobj, ((so.left+1, so.top), so.topleft))
        elif kp[K_LEFT]:
            if self.selectedobj == 'ball': act = BallAction('move', ((self.ball[0][0]-1,self.ball[0][1]), self.ball[1], self.ball[0], self.ball[1]))
            elif self.selectedobj:
                so = self.getObjRect(self.selectedobj)
                act = Action('move',objtype(self.selectedobj),self.selectedobj, ((so.left-1, so.top), so.topleft))
        
        if act:
            self.implementAction(act)
            return True
        else: return False
    
    # Performs actions arising from mouse clicks
    def doclick(self, mpos):
        # In menu territory
        if mpos[1] < 50:
            act = self.menu.checkClick(mpos)
            if act == 'load': self.load(); return 'resize'
            elif act == 'save': self.save(); return True
            elif act == 'saveas': self.save(saveas = True); return True
            elif act == 'undo':
                self.implementAction(self.undostack.pop(),isundo=True)
                if len(self.undostack) == 0: self.menu.buttons['undo'].disable()
                return True
            elif act == 'redo':
                self.implementAction(self.redostack.pop(),isredo = True)
                if len(self.redostack) == 0: self.menu.buttons['redo'].disable()
                return True
            elif act == 'play':
                self.play()
                return True
            elif act == 'record':
                self.record()
                return True
            elif act in ['cursor','ball','ggoal','rgoal','wall','occ']:
                self.curaction = act
                self.menu.clearAct(act)
                self.selectedobj = None
                return True
            
        # Otherwise in table territory
        elif mpos[0] > self.tbpos[0] and mpos[0] < (self.tbpos[0] + self.tbdim[0]):
            # Adding rectangular objects
            if self.curaction in ['ggoal','rgoal','wall','occ']:
                nobj = self.findRect(mpos, defcols[self.curaction])
                if self.curaction == 'ggoal':
                    if self.ggoal: act = Action('replace','ggoal','ggoal',nobj + self.ggoal)
                    else: act = Action('add','ggoal','ggoal',nobj)
                elif self.curaction == 'rgoal':
                    if self.rgoal: act = Action('replace','rgoal','rgoal',nobj + self.rgoal)
                    else: act = Action('add','rgoal','rgoal',nobj)
                elif self.curaction == 'wall':
                    nm = 'w'+str(self.wct)
                    self.wct += 1
                    act = Action('add','wall',nm,nobj)
                elif self.curaction == 'occ':
                    nm = 'o' + str(self.oct)
                    self.oct += 1
                    act = Action('add','occ',nm, nobj)
                self.implementAction(act)
                return True
            
            # Adding the ball
            elif self.curaction == 'ball':
                vel = self.findBall(mpos, DEFBALLRAD)
                if self.ball: act = BallAction('replace', (self.mouseontab(mpos),vel,DEFBALLRAD) + self.ball)
                else: act = BallAction('add', (self.mouseontab(mpos), vel, DEFBALLRAD) )
                self.implementAction(act)
                return True
            
            # Cursor selects then moves
            elif self.curaction == 'cursor':
                if self.selectedobj: 
                    if self.getObjRect(self.selectedobj).collidepoint(self.mouseontab(mpos)):
                        self.docursor(mpos)
                        return True
                
                if self.ball:
                    if self.getObjRect('ball').collidepoint(self.mouseontab(mpos)):
                        self.selectedobj = 'ball'
                        self.docursor(mpos)
                        return True
                
                if self.rgoal:
                    if self.getObjRect('rgoal').collidepoint(self.mouseontab(mpos)):
                        self.selectedobj = 'rgoal'
                        self.docursor(mpos)
                        return True
                
                if self.ggoal:
                    if self.getObjRect('ggoal').collidepoint(self.mouseontab(mpos)):
                        self.selectedobj = 'ggoal'
                        self.docursor(mpos)
                        return True
                    
                for wk in self.walls.keys():
                    if self.getObjRect(wk).collidepoint(self.mouseontab(mpos)):
                        self.selectedobj = wk
                        self.docursor(mpos)
                        return True
                    
                for ok in self.occs.keys():
                    if self.getObjRect(ok).collidepoint(self.mouseontab(mpos)):
                        self.selectedobj = ok
                        self.docursor(mpos)
                        return True
                
        return None
    
    # Runs the movement or resizing of the selected object
    def docursor(self, mpos):
        
        # Make sure the button is held down for more than 200ms before any moving or resizing
        stime = time.time()
        while (time.time() - stime) < .2:
            if not any(pg.mouse.get_pressed()): return False
        
        orect = self.getObjRect(self.selectedobj)
        shr = orect.inflate(-.2*orect.width, -.2*orect.height)
        act = None
        # Replacing ball velocity
        if self.selectedobj == 'ball' and pg.mouse.get_pressed()[2]:
            self.curs.set('none')
            newvel = self.findBall(self.objonscreen(self.ball[0]),self.ball[2])
            act = BallAction('move',(self.ball[0],newvel,self.ball[0],self.ball[1]))
            self.curs.set('default')
        
        # Moving objects - placeholder until test for resizing
        elif shr.collidepoint(mpos):
            self.curs.set('move')
            act = self.domove(mpos)
            self.curs.set('default')
        else:
            self.curs.set('resize')
            # Find the closest corner
            corn = getCorner(self.mouseontab(mpos), orect)
            orig = [orect.topleft,orect.bottomright]
            if corn == 'topleft': act = self.doresize(orect.bottomright,mpos,orig)
            elif corn == 'topright': act = self.doresize(orect.bottomleft,mpos,orig)
            elif corn == 'bottomleft': act = self.doresize(orect.topright,mpos,orig)
            elif corn == 'bottomright': act = self.doresize(orect.topleft,mpos,orig)
            self.curs.set('default')
            
        if act: self.implementAction(act); return True
        else: return False
    
    def doresize(self, ref, mpos,origobj):
        if self.selectedobj == 'ball':
            while True:
                nmpos = self.mouseontab(self.bindmouse(pg.mouse.get_pos()))
                bpos = self.ball[0]
                
                nrad = int(euclidDist(nmpos,bpos))
                sc = pg.display.get_surface()
                sf = self.draw([self.selectedobj])
                pg.draw.circle(sf,BLUE,self.objonscreen(bpos),nrad)
                sc.blit(sf,(0,0))
                pg.display.flip()
                for e in pg.event.get():
                    if e.type == QUIT: self.quit()
                    if e.type == MOUSEBUTTONUP:
                        return BallAction('replace',[self.ball[0],self.ball[1],nrad,self.ball[0],self.ball[1],self.ball[2]]) 
                self.clk.tick(FPS)
        else:
            col = defcols[objtype(self.selectedobj)]
            while True:
                nmpos = self.mouseontab(self.bindmouse(pg.mouse.get_pos()))
                
                top = min(ref[1],nmpos[1])
                bottom = max(ref[1],nmpos[1])
                left = min(ref[0],nmpos[0])
                right = max(ref[0],nmpos[0])
                
                drect = pg.Rect(left + self.tbpos[0],top + self.tbpos[1],right-left,bottom-top)
                sc = pg.display.get_surface()
                sf = self.draw([self.selectedobj])
                pg.draw.rect(sf,col,drect,3)
                sc.blit(sf,(0,0))
                pg.display.flip()
                for e in pg.event.get():
                    if e.type == QUIT: self.quit()
                    if e.type == MOUSEBUTTONUP:
                        return Action('replace',objtype(self.selectedobj),self.selectedobj,[(left,top),(right,bottom)]+origobj) 
                self.clk.tick(FPS)
        
    def domove(self, mpos):
        if self.selectedobj == 'ball':
            bpos = self.ball[0]
            brad = self.ball[2]
            while True:
                nmpos = pg.mouse.get_pos()
                mpoff = (nmpos[0] - mpos[0], nmpos[1] - mpos[1])
                npos = (min(max(bpos[0]+mpoff[0],brad),self.tbdim[0]-brad),
                        min(max(bpos[1]+mpoff[1],brad),self.tbdim[1]-brad))
                sc = pg.display.get_surface()
                sf = self.draw([self.selectedobj])
                pg.draw.circle(sf,BLUE,(npos[0] + self.tbpos[0], npos[1]+self.tbpos[1]),brad)
                sc.blit(sf,(0,0))
                pg.display.flip()
                
                for e in pg.event.get():
                    if e.type == QUIT: self.quit()
                    if e.type == MOUSEBUTTONUP:
                        return BallAction('move',[npos, self.ball[1],self.ball[0],self.ball[1]])
                
                self.clk.tick(FPS)
                
                
        else:
            sorect = self.getObjRect(self.selectedobj)
            col = defcols[objtype(self.selectedobj)]
            while True:
                nmpos = pg.mouse.get_pos()
                mpoff = (nmpos[0] - mpos[0], nmpos[1] - mpos[1])
                nrect = sorect.move(mpoff)
                nrect.top = max(0,nrect.top)
                nrect.left = max(0,nrect.left)
                nrect.right = min(self.tbdim[0],nrect.right)
                nrect.bottom = min(self.tbdim[1],nrect.bottom)
                
                sc = pg.display.get_surface()
                sf = self.draw([self.selectedobj])
                pg.draw.rect(sf,col, nrect.move(self.tbpos))
                sc.blit(sf,(0,0))
                pg.display.flip()
                
                for e in pg.event.get():
                    if e.type == QUIT: self.quit()
                    if e.type == MOUSEBUTTONUP:
                        return Action('move',objtype(self.selectedobj),self.selectedobj,[nrect.topleft, sorect.topleft])
                
                self.clk.tick(FPS)
            
                
    
    def getObjRect(self, oname):
        if oname == 'ball':
            rad = self.ball[2]
            r = ( (self.ball[0][0] - rad, self.ball[0][1] - rad), (self.ball[0][0] + rad, self.ball[0][1] + rad) )
        elif oname == 'rgoal': r = self.rgoal
        elif oname == 'ggoal': r = self.ggoal
        elif oname[0] == 'w': r = self.walls[oname]
        elif oname[0] == 'o': r = self.occs[oname]
        else: print oname
        
        return pg.Rect(r[0], (r[1][0] - r[0][0], r[1][1] - r[0][1]) )
    
    
    def findRect(self, origpos, col):
        # Goes until mouse up
        pg.mouse.set_visible(False)
        while True:
            mpos = self.bindmouse(pg.mouse.get_pos())
            l = min(mpos[0], origpos[0])
            r = max(mpos[0], origpos[0])
            u = min(mpos[1], origpos[1])
            b = max(mpos[1], origpos[1])
            sc = self.draw()
            pg.draw.rect(sc, col, pg.Rect((l,u), (r - l, b - u)), 3)
            pg.display.get_surface().blit(sc,(0,0))
            pg.display.flip()
            for e in pg.event.get():
                if e.type == QUIT: self.quit()
                    
                if e.type == MOUSEBUTTONUP:
                    pg.mouse.set_visible(True)
                    return ( self.mouseontab( (l,u) ), self.mouseontab( (r,b) ) )
            self.clk.tick(20)
    # Binds the mouse to within the physics table area
    def bindmouse(self, mpos):
        x = min(max(mpos[0], self.tbpos[0]), self.tbpos[0] + self.tbdim[0])
        y = min(max(mpos[1], self.tbpos[1]), self.tbpos[1] + self.tbdim[1])
        return (x,y)
    
    # Finds the velocity of the ball
    def findBall(self, origpos, rad):
        pg.mouse.set_visible(False)
        while True:
            mpos = self.bindmouse(pg.mouse.get_pos())
            sc = self.draw(['ball'])
            pg.draw.circle(sc, BLUE, origpos, rad, 3)
            pg.draw.line(sc, BLUE, origpos, mpos, 3)
            pg.display.get_surface().blit(sc,(0,0))
            pg.display.flip()
            for e in pg.event.get():
                if e.type == QUIT: self.quit()
                if e.type == MOUSEBUTTONUP:
                    pg.mouse.set_visible(True)
                    return ( mpos[0] - origpos[0], mpos[1] - origpos[1] )
            self.clk.tick(20)
    
    # Takes an action object and makes it happen
    def implementAction(self, action, isundo = False, isredo = False):        
        if action.acttype == 'add':
            if action.objtype == 'ball':
                self.ball = action.specifics
            elif action.objtype == 'rgoal':
                self.rgoal = action.specifics
            elif action.objtype == 'ggoal':
                self.ggoal = action.specifics
            elif action.objtype == 'wall':
                self.walls[action.objname] = action.specifics
            elif action.objtype == 'occ':
                self.occs[action.objname] = action.specifics
        elif action.acttype == 'delete':
            self.selectedobj = None
            if action.objtype == 'ball': self.ball = None
            elif action.objtype == 'rgoal': self.rgoal = None
            elif action.objtype == 'ggoal': self.ggoal = None
            elif action.objtype == 'wall': del self.walls[action.objname]
            elif action.objtype == 'occ': del self.occs[action.objname]
        elif action.acttype == 'replace':
            if action.objtype == 'ball':
                self.ball = action.specifics[:3]
            elif action.objtype == 'rgoal':
                self.rgoal = action.specifics[:2]
            elif action.objtype == 'ggoal':
                self.ggoal = action.specifics[:2]
            elif action.objtype == 'wall':
                self.walls[action.objname] = action.specifics[:2]
            elif action.objtype == 'occ':
                self.occs[action.objname] = action.specifics[:2]
        elif action.acttype == 'move':
            if action.objtype == 'ball': self.ball = (action.specifics[0], action.specifics[1], self.ball[2])
            elif action.objtype == 'rgoal': self.rgoal = move(self.rgoal, action.specifics[0])
            elif action.objtype == 'ggoal': self.ggoal = move(self.ggoal, action.specifics[0])
            elif action.objtype == 'wall':
                w = self.walls[action.objname]
                self.walls[action.objname] = move(w,action.specifics[0])
            elif action.objtype == 'occ':
                o = self.occs[action.objname]
                self.occs[action.objname] = move(o, action.specifics[0])
        elif action.acttype == 'resize':
            if action.objtype == 'ball': self.ball = (self.ball[0], self.ball[1], action.specifics[0])
            elif action.objtype == 'rgoal': self.rgoal = resize(self.rgoal, action.specifics[0])
            elif action.objtype == 'ggoal': self.ggoal = resize(self.ggoal, action.specifics[0])
            elif action.objtype == 'wall':
                w = self.walls[action.objname]
                self.walls[action.objname] = resize(w, action.specifics[0])
            elif action.objtype == 'occ':
                o = self.occs[action.objname]
                self.occs[action.objname] = resize(o, action.specifics[0])
        if isundo:
            self.redostack.append(action.reverse())
            if len(self.redostack) > 100:
                self.redostack = self.redostack[1:]
            self.menu.buttons['redo'].enable()
        else:
            self.undostack.append(action.reverse())
            if len(self.undostack) > 100:
                self.undostack = self.undostack[1:]
            self.menu.buttons['undo'].enable()
            if not isredo: self.redostack = []; self.menu.buttons['redo'].disable()
            
        
        self.changed = True
    
    # Sets the dimensions of the screen based on object size
    def sizeScreen(self, tbdims):
        self.tbdim = tbdims
        self.dims = (max(self.tbdim[0] + 4, 600), self.tbdim[1] + 52)
        self.menu = RGMenu(self.dims[0])
        self.tbpos = ( int((self.dims[0] - self.tbdim[0])/2) , 50)
    
    # Offset mouse clicks or objects
    def mouseontab(self, mpos): return (mpos[0] - self.tbpos[0], mpos[1] - self.tbpos[1])
    def objonscreen(self, pos): return(pos[0] + self.tbpos[0], pos[1] + self.tbpos[1])
    
    # Helper function for drawing rectangular items in native form
    def drawRectThing(self, sc, obj, color):
        ul = obj[0]
        lr = obj[1]
        w = lr[0] - ul[0]
        h = lr[1] - ul[1]
        pg.draw.rect(sc, color, pg.Rect(ul, (w,h) ))
    
    def draw(self, excludeobjs = []):
        surf = pg.Surface(self.dims)
        surf.blit(self.menu.draw(), (0,0))
        
        # Draw the table
        tabsurf = pg.Surface(self.tbdim)
        tabsurf.fill(WHITE)
        
        if self.ball and 'ball' not in excludeobjs: pg.draw.circle(tabsurf, BLUE, self.ball[0], self.ball[2])
        for k in self.occs.keys():
            if k not in excludeobjs: self.drawRectThing(tabsurf, self.occs[k], GREY)
        for k in self.walls.keys(): 
            if k not in excludeobjs: self.drawRectThing(tabsurf, self.walls[k], BLACK)
        if self.rgoal and 'rgoal' not in excludeobjs: self.drawRectThing(tabsurf, self.rgoal, RED)
        if self.ggoal and 'ggoal' not in excludeobjs: self.drawRectThing(tabsurf, self.ggoal, GREEN)
        
        if self.selectedobj and self.selectedobj not in excludeobjs:
            rct = self.getObjRect(self.selectedobj)
            pg.draw.rect(tabsurf, PURPLE, rct.inflate(4,4),2)
        
        surf.blit(tabsurf, self.tbpos)
        return surf
    
    def record(self, movfl = None):
        
        tr = self.makeTrial()
        if tr is None: return None
        
        if movfl is None:
            movfl = tkFileDialog.asksaveasfilename(defaultextension='.mov', initialfile = self.name+'.mov')
            if movfl == '': return None
        
        movpath = os.path.dirname(movfl)
        movnm = os.path.basename(movfl)
        
        tb = tr.makeTable()
        with warnings.catch_warnings(record = True) as w:
            warnings.simplefilter('always')
            isgood = tb.makeMovie(movnm, movpath)
            if not isgood:
                tkMessageBox.showerror('Cannot make movie!', str(w[0].message))
                return None
            else:
                tkMessageBox.showinfo('Done!','Your movie has been created')
                return True
    
    def load(self, trpath = None):
        if trpath is None:
            if self.tkon:
                trpath = tkFileDialog.askopenfilename(filetypes = [('PhysicsTrials','*.ptr')])
                if trpath == '': return False
            else: return False
        
        try:
            tr = loadTrial(trpath)
        except:
            if self.tkon: tkMessageBox.showerror('File not found!','File not found!')
            else: warnings.warn( "File not found!")
            return False
        if tr.__class__.__name__ != "RedGreenTrial":
            if self.tkon: tkMessageBox.showerror('Incorrect file type','File is not a RedGreenTrial type')
            else: warnings.warn( "File is not a RedGreenTrial type")
            return False
        
        for g in tr.goals:
            if g[2] not in [REDGOAL, GREENGOAL]:
                if self.tkon: tkMessageBox.showerror('Goal error','Goal that is not red or green found')
                else: warnings.warn( "Goal that is not red or green found")
                return False
        
        self.sizeScreen(tr.dims)
        self.name = tr.name
        self.ball = (tr.ball[0], tr.ball[1], tr.ball[2])
        self.walls = dict()
        self.occs = dict()
        for g in tr.goals:
            if g[2] == REDGOAL: self.rgoal = g[0:2]
            elif g[2] == GREENGOAL: self.ggoal = g[0:2]
        self.wct = 0
        for w in tr.normwalls:
            self.walls['w'+str(self.wct)] = w[0:2]
            self.wct += 1
        self.oct = 0
        for o in tr.occs:
            self.occs['o'+str(self.oct)] = o[0:2]
            self.oct += 1
        if len(tr.abnormwalls) > 0:
            if self.tkon: tkMessageBox.showwarning('Feature not implemented yet!', "Abnormal walls (polygons) not supported yet and will not be loaded" )
            else: warnings.warn( "Abnormal walls (polygons) not supported yet and will not be loaded" )
        self.changed = False
        self.deffl = trpath
        
        return True
        
    def makeTrial(self, bvel = 300, allowInfTime = False):
        if self.ball is None or self.rgoal is None or self.ggoal is None:
            tkMessageBox.showerror('Missing object','Cannot do this until you have a ball, red goal, and green goal')
            return None
        tr = RedGreenTrial(self.name, self.tbdim, def_ball_vel=bvel)
        tr.addBall(self.ball[0],self.ball[1],self.ball[2])
        tr.addGoal(self.rgoal[0],self.rgoal[1],REDGOAL,RED)
        tr.addGoal(self.ggoal[0],self.ggoal[1],GREENGOAL,GREEN)
        for w in self.walls.values():
            tr.addWall(w[0],w[1])
        for o in self.occs.values():
            tr.addOcc(o[0],o[1])
        tr.normalizeVel()
        
        with warnings.catch_warnings(record = True) as w:
            warnings.simplefilter('always')
            consist = tr.checkConsistency(nochecktime =allowInfTime)
            if len(w) > 1:
                msg = "Multiple trial errors:"
                for wm in w:
                    msg += '\n' + str(wm.message)
                tkMessageBox.showerror('Trial consistency error!',msg)
            elif len(w) == 1:
                tkMessageBox.showerror('Trial consistency error!',str(w[0].message))
        if not consist: return None
        return tr
    
    def save(self, flnm = None, saveas = False):
        
        tr = self.makeTrial()
        if tr is None:
            #tkMessageBox.showerror('Trial error!', 'Trial is not a valid red/green trial. See console for details on what must be changed.')
            return None
        
        if flnm is None and (self.deffl is None or saveas):
            if self.deffl is None: nm = self.name + '.ptr'
            else: nm = os.path.basename(self.deffl)
            flnm = tkFileDialog.asksaveasfilename(defaultextension='.ptr', initialfile = nm)
            if flnm == '': return False
        else: flnm = self.deffl
        
        tr.save(flnm, askoverwrite = False)
        self.deffl = flnm
        self.name = os.path.splitext(os.path.basename(flnm))[0]
        self.changed = False
        return True