Ejemplo n.º 1
0
class DropletRunner(object):
    """Run a script as a droplet (an application onto which you drop file) with a log window.
    
    Data the script writes to sys.stdout and sys.stderr is written to a log window;
    stderr output is shown in red.    

    On Mac OS X additional files may be dropped on the application icon once the first batch is processed.
    I don't know how to support this on other platforms.
    """
    def __init__(self, scriptPath, title=None, initialText=None, **keyArgs):
        """Construct and run a DropletRunner
        
        Inputs:
        - scriptPath: path to script to run when files are dropped on the application
        - title: title for log window; if None then generated from scriptPath
        - initialText: initial text to display in log window
        **keyArgs: all other keyword arguments are sent to the RO.Wdg.LogWdg constructor
        """
        self.isRunning = False
        self.scriptPath = os.path.abspath(scriptPath)
        if not os.path.isfile(scriptPath):
            raise RuntimeError("Cannot find script %r" % (self.scriptPath, ))

        self.tkRoot = tkinter.Tk()
        self._timer = Timer()

        if title == None:
            title = os.path.splitext(os.path.basename(scriptPath))[0]
        self.tkRoot.title(title)

        if RO.OS.PlatformName == "mac":
            self.tkRoot.createcommand('::tk::mac::OpenDocument',
                                      self._macOpenDocument)
            # the second argument is a process ID (approximately) if run as an Applet;
            # the conditional handles operation from the command line
            if len(sys.argv) > 1 and sys.argv[1].startswith("-"):
                filePathList = sys.argv[2:]
            else:
                filePathList = sys.argv[1:]
        else:
            filePathList = sys.argv[1:]

        self.logWdg = LogWdg.LogWdg(self.tkRoot, **keyArgs)
        self.logWdg.grid(row=0, column=0, sticky="nsew")
        self.tkRoot.grid_rowconfigure(0, weight=1)
        self.tkRoot.grid_columnconfigure(0, weight=1)

        if initialText:
            self.logWdg.addOutput(initialText)

        if filePathList:
            self.runFiles(filePathList)

        self.tkRoot.mainloop()

    def runFiles(self, filePathList):
        """Run the script with the specified files
        """
        #        print "runFiles(filePathList=%s)" % (filePathList,)
        self.isRunning = True
        argList = [sys.executable, self.scriptPath] + list(filePathList)
        self.subProc = subprocess.Popen(argList,
                                        stdout=subprocess.PIPE,
                                        stderr=subprocess.PIPE)
        self.tkRoot.tk.createfilehandler(self.subProc.stderr, tkinter.READABLE,
                                         self._readStdErr)
        self.tkRoot.tk.createfilehandler(self.subProc.stdout, tkinter.READABLE,
                                         self._readStdOut)
        self._poll()

    def _macOpenDocument(self, *filePathList):
        """Handle Mac OpenDocument event
        """
        self.runFiles(filePathList)

    def _poll(self):
        """Poll for subprocess completion
        """
        if self.subProc.returncode != None:
            self._cleanup()
        else:
            self._timer(0.1, self._poll)

    def _readStdOut(self, *dumArgs):
        """Read and log data from script's stdout
        """
        self.logWdg.addOutput(self.subProc.stdout.read())
        if self.subProc.poll() != None:
            self._cleanup()

    def _readStdErr(self, *dumArgs):
        """Read and log data from script's stderr
        """
        self.logWdg.addOutput(self.subProc.stderr.read(),
                              severity=RO.Constants.sevError)
        if self.subProc.poll() != None:
            self._cleanup()

    def _cleanup(self):
        """Close Tk file handlers and print any final data from the subprocess
        """
        self._timer.cancel()
        if self.isRunning:
            self.isRunning = False
            self.tkRoot.tk.deletefilehandler(self.subProc.stdout)
            self.tkRoot.tk.deletefilehandler(self.subProc.stderr)
            outData, errData = self.subProc.communicate()
            if outData:
                self.logWdg.addOutput(outData)
            if errData:
                self.logWdg.addOutput(errData, severity=RO.Constants.sevError)
Ejemplo n.º 2
0
class FTPLogWdg(Tkinter.Frame):
    """A widget to initiate file get via ftp, to display the status
    of the transfer and to allow users to abort the transfer.
    
    Inputs:
    - master: master widget
    - maxTransfers: maximum number of simultaneous transfers; additional transfers are queued
    - maxLines: the maximum number of lines to display in the log window. Extra lines are removed (unless a queued or running transfer would be removed).
    - helpURL: the URL of a help page; it may include anchors for:
      - "LogDisplay" for the log display area
      - "From" for the From field of the details display
      - "To" for the To field of the details display
      - "State" for the State field of the details display
      - "Abort" for the abort button in the details display
    """
    def __init__(self,
        master,
        maxTransfers = 1,
        maxLines = 500,
        helpURL = None,
    **kargs):
        Tkinter.Frame.__init__(self, master = master, **kargs)
        self._memDebugDict = {}
        
        self.maxLines = maxLines
        self.maxTransfers = maxTransfers
        self.selFTPGet = None # selected getter, for displaying details; None if none
        
        self.dispList = []  # list of displayed ftpGets
        self.getQueue = []  # list of unfinished (ftpGet, stateLabel, ftpCallback) triples

        self._timer = Timer()
        
        self.yscroll = Tkinter.Scrollbar (
            master = self,
            orient = "vertical",
        )
        self.text = Tkinter.Text (
            master = self,
            yscrollcommand = self.yscroll.set,
            wrap = "none",
            tabs = (8,),
            height = 4,
            width = 50,
        )
        self.yscroll.configure(command=self.text.yview)
        self.text.grid(row=0, column=0, sticky="nsew")
        self.yscroll.grid(row=0, column=1, sticky="ns")
        Bindings.makeReadOnly(self.text)
        if helpURL:
            CtxMenu.addCtxMenu(
                wdg = self.text,
                helpURL = helpURL + "#LogDisplay",
            )
        
        self.rowconfigure(0, weight=1)
        self.columnconfigure(0, weight=1)
        
        detFrame = Tkinter.Frame(self)
            
        gr = RO.Wdg.Gridder(detFrame, sticky="ew")
        
        self.fromWdg = RO.Wdg.StrEntry(
            master = detFrame,
            readOnly = True,
            helpURL = helpURL and helpURL + "#From",
            borderwidth = 0,
        )
        gr.gridWdg("From", self.fromWdg, colSpan=3)

        self.toWdg = RO.Wdg.StrEntry(
            master = detFrame,
            readOnly = True,
            helpURL = helpURL and helpURL + "#To",
            borderwidth = 0,
        )
        gr.gridWdg("To", self.toWdg, colSpan=2)
        
        self.stateWdg = RO.Wdg.StrEntry(
            master = detFrame,
            readOnly = True,
            helpURL = helpURL and helpURL + "#State",
            borderwidth = 0,
        )
        self.abortWdg = RO.Wdg.Button(
            master = detFrame,
            text = "Abort",
            command = self._abort,
            helpURL = helpURL and helpURL + "#Abort",
        )
        gr.gridWdg("State", self.stateWdg, colSpan=2)
        
        self.abortWdg.grid(row=1, column=2, rowspan=2, sticky="s")
        
        detFrame.columnconfigure(1, weight=1)
        
        detFrame.grid(row=1, column=0, columnspan=2, sticky="ew")
        
        self.text.bind("<ButtonPress-1>", self._selectEvt)
        self.text.bind("<B1-Motion>", self._selectEvt)
        
        self._updAllStatus()
        
        atexit.register(self._abortAll)

    def getFile(self,
        host,
        fromPath,
        toPath,
        isBinary = True,
        overwrite = False,
        createDir = True,
        callFunc = None,
        dispStr = None,
        username = None,
        password = None,
    ):
        """Get a file
    
        Inputs:
        - host  IP address of ftp host
        - fromPath  full path of file on host to retrieve
        - toPath    full path of destination file
        - isBinary  file is binary? (if False, EOL translation is probably performed)
        - overwrite: if True, overwrites the destination file if it exists;
            otherwise raises ValueError
        - createDir: if True, creates any required directories;
            otherwise raises ValueError
        - callFunc: called whenever more data is read or the state changes;
            receives one argument: an RO.Comm.FTPGet.FTPGet object.
        - dispStr   a string to display while downloading the file;
                    if omitted, an ftp URL (with no username/password) is created
        - username  the usual; *NOT SECURE*
        - password  the usual; *NOT SECURE*
        """
#       print "getFile(%r, %r, %r)" % (host, fromPath, toPath)
        stateLabel = RO.Wdg.StrLabel(self, anchor="w", width=FTPGet.StateStrMaxLen)
        
        ftpGet = FTPGet(
            host = host,
            fromPath = fromPath,
            toPath = toPath,
            isBinary = isBinary,
            overwrite = overwrite,
            createDir = createDir,
            startNow = False,
            dispStr = dispStr,
            username = username,
            password = password,
        )
        self._trackMem(ftpGet, "ftpGet(%s)" % (fromPath,))

        # display item and append to list
        # (in that order so we can test for an empty list before displaying)
        if self.dispList:
            # at least one item is shown
            self.text.insert("end", "\n")
            doAutoSelect = self.selFTPGet in (self.dispList[-1], None)
        else:
            doAutoSelect = True
        self.text.window_create("end", window=stateLabel)
        self.text.insert("end", ftpGet.dispStr)
        self.dispList.append(ftpGet)
        
        self._timer.cancel()

        # append ftpGet to the queue
        ftpCallback = FTPCallback(ftpGet, callFunc)
        self.getQueue.append((ftpGet, stateLabel, ftpCallback))
        
        # purge old display items if necessary
        ind = 0
        selInd = None
        while max(self.maxLines, ind) < len(self.dispList):
            #print "FTPLogWdg.getFile: maxLines=%s, ind=%s, nEntries=%s" % (self.maxLines, ind, len(self.dispList),)

            # only erase entries for files that are finished
            if not self.dispList[ind].isDone:
                #print "FTPLogWdg.getFile: file at ind=%s is not done" % (ind,)
                ind += 1
                continue
            #print "FTPLogWdg.getFile: purging entry at ind=%s" % (ind,)
            
            if (not doAutoSelect) and (self.selFTPGet == self.dispList[ind]):
                selInd = ind
                #print "FTPLogWdg.getFile: purging currently selected file; saving index"

            del(self.dispList[ind])
            self.text.delete("%d.0" % (ind+1,), "%d.0" % (ind+2,))

        # if one of the purged items was selected,
        # select the next down extant item
        # auto scroll
        if doAutoSelect:
            self._selectInd(-1)
            self.text.see("end")
        elif selInd is not None:
            self._selectInd(selInd)
        
        #print "dispList=", self.dispList
        #print "getQueue=", self.getQueue

    
    def _abort(self):
        """Abort the currently selected transaction (if any).
        """
        if self.selFTPGet:
            self.selFTPGet.abort()
    
    def _abortAll(self):
        """Abort all transactions (for use at exit).
        """
        for ftpGet, stateLabel, callFunc in self.getQueue:
            if not ftpGet.isDone:
                ftpGet.abort()

    def _selectEvt(self, evt):
        """Determine the line currently pointed to by the mouse
        and show details for that transaction.
        Intended to handle the mouseDown event.
        """
        self.text.tag_remove("sel", "1.0", "end")
        x, y = evt.x, evt.y
        mousePosStr = "@%d,%d" % (x, y)
        indStr = self.text.index(mousePosStr)
        ind = int(indStr.split(".")[0]) - 1
        self._selectInd(ind)
        return "break"
    
    def _selectInd(self, ind):
        """Display details for the ftpGet at self.dispList[ind]
        and selects the associated line in the displayed list.
        If ind is None then displays no info and deselects all.
        """
        self.text.tag_remove('sel', '1.0', 'end')
        try:
            self.selFTPGet = self.dispList[ind]
            if ind < 0:
                lineNum = len(self.dispList) + 1 + ind
            else:
                lineNum = ind + 1
            self.text.tag_add('sel', '%s.0' % lineNum, '%s.0 lineend' % lineNum)
        except IndexError:
            self.selFTPGet = None
        self._updDetailStatus()

    def _trackMem(self, obj, objName):
        """Print a message when an object is deleted.
        """
        if not _DebugMem:
            return
        objID = id(obj)
        def refGone(ref=None, objID=objID, objName=objName):
            print("%s deleting %s" % (self.__class__.__name__, objName,))
            del(self._memDebugDict[objID])

        self._memDebugDict[objID] = weakref.ref(obj, refGone)
        del(obj)
    
    def _updAllStatus(self):
        """Update status for running transfers
        and start new transfers if there is room
        """
        newGetQueue = list()
        nRunning = 0
        for ftpGet, stateLabel, ftpCallback in self.getQueue:
            if ftpGet.isDone:
                ftpCallback()
            else:
                newGetQueue.append((ftpGet, stateLabel, ftpCallback))
                state = ftpGet.state
                if state == ftpGet.Queued:
                    if nRunning < self.maxTransfers:
                        ftpGet.start()
                        nRunning += 1
                        ftpCallback()
                elif state in (ftpGet.Running, ftpGet.Connecting):
                    nRunning += 1
            self._updOneStatus(ftpGet, stateLabel)
        self.getQueue = newGetQueue
    
        self._updDetailStatus()
         
        self._timer.start(_StatusInterval, self._updAllStatus)
        
    def _updOneStatus(self, ftpGet, stateLabel):
        """Update the status of one transfer"""
        state = ftpGet.state
        if state == ftpGet.Running:
            if ftpGet.totBytes:
                pctDone = int(round(100 * ftpGet.readBytes / float(ftpGet.totBytes)))
                stateLabel["text"] = "%3d %%" % pctDone
            else:
                kbRead = ftpGet.readBytes / 1024
                stateLabel["text"] = "%d kB" % kbRead
        else:
            # display state
            if state == ftpGet.Failed:
                severity = RO.Constants.sevError
            else:
                severity = RO.Constants.sevNormal
            stateLabel.set(state, severity=severity)
    
    def _updDetailStatus(self):
        """Update the detail status for self.selFTPGet"""
        if not self.selFTPGet:
            self.fromWdg.set("")
            self.toWdg.set("")
            self.stateWdg.set("")
            if self.abortWdg.winfo_ismapped():
                self.abortWdg.grid_remove()
            return

        ftpGet = self.selFTPGet
        currState = ftpGet.state
        
        # show or hide abort button, appropriately
        if ftpGet.isAbortable:
            if not self.abortWdg.winfo_ismapped():
                self.abortWdg.grid()
        else:
            if self.abortWdg.winfo_ismapped():
                self.abortWdg.grid_remove()

        stateStr = currState
        severity = RO.Constants.sevNormal
        if currState == ftpGet.Running:
            if ftpGet.totBytes:
                stateStr = "read %s of %s bytes" % (ftpGet.readBytes, ftpGet.totBytes)
            else:
                stateStr = "read %s bytes" % (ftpGet.readBytes,)
        else:
            if currState == ftpGet.Failed:
                stateStr = "Failed: %s" % (ftpGet.getException())
                severity = RO.Constants.sevError
            elif currState in (ftpGet.Aborting, ftpGet.Aborted):
                severity = RO.Constants.sevWarning

        self.stateWdg.set(stateStr, severity=severity)
        self.fromWdg.set(ftpGet.dispStr)
        self.toWdg.set(ftpGet.toPath)
Ejemplo n.º 3
0
class WaitForTCPServer(object):
    """Wait for a TCP server to accept a connection
    """
    def __init__(self, host, port, callFunc, timeLim=5, pollInterval=0.2):
        """Start waiting for a TCP server to accept a connection
        
        @param[in] host  host address of server
        @param[in] port  port number of server
        @param[in] callFunc  function to call when server ready or wait times out;
            receives one parameter: this object
        @param[in] timeLim  approximate maximum wait time (sec);
            the actual wait time may be up to pollInterval longer
        @param[in] pollInterval  interval at which to poll (sec)
        
        Useful attributes:
        - isDone: the wait is over
        - didFail: the wait failed
        """
        self.host = host
        self.port = port
        self.isDone = False
        self.didFail = False
        self._callFunc = callFunc
        self._pollInterval = float(pollInterval)
        self._timeLim = float(timeLim)
        self._pollTimer = Timer()
        self._startTime = time.time()
        self._tryConnection()
        self._timeoutTimer = Timer(timeLim, self._finish)
    
    def _tryConnection(self):
        """Attempt a connection
        """
        self._sock = TCPSocket(host=self.host, port=self.port, stateCallback=self._sockStateCallback)
    
    def _sockStateCallback(self, sock):
        """Socket state callback
        """
        if sock.isReady:
            # success
            self._finish()
        elif sock.isDone:
            # connection failed; try again
            self._pollTimer.start(self._pollInterval, self._tryConnection)
        
    def _finish(self):
        """Set _isReady and call the callback function
        """
        self._pollTimer.cancel()
        self._timeoutTimer.cancel()
        self.didFail = not self._sock.isReady
        self.isDone = True
        if not self._sock.isDone:
            self._sock.setStateCallback()
            self._sock.close()
            self._sock = None
        if self._callFunc:
            callFunc = self._callFunc
            self._callFunc = None
            safeCall2("%s._finish" % (self,), callFunc, self)

    def __repr__(self):
        return "%s(host=%s, port=%s)" % (type(self).__name__, self.host, self.port)
Ejemplo n.º 4
0
class UsersWdg(Tkinter.Frame):
    """Display the current users and those recently logged out.
    
    Inputs:
    - master    parent widget
    - retainSec time to retain information about logged out users (sec)
    - height    default height of text widget
    - width default width of text widget
    - other keyword arguments are used for the frame
    """
    def __init__ (self,
        master=None,
        retainSec=300,
        height = 10,
        width = 50,
    **kargs):
        Tkinter.Frame.__init__(self, master, **kargs)
        
        hubModel = TUI.Models.HubModel.getModel()
        self.tuiModel = TUI.TUIModel.getModel()
        
        # entries are commanders (prog.user)
        self._cmdrList = []
        # entries are (cmdr, time deleted); time is from time.time()
        self._delCmdrTimeList = []
        # time to show deleted users
        self._retainSec = retainSec
        
        # dictionary of user name: User object
        self.userDict = dict()
        
        self._updateTimer = Timer()
                
        self.yscroll = Tkinter.Scrollbar (
            master = self,
            orient = "vertical",
        )
        self.text = Tkinter.Text (
            master = self,
            yscrollcommand = self.yscroll.set,
            wrap = "none",
            tabs = "1.6c 5.0c 6.7c 8.5c",
            height = height,
            width = width,
        )
        self.yscroll.configure(command=self.text.yview)
        self.text.grid(row=0, column=0, sticky="nsew")
        self.yscroll.grid(row=0, column=1, sticky="ns")
        RO.Wdg.Bindings.makeReadOnly(self.text)
        RO.Wdg.addCtxMenu(
            wdg = self.text,
            helpURL = _HelpPage,
        )
        
        self.rowconfigure(0, weight=1)
        self.columnconfigure(0, weight=1)
        
        self.text.tag_configure("del", overstrike=True)
        self.text.tag_configure("me", underline=True)

        hubModel.user.addCallback(self.updUser, callNow=False)
        hubModel.users.addCallback(self.updUsers)

    def scheduleUpdate(self, afterSec=1.0):
        """Schedule a new update
        """
        self._updateTimer.start(afterSec, self.updDisplay)

    def updDisplay(self):
        """Display current data.
        """
        self._updateTimer.cancel()
        
        myCmdr = self.tuiModel.getCmdr()
        maxDisplayTime = time.time() - self._retainSec

        self.text.delete("1.0", "end")
        doScheduleUpdate = False
        deleteCmdrList = []
        for cmdr in sorted(self.userDict.keys()):
            userObj = self.userDict[cmdr]
            if userObj.clientName == "monitor":
                continue
            if userObj.isConnected:
                tagList = ["curr"]
            elif userObj.disconnTime < maxDisplayTime:
                deleteCmdrList.append(cmdr)
                continue
            else:
                tagList = ["del"]
                doScheduleUpdate = True
            if cmdr == myCmdr:
                tagList.append("me")
            displayStr = "%s\t%s\t%s\t%s\t%s\n" % \
                (userObj.prog, userObj.user, userObj.clientName, userObj.clientVersion, userObj.systemInfo)
            self.text.insert("end", displayStr, " ".join(tagList))

        for cmdr in deleteCmdrList:
            del(self.userDict[cmdr])
        
        if doScheduleUpdate:
            self.scheduleUpdate()

    def updUser(self, userInfo, isCurrent, keyVar=None):
        """User keyword callback; add user data to self.userDict"""
        if (not isCurrent) or (userInfo == None):
            return

        cmdr = userInfo[0]
        oldUserObj = self.userDict.get(cmdr, None)
        if oldUserObj:
            oldUserObj.setUserInfo(userInfo)
        else:
            self.userDict[cmdr] = User(cmdr, userInfo)
        self.scheduleUpdate()

    def updUsers(self, newCmdrList, isCurrent=True, keyVar=None):
        """Users keyword callback. The value is a list of commander IDs.
        """
        if not isCurrent:
            # set background to notCurrent?
            return

        for cmdr in newCmdrList:
            userObj = self.userDict.get(cmdr, None)
            if userObj:
                if not userObj.isConnected:
                    userObj.setConnected()
            else:
                self.userDict[cmdr] = User(cmdr)

        # handle disconnected users (those in my userDict that aren't in newCmdrList)
        # handle timeout and final deletion in updDisplay, since it has to remove
        # stale entries even if the users keyword hasn't changed.
        disconnCmdrSet = set(self.userDict.keys()) - set(newCmdrList)
        for cmdr in disconnCmdrSet:
            userObj = self.userDict[cmdr]
            if userObj.isConnected:
                userObj.setDisconnected()

        self.updDisplay()
Ejemplo n.º 5
0
class TestGuiderWdg(Tkinter.Frame):
    def __init__(self, testDispatcher, master):
        Tkinter.Frame.__init__(self, master)
        self.testDispatcher = testDispatcher

        random.seed(0)
   
        self.tuiModel = self.testDispatcher.tuiModel
        self.pollTimer = Timer()
        self.oldPendingCmd = None
        self.fileNum = 0

        gr = RO.Wdg.Gridder(self, sticky="ew")
    
        self.guideWdg = AgileGuideWindow.AgileGuideWdg(self)
        gr.gridWdg(False, self.guideWdg, colSpan=10)
        
        self.imageAvailWdg = RO.Wdg.Button(
            master = self,
            text = "Image is Available",
            callFunc = self.dispatchFileData,
        )
        gr.gridWdg(None, self.imageAvailWdg)
        
        self.starPosWdgSet = []
        for ii in range(2):
            letter = ("X", "Y")[ii]
            starPosWdg = RO.Wdg.FloatEntry(
                master = self,
                label = "Star Pos %s" % (letter,),
                minValue = 0,
                defValue = 100 * (ii + 1),
                maxValue = 5000,
                autoIsCurrent = True,
                autoSetDefault = True,
                helpText = "Star %s position in binned pixels" % (letter,),
            )
            self.starPosWdgSet.append(starPosWdg)
        gr.gridWdg("Star Pos", self.starPosWdgSet, "pix")

        self.centroidRadWdg = RO.Wdg.IntEntry(
            master = self,
            label = "Centroid Rad",
            minValue = 5,
            maxValue = 1024,
            defValue = 10,
            defMenu = "Default",
            autoIsCurrent = True,
            autoSetDefault = True,
            helpText = "Radius of region to centroid in binned pixels; don't skimp",
        )
        gr.gridWdg(self.centroidRadWdg.label, self.centroidRadWdg, "arcsec", sticky="ew")

        self.numToFindWdg = RO.Wdg.IntEntry(
            master = self,
            label = "Num To Find",
            minValue = 0,
            maxValue = 100,
            defValue = 5,
            defMenu = "Default",
            autoIsCurrent = True,
            autoSetDefault = True,
            helpText = "Number of stars to find (0 for findstars to fail)",
        )
        gr.gridWdg(self.numToFindWdg.label, self.numToFindWdg)
        
        self.centroidOKWdg = RO.Wdg.Checkbutton(
            master = self,
            text = "Centroid OK",
            defValue = True,
            helpText = "Should centroid command succeed?",
        )
        gr.gridWdg(None, self.centroidOKWdg)
        
        self.offsetOKWdg = RO.Wdg.Checkbutton(
            master = self,
            text = "Offset OK",
            defValue = True,
            helpText = "Should offset command succeed?",
        )
        gr.gridWdg(None, self.offsetOKWdg)
        
        self.axesTrackingWdg = RO.Wdg.Checkbutton(
            master = self,
            text = "Axes Tracking",
            defValue = True,
            callFunc = self.axesTrackingCallback,
            helpText = "Are axes tracking?",
        )
        gr.gridWdg(None, self.axesTrackingWdg)
        
        self.isInstAgileWdg = RO.Wdg.Checkbutton(
            master = self,
            text = "Is Curr Inst Agile?",
            defValue = True,
            callFunc = self.isInstAgileCallback,
            helpText = "Is the current instrument Agile?",
        )
        gr.gridWdg(None, self.isInstAgileWdg)

        self.useWrongCmdrWdg = RO.Wdg.Checkbutton(
            master = self,
            text = "Use Wrong Cmdr",
            defValue = False,
            helpText = "Should replies be for a different cmdr?",
        )
        gr.gridWdg(None, self.useWrongCmdrWdg)

        self.useWrongCmdIDWdg = RO.Wdg.Checkbutton(
            master = self,
            text = "Use Wrong Cmd ID",
            defValue = False,
            helpText = "Should replies be for a different command?",
        )
        gr.gridWdg(None, self.useWrongCmdIDWdg)

        self.useWrongActorWdg = RO.Wdg.Checkbutton(
            master = self,
            text = "Use Wrong Actor",
            defValue = False,
            helpText = "Should replies be for a different actor?",
        )
        gr.gridWdg(None, self.useWrongActorWdg)
        
        self.grid_columnconfigure(9, weight=1)
        
        tccData = (
            "inst=Agile",
            "iimScale=-27784.4, 27569.0",
            "axisCmdState=Tracking, Tracking, Tracking",
        )
        self.testDispatcher.dispatch(tccData, actor="tcc")
        self.testDispatcher.dispatch("bin=1", actor="agile")
        
        self.pollPendingCmd()

    def axesTrackingCallback(self, wdg=None):
        if self.axesTrackingWdg.getBool():
            tccData = "axisCmdState=Tracking, Tracking, Tracking"
        else:
            tccData = "axisCmdState=Tracking, Halted, Tracking"
        self.testDispatcher.dispatch(tccData, actor="tcc")

    def isInstAgileCallback(self, wdg=None):
        if self.isInstAgileWdg.getBool():
            tccData = "inst=Agile"
        else:
            tccData = "inst=SPICam"
        self.testDispatcher.dispatch(tccData, actor="tcc")
        
    def dispatchFileData(self, wdg=None):
        keyArgs = self.getDispatchKeyArgs("agileExpose", cmdID=0)
        fileName = "image%d" % (self.fileNum,)
        self.fileNum += 1
        filesKeyword = makeFilesKeyword(cmdr=keyArgs["cmdr"], fileName=fileName)
        self.testDispatcher.dispatch(filesKeyword, msgCode=":", **keyArgs)

    def dispatchFindData(self, wdg=None):
        keyArgs = self.getDispatchKeyArgs("afocus")

        numToFind = self.numToFindWdg.getNum()

        if numToFind < 1:
            self.testDispatcher.dispatch("text='No stars found'", msgCode="f", **keyArgs)
            return

        mainXYPos = [wdg.getNum() for wdg in self.starPosWdgSet]
        centroidRad = self.centroidRadWdg.getNum()
        findData = makeFindData(numFound=numToFind, mainXYPos=mainXYPos, centroidRad=centroidRad)
        self.testDispatcher.dispatch(findData, msgCode="i", **keyArgs)
        self.testDispatcher.dispatch("", msgCode=":", **keyArgs)

    def dispatchCentroidData(self, wdg=None):
        keyArgs = self.getDispatchKeyArgs("afocus")

        if not self.centroidOKWdg.getBool():
            self.testDispatcher.dispatch("text='No stars found'", msgCode="f", **keyArgs)
            return
        
        xyPos = [wdg.getNum() for wdg in self.starPosWdgSet]
        centroidRad = self.centroidRadWdg.getNum()
        centroidData = makeStarKeyword(isFind=False, xyPos=xyPos, randRange=10, centroidRad=centroidRad)
        self.testDispatcher.dispatch(centroidData, msgCode=":", **keyArgs)

    def getDispatchKeyArgs(self, actor, cmdID=None):
        """Get keyword arguments for the test dispatcher's dispatch command
        """
        if self.useWrongCmdrWdg.getBool():
            cmdr = "APO.other"
        else:
            cmdr = self.tuiModel.getCmdr()

        if cmdID is None:
            if self.guideWdg.pendingCmd:
                cmdID = self.guideWdg.pendingCmd.cmdID or 0
            else:
                cmdID = 0

        if self.useWrongCmdIDWdg.getBool():
            cmdID += 1000
        
        if self.useWrongActorWdg.getBool():
            actor = "other"
        else:
            actor = actor

        return dict(cmdr=cmdr, cmdID=cmdID, actor=actor)

    def pollPendingCmd(self):
        """Poll to see if there's a new pending command and respond accordingly
        """
        self.pollTimer.cancel()

        if self.guideWdg.pendingCmd != self.oldPendingCmd:
            self.oldPendingCmd = self.guideWdg.pendingCmd
            if not self.oldPendingCmd.isDone():
                self.replyToCommand()

        self.pollTimer.start(1.0, self.pollPendingCmd)

    def replyToCommand(self):
        """Issue the appropriate replly to a pending command
        """
#         print "replyToCommand", self.oldPendingCmd
        actor = self.oldPendingCmd.actor.lower()
        cmdStr = self.oldPendingCmd.cmdStr
        cmdID = self.oldPendingCmd.cmdID

        keyArgs = self.getDispatchKeyArgs(actor)
        
        if actor == "tcc":
            if self.offsetOKWdg.getBool():
                self.testDispatcher.dispatch("", msgCode=":", **keyArgs)
            else:
                self.testDispatcher.dispatch("text='Offset failed'", msgCode="f", **keyArgs)
        elif actor == "afocus":
            if cmdStr.startswith("centroid"):
                self.dispatchCentroidData()
            elif cmdStr.startswith("find"):
                self.dispatchFindData()
            else:
                print "Unknown afocus command:", cmdStr
        else:
            print "Unknown actor:", actor
Ejemplo n.º 6
0
class PrefEditor(object):
    """Basic preferences editor. Works for string, numeric and boolean data
    (PrefVar, StrPrefVar, IntPrefVar, FloatPrefVar, BoolPrefVar).
    """
    def __init__(
        self,
        prefVar,
        master,
        row=0,
        column=0,
    ):
        self.master = master
        self.prefVar = prefVar

        # create and set a variable to contain the edited value
        self.editVar = Tkinter.StringVar()
        self.editVar.set(self.prefVar.getValueStr())

        # save initial value, in case we have to restore it
        self.initialValue = self.getCurrentValue()

        # save last good value, for use if a typed char is rejected
        self.mostRecentValidValue = self.editVar.get()

        # a list (in grid order) of (widget name, sticky setting)
        wdgInfo = (
            ("labelWdg", "e"),
            ("changedWdg", ""),
            ("editWdg", "w"),
            ("unitsWdg", "w"),
        )
        self.labelWdg = Tkinter.Label(self.master, text=self.prefVar.name)
        self._addCtxMenu(self.labelWdg)

        self.changedVar = Tkinter.StringVar()
        self.changedWdg = Tkinter.Label(self.master,
                                        width=1,
                                        textvariable=self.changedVar)
        self._addCtxMenu(self.changedWdg)

        self.editWdg = self._getEditWdg()
        # self.rangeWdg = self._getRangeWdg()

        if self.prefVar.units:
            self.unitsWdg = Tkinter.Label(self.master, text=self.prefVar.name)
            self._addCtxMenu(self.unitsWdg)
        else:
            self.unitsWdg = None

        # grid the widgets
        for wdgName, sticky in wdgInfo:
            wdg = getattr(self, wdgName)
            if wdg:
                wdg.grid(row=row, column=column, sticky=sticky)
            column += 1

        self.timer = Timer()

        self._setupCallbacks()

    def getCurrentValue(self):
        """Returns the current value of the preference variable
        (not necessarily the value shown in the value editor).
        """
        return self.prefVar.getValueStr()

    def getDefValue(self):
        """Returns the current value of the preference variable
        (not necessarily the value shown in the value editor).
        """
        return self.prefVar.getDefValueStr()

    def getEditValue(self):
        """Returns the value from the editor widget"""
        return self.editVar.get()

    def getInitialValue(self):
        return self.initialValue

    def setVariable(self):
        """Sets the preference variable to the edit value"""
        self.prefVar.setValue(self.getEditValue())
        self.updateChanged()

    def showValue(self, value):
        self.editVar.set(value)
        self.updateEditor()

    def showCurrentValue(self):
        self.showValue(self.getCurrentValue())

    def showDefaultValue(self):
        self.showValue(self.getDefValue())

    def showInitialValue(self):
        self.showValue(self.getInitialValue())

    def unappliedChanges(self):
        """Returns true if the user has changed the value and it has not been applied"""
        return self.getEditValue() != self.prefVar.getValueStr()

    def updateEditor(self):
        """Called after editVal is changed (and verified)"""
        pass

    def updateChanged(self):
        """Updates the "value changed" indicator.
        """
        self.timer.cancel()
        editValue = self.getEditValue()
        #       print "updateChanged called"
        #       print "editValue = %r" % editValue
        #       print "currentValue = %r" % self.getCurrentValue()
        #       print "initialValue = %r" % self.getInitialValue()
        #       print "defaultValue = %r" % self.getDefValue()
        if editValue == self.getCurrentValue():
            self.changedVar.set("")
        else:
            self.changedVar.set("!")

    def _addCtxMenu(self, wdg):
        """Convenience function; adds the usual contextual menu to a widget"""
        RO.Wdg.addCtxMenu(
            wdg=wdg,
            helpURL=self.prefVar.helpURL,
            configFunc=self._configCtxMenu,
        )
        wdg.helpText = self.prefVar.helpText

    def _editCallback(self, *args):
        """Called whenever the edited value changes.
        Uses after to avoid the problem of the user entering
        an invalid character that is immediately rejected;
        that rejection doesn't show up in editVar.get
        and so the changed indicator falsely turns on.
        """
        #       print "_editCallback; afterID=", self.afterID
        self.timer.start(0.001, self.updateChanged)

    def _setupCallbacks(self):
        """Set up a callback to call self.updateChanged
        whenever the edit value changes.
        """
        self.editVar.trace_variable("w", self._editCallback)

    def _getEditWdg(self):
        if self.prefVar.validValues:
            # use a pop-up list of values
            # first generate a list of strings representing the values
            valueList = [
                self.prefVar.asStr(val) for val in self.prefVar.validValues
            ]
            # now return a menu containing those values
            wdg = RO.Wdg.OptionMenu(
                master=self.master,
                var=self.editVar,
                items=valueList,
                helpText=self.prefVar.helpText,
                helpURL=self.prefVar.helpURL,
            )
            wdg.ctxSetConfigFunc(self._configCtxMenu)
            wdg.set(self.getInitialValue())
        else:
            wdg = self.prefVar.getEditWdg(self.master, self.editVar,
                                          self._configCtxMenu)
        return wdg

    def _getRangeWdg(self):
        return Tkinter.Label(self.master, text=self.prefVar.getRangeStr())

    def _getShowMenu(self):
        mbut = Tkinter.Menubutton(
            self.master,
            indicatoron=1,
            direction="below",
            borderwidth=2,
            relief="raised",
            highlightthickness=2,
        )
        mnu = Tkinter.Menu(mbut, tearoff=0)
        mnu.add_command(label="Current", command=self.showCurrentValue)
        mnu.add_command(label="Initial", command=self.showInitialValue)
        mnu.add_command(label="Default", command=self.showDefaultValue)
        mnu.add_separator()
        mnu.add_command(label="Apply", command=self.setVariable)
        mbut["menu"] = mnu
        return mbut

    def _configCtxMenu(self, mnu):
        def summaryFromVal(val):
            try:
                return self.prefVar.asSummary(val)
            except Exception as e:
                sys.stderr.write("could not get summary of %r for %s: %s\n" %
                                 (val, self.prefVar.name, e))
                return "???"

        # basic current/initial/default menu items
        mnu.add_command(label="Current (%s)" %
                        (summaryFromVal(self.getCurrentValue()), ),
                        command=self.showCurrentValue)
        mnu.add_command(label="Initial (%s)" %
                        (summaryFromVal(self.getInitialValue()), ),
                        command=self.showInitialValue)
        mnu.add_command(label="Default (%s)" %
                        (summaryFromVal(self.getDefValue()), ),
                        command=self.showDefaultValue)
        mnu.add_separator()

        # minimum and maximum values, if present
        try:
            didAdd = False
            if self.prefVar.minValue is not None:
                mnu.add_command(label="Minimum (%s)" %
                                (summaryFromVal(self.prefVar.minValue), ),
                                command=self.showCurrentValue)
                didAdd = True
            if self.prefVar.maxValue is not None:
                mnu.add_command(label="Maximum (%s)" %
                                (summaryFromVal(self.prefVar.maxValue), ),
                                command=self.showCurrentValue)
                didAdd = True
            if didAdd:
                mnu.add_separator()
        except AttributeError:
            pass

        # apply menu item
        mnu.add_command(label="Apply", command=self.setVariable)
        if self.prefVar.helpURL:
            mnu.add_separator()
            return True
        return False
Ejemplo n.º 7
0
class WaitForTCPServer(object):
    """Wait for a TCP server to accept a connection
    """
    def __init__(self, host, port, callFunc, timeLim=5, pollInterval=0.2):
        """Start waiting for a TCP server to accept a connection
        
        @param[in] host: host address of server
        @param[in] port: port number of server
        @param[in] callFunc: function to call when server ready or wait times out;
            receives one parameter: this object
        @param[in] timeLim: approximate maximum wait time (sec);
            the actual wait time may be up to pollInterval longer
        @param[in] pollInterval: interval at which to poll (sec)
        
        Useful attributes:
        - isDone: the wait is over
        - didFail: the wait failed
        """
        self.host = host
        self.port = port
        self.isDone = False
        self.didFail = False
        self._callFunc = callFunc
        self._pollInterval = float(pollInterval)
        self._timeLim = float(timeLim)
        self._pollTimer = Timer()
        self._startTime = time.time()
        self._tryConnection()
        self._timeoutTimer = Timer(timeLim, self._finish)
    
    def _tryConnection(self):
        """Attempt a connection
        """
        self._sock = TCPSocket(host=self.host, port=self.port, stateCallback=self._sockStateCallback)
    
    def _sockStateCallback(self, sock):
        """Socket state callback
        """
        if sock.isReady:
            # success
            self._finish()
        elif sock.isDone:
            # connection failed; try again
            self._pollTimer.start(self._pollInterval, self._tryConnection)
        
    def _finish(self):
        """Set _isReady and call the callback function
        """
        self._pollTimer.cancel()
        self._timeoutTimer.cancel()
        self.didFail = not self._sock.isReady
        self.isDone = True
        if not self._sock.isDone:
            self._sock.setStateCallback()
            self._sock.close()
            self._sock = None
        if self._callFunc:
            callFunc = self._callFunc
            self._callFunc = None
            safeCall2("%s._finish" % (self,), callFunc, self)

    def __repr__(self):
        return "%s(host=%s, port=%s)" % (type(self).__name__, self.host, self.port)
Ejemplo n.º 8
0
class TimeBar(ProgressBar):
    """Progress bar to display elapsed or remaining time in seconds.
    Inputs:
    - countUp: if True, counts up, else counts down
    - autoStop: automatically stop when the limit is reached
    - updateInterval: how often to update the display (sec)
    **kargs: other arguments for ProgressBar, including:
      - value: initial time;
        typically 0 for a count up timer, maxValue for a countdown timer
        if omitted then 0 is shown and the bar does not progress until you call start.
      - minvalue: minimum time; typically 0
      - maxValue: maximum time
    """
    def __init__(self,
                 master,
                 countUp=False,
                 valueFormat=("%3.0f sec", "??? sec"),
                 autoStop=False,
                 updateInterval=0.1,
                 **kargs):
        ProgressBar.__init__(self,
                             master=master,
                             valueFormat=valueFormat,
                             **kargs)
        self._autoStop = bool(autoStop)
        self._countUp = bool(countUp)

        self._updateInterval = updateInterval
        self._updateTimer = Timer()
        self._startTime = None

        if "value" in kargs:
            self.start(kargs["value"])

    def clear(self):
        """Set the bar length to zero, clear the numeric time display and stop the timer.
        """
        self._updateTimer.cancel()
        ProgressBar.clear(self)
        self._startTime = None

    def pause(self, value=None):
        """Pause the timer.
        
        Inputs:
        - value: the value at which to pause; if omitted then the current value is used
        
        Error conditions: does nothing if not running.
        """
        if self._updateTimer.cancel():
            # update timer was running
            if value:
                self.setValue(value)
            else:
                self._updateTime(reschedule=False)

    def resume(self):
        """Resume the timer from the current value.

        Does nothing if not paused or running.
        """
        if self._startTime == None:
            return
        self._startUpdate()

    def start(self, value=None, newMin=None, newMax=None, countUp=None):
        """Start the timer.

        Inputs:
        - value: starting value; if None, set to 0 if counting up, max if counting down
        - newMin: minimum value; if None then the existing value is used
        - newMax: maximum value: if None then the existing value is used
        - countUp: if True/False then count up/down; if None then the existing value is used
        
        Typically you will only specify newMax or nothing.
        """
        if newMin != None:
            self.minValue = float(newMin)
        if newMax != None:
            self.maxValue = float(newMax)
        if countUp != None:
            self._countUp = bool(countUp)

        if value != None:
            value = float(value)
        elif self._countUp:
            value = 0.0
        else:
            value = self.maxValue
        self.setValue(value)

        self._startUpdate()

    def _startUpdate(self):
        """Starts updating from the current value.
        """
        if self._countUp:
            self._startTime = time.time() - self.value
        else:
            self._startTime = time.time() - (self.maxValue - self.value)
        self._updateTime()

    def _updateTime(self, reschedule=True):
        """Automatically update the elapsed time display.
        Call once to get things going.
        """
        # print "_updateTime called"
        # cancel pending update, if any
        self._updateTimer.cancel()

        if self._startTime == None:
            raise RuntimeError("bug! nothing to update")

        # update displayed value
        if self._countUp:
            value = time.time() - self._startTime
            if (self._autoStop and value >= self.maxValue):
                self.setValue(self.maxValue)
                return
        else:
            value = (self._startTime + self.maxValue) - time.time()
            if (self._autoStop and value <= 0.0):
                self.setValue(0)
                return

        self.setValue(value)

        # if requested, schedule next update
        if reschedule:
            self._updateTimer.start(self._updateInterval, self._updateTime)
Ejemplo n.º 9
0
class ObjPosWdg(RO.Wdg.InputContFrame):
    """A widget for specifying object positions

    Inputs:
    - master        master Tk widget -- typically a frame or window
    - userModel     a TUI.TCC.UserModel; specify only if global model
                    not wanted (e.g. for checking catalog values)
    - **kargs       keyword arguments for Tkinter.Frame
    """
    def __init__(self, master=None, userModel=None, **kargs):
        RO.Wdg.InputContFrame.__init__(self, master, **kargs)
        gr = RO.Wdg.Gridder(self, sticky="w")

        # start out by not checking object position
        # set this true after all widgets are painted
        # and the formatting functions have had their test run
        self.checkObjPos = 0

        self._azAltRefreshTimer = Timer()

        self.objNameWdg = RO.Wdg.StrEntry(
            self,
            helpText="Object name (optional)",
            helpURL=_HelpPrefix + "NameWdg",
            width=25,
        )
        self.objName = gr.gridWdg(
            label="Name",
            dataWdg=self.objNameWdg,
            colSpan=3,
        )
        lastCol = gr.getNextCol() - 2
        self.columnconfigure(lastCol, weight=1)

        objPos1UnitsVar = Tkinter.StringVar()
        self.objPos1 = gr.gridWdg(
            label="",
            dataWdg=RO.Wdg.DMSEntry(
                self,
                minValue=0,
                maxValue=359.99999999,
                defValue=None,
                unitsVar=objPos1UnitsVar,
                isHours=
                0,  # this will vary so no initial value is actually needed
                helpText="Object position",
                helpURL=_HelpPrefix + "PosWdg",
            ),
            units=objPos1UnitsVar,
        )

        objPos2UnitsVar = Tkinter.StringVar()
        self.objPos2 = gr.gridWdg(
            label="",
            dataWdg=RO.Wdg.DMSEntry(
                self,
                minValue=0,
                maxValue=90,
                defValue=None,
                unitsVar=objPos2UnitsVar,
                isHours=0,  # always in degrees
                helpText="Object position",
                helpURL=_HelpPrefix + "PosWdg",
            ),
            units=objPos2UnitsVar,
        )

        self.coordSysWdg = CoordSysWdg.CoordSysWdg(
            master=self,
            userModel=userModel,
        )
        gr.gridWdg(
            label="CSys",
            dataWdg=self.coordSysWdg,
            colSpan=3,
        )

        self.rotWdg = RotWdg.RotWdg(
            master=self,
            userModel=userModel,
        )
        gr.gridWdg(
            label="Rot",
            dataWdg=self.rotWdg,
            colSpan=3,
        )

        azAltFrame = Tkinter.Frame(self)

        self.azWdg = RO.Wdg.FloatLabel(
            master=azAltFrame,
            precision=2,
            width=6,
            helpText="azimuth for proposed object",
            helpURL=_HelpPrefix + "Azimuth",
        )
        self.azWdg.pack(side="left")
        Tkinter.Label(azAltFrame, text="%s  Alt" %
                      (RO.StringUtil.DegStr, )).pack(side="left")

        self.altWdg = RO.Wdg.FloatLabel(
            master=azAltFrame,
            precision=2,
            width=6,
            helpText="altitude for proposed object",
            helpURL=_HelpPrefix + "Altitude",
        )
        self.altWdg.pack(side="left")
        Tkinter.Label(azAltFrame, text=RO.StringUtil.DegStr).pack(side="left")

        gr.gridWdg(
            label="Az",
            dataWdg=azAltFrame,
            colSpan=3,
        )

        self.airmassWdg = RO.Wdg.FloatLabel(
            master=self,
            precision=3,
            width=6,
            helpText="airmass for proposed object",
            helpURL=_HelpPrefix + "Airmass",
        )
        gr.gridWdg(
            label="Airmass",
            dataWdg=self.airmassWdg,
        )

        # create a set of input widget containers
        # this makes it easy to retrieve a command
        # and also to get and set all data using a value dictionary

        # note: the coordsys widget must be FIRST
        # because it has to be set (when restoring from a value dict)
        # before pos1 is set, to set the isHours flag correctly
        def formatObjPos(inputCont):
            wdgList = inputCont.getWdgList()

            # format data using the widgets
            valList = []
            for wdg in wdgList:
                if wdg.getString() == '':
                    raise ValueError, "must specify position"

                val = wdg.getNum()
                if wdg.getIsHours():
                    val = val * 15.0
                valList.append(val)
            return 'track %.7f, %.7f' % tuple(valList)

        def formatAll(inputCont):
            # container order is coordsys, objpos, rotator, name (optional)
            strList = inputCont.getStringList()
            return strList[1] + ' ' + strList[0] + ''.join(strList[2:])

        def vmsQuoteStr(astr):
            return RO.StringUtil.quoteStr(astr, '"')

        self.inputCont = RO.InputCont.ContList(
            conts=[
                self.coordSysWdg.inputCont,
                RO.InputCont.WdgCont(
                    name="ObjPos",
                    wdgs=(self.objPos1.dataWdg, self.objPos2.dataWdg),
                    formatFunc=formatObjPos,
                ),
                RO.InputCont.WdgCont(
                    name="Name",
                    wdgs=self.objNameWdg,
                    formatFunc=RO.InputCont.VMSQualFmt(vmsQuoteStr),
                ),
                self.rotWdg.inputCont,
            ],
            formatFunc=formatAll,
        )

        self.userModel = userModel or TUI.TCC.UserModel.getModel()
        self.userModel.coordSysName.addCallback(self._coordSysChanged)
        self.userModel.potentialTarget.addCallback(self.setAzAltAirmass)

        self.tccModel = TUI.TCC.TCCModel.getModel()
        self.tccModel.azLim.addCallback(self._azLimChanged)
        self.tccModel.altLim.addCallback(self._altLimChanged)

        # initialize display
        self.restoreDefault()

        self.objNameWdg.focus_set()

    def _azLimChanged(self, azLim, isCurrent, **kargs):
        #       print "_azLimitChanged(azLim=%r, isCurrent=%s)" % (azLim, isCurrent)
        coordSys = self.userModel.coordSysName.get()
        if coordSys != RO.CoordSys.Mount:
            return

        self.objPos1.dataWdg.setRange(*azLim[0:2])

    def _altLimChanged(self, altLim, isCurrent, **kargs):
        #       print "_altLimitChanged(altLim=%r, isCurrent=%s)" % (altLim, isCurrent)
        coordSys = self.userModel.coordSysName.get()
        if coordSys != RO.CoordSys.Mount:
            return

        self.objPos2.dataWdg.setRange(*altLim[0:2])

    def _coordSysChanged(self, coordSys):
        """Update the display when the coordinate system is changed.
        """
        #       print "ObjPosWdg._coordSysChanged(coordSys=%r)" % (coordSys,)
        pos1IsHours = 1
        csysObj = RO.CoordSys.getSysConst(coordSys)
        pos1IsHours = csysObj.eqInHours()
        posLabels = csysObj.posLabels()

        if coordSys in RO.CoordSys.AzAlt:
            if coordSys == RO.CoordSys.Mount:
                azLim, isCurrent = self.tccModel.azLim.get()
                pos1Range = azLim[0:2]
                altLim, isCurrent = self.tccModel.altLim.get()
                pos2Range = altLim[0:2]
            else:
                pos1Range = (0, 360)
                pos2Range = (0, 90)
        elif pos1IsHours:
            pos1Range = (0, 24)
            pos2Range = (-90, 90)
        else:
            # no such coordsys, so makes a good sanity check
            raise RuntimeError, "ObjPosWdg bug: cannot handle coordinate system %r" % (
                coordSys, )

        self.objPos1.labelWdg["text"] = posLabels[0]
        self.objPos2.labelWdg["text"] = posLabels[1]
        self.objPos1.dataWdg.setIsHours(pos1IsHours)
        self.objPos1.dataWdg.setRange(*pos1Range)
        self.objPos2.dataWdg.setRange(*pos2Range)

    def getSummary(self):
        """Returns (name, pos1, pos2, csys), all as the strings shown in the widgets
        (not the numeric values).
        It would be slightly nicer if the summary could be derived from the value dictionary
        but this is quite tricky to do right."""
        name = self.objNameWdg.get()
        pos1 = self.objPos1.dataWdg.getString()
        pos2 = self.objPos2.dataWdg.getString()
        csys = self.userModel.coordSysName.get()
        return (name, pos1, pos2, csys)

    def neatenDisplay(self):
        self.objPos1.dataWdg.neatenDisplay()
        self.objPos2.dataWdg.neatenDisplay()

    def setAzAltAirmass(self, *args, **kargs):
        #       print "ObjPosWdg.setAzAltAirmass"
        self._azAltRefreshTimer.cancel()

        target = self.userModel.potentialTarget.get()
        if target is None:
            self.azWdg.set(None)
            self.altWdg.set(None)
            self.airmassWdg.set(None)
            return

        azalt = target.getAzAlt()
        if azalt is None:
            self.azWdg.set(None)
            self.altWdg.set(None)
            self.airmassWdg.set(None)
            return

        az, alt = azalt
        airmass = RO.Astro.Sph.airmass(alt)
        altData, limCurrent = self.tccModel.altLim.get()
        altSeverity = RO.Constants.sevNormal
        minAlt = altData[0]
        if minAlt is not None:
            if alt < minAlt:
                altSeverity = RO.Constants.sevError

        self.azWdg.set(az)
        self.altWdg.set(alt, severity=altSeverity)
        self.airmassWdg.set(airmass)
        self._azAltRefreshTimer.start(_AzAltRefreshDelay, self.setAzAltAirmass)
Ejemplo n.º 10
0
class UsersWdg(Tkinter.Frame):
    """Display the current users and those recently logged out.
    
    Inputs:
    - master    parent widget
    - retainSec time to retain information about logged out users (sec)
    - height    default height of text widget
    - width default width of text widget
    - other keyword arguments are used for the frame
    """
    def __init__(self,
                 master=None,
                 retainSec=300,
                 height=10,
                 width=50,
                 **kargs):
        Tkinter.Frame.__init__(self, master, **kargs)

        hubModel = TUI.Models.HubModel.getModel()
        self.tuiModel = TUI.TUIModel.getModel()

        # entries are commanders (prog.user)
        self._cmdrList = []
        # entries are (cmdr, time deleted); time is from time.time()
        self._delCmdrTimeList = []
        # time to show deleted users
        self._retainSec = retainSec

        # dictionary of user name: User object
        self.userDict = dict()

        self._updateTimer = Timer()

        self.yscroll = Tkinter.Scrollbar(
            master=self,
            orient="vertical",
        )
        self.text = Tkinter.Text(
            master=self,
            yscrollcommand=self.yscroll.set,
            wrap="none",
            tabs="1.6c 5.0c 6.7c 8.5c",
            height=height,
            width=width,
        )
        self.yscroll.configure(command=self.text.yview)
        self.text.grid(row=0, column=0, sticky="nsew")
        self.yscroll.grid(row=0, column=1, sticky="ns")
        RO.Wdg.Bindings.makeReadOnly(self.text)
        RO.Wdg.addCtxMenu(
            wdg=self.text,
            helpURL=_HelpPage,
        )

        self.rowconfigure(0, weight=1)
        self.columnconfigure(0, weight=1)

        self.text.tag_configure("del", overstrike=True)
        self.text.tag_configure("me", underline=True)

        hubModel.user.addCallback(self.updUser, callNow=False)
        hubModel.users.addCallback(self.updUsers)

    def scheduleUpdate(self, afterSec=1.0):
        """Schedule a new update
        """
        self._updateTimer.start(afterSec, self.updDisplay)

    def updDisplay(self):
        """Display current data.
        """
        self._updateTimer.cancel()

        myCmdr = self.tuiModel.getCmdr()
        maxDisplayTime = time.time() - self._retainSec

        self.text.delete("1.0", "end")
        doScheduleUpdate = False
        deleteCmdrList = []
        for cmdr in sorted(self.userDict.keys()):
            userObj = self.userDict[cmdr]
            if userObj.clientName == "monitor":
                continue
            if userObj.isConnected:
                tagList = ["curr"]
            elif userObj.disconnTime < maxDisplayTime:
                deleteCmdrList.append(cmdr)
                continue
            else:
                tagList = ["del"]
                doScheduleUpdate = True
            if cmdr == myCmdr:
                tagList.append("me")
            displayStr = "%s\t%s\t%s\t%s\t%s\n" % \
                (userObj.prog, userObj.user, userObj.clientName, userObj.clientVersion, userObj.systemInfo)
            self.text.insert("end", displayStr, " ".join(tagList))

        for cmdr in deleteCmdrList:
            del (self.userDict[cmdr])

        if doScheduleUpdate:
            self.scheduleUpdate()

    def updUser(self, userInfo, isCurrent, keyVar=None):
        """User keyword callback; add user data to self.userDict"""
        if (not isCurrent) or (userInfo == None):
            return

        cmdr = userInfo[0]
        oldUserObj = self.userDict.get(cmdr, None)
        if oldUserObj:
            oldUserObj.setUserInfo(userInfo)
        else:
            self.userDict[cmdr] = User(cmdr, userInfo)
        self.scheduleUpdate()

    def updUsers(self, newCmdrList, isCurrent=True, keyVar=None):
        """Users keyword callback. The value is a list of commander IDs.
        """
        if not isCurrent:
            # set background to notCurrent?
            return

        for cmdr in newCmdrList:
            userObj = self.userDict.get(cmdr, None)
            if userObj:
                if not userObj.isConnected:
                    userObj.setConnected()
            else:
                self.userDict[cmdr] = User(cmdr)

        # handle disconnected users (those in my userDict that aren't in newCmdrList)
        # handle timeout and final deletion in updDisplay, since it has to remove
        # stale entries even if the users keyword hasn't changed.
        disconnCmdrSet = set(self.userDict.keys()) - set(newCmdrList)
        for cmdr in disconnCmdrSet:
            userObj = self.userDict[cmdr]
            if userObj.isConnected:
                userObj.setDisconnected()

        self.updDisplay()
Ejemplo n.º 11
0
class FTPLogWdg(tkinter.Frame):
    """A widget to initiate file get via ftp, to display the status
    of the transfer and to allow users to abort the transfer.
    
    Inputs:
    - master: master widget
    - maxTransfers: maximum number of simultaneous transfers; additional transfers are queued
    - maxLines: the maximum number of lines to display in the log window. Extra lines are removed (unless a queued or running transfer would be removed).
    - helpURL: the URL of a help page; it may include anchors for:
      - "LogDisplay" for the log display area
      - "From" for the From field of the details display
      - "To" for the To field of the details display
      - "State" for the State field of the details display
      - "Abort" for the abort button in the details display
    """
    def __init__(self,
        master,
        maxTransfers = 1,
        maxLines = 500,
        helpURL = None,
    **kargs):
        tkinter.Frame.__init__(self, master = master, **kargs)
        self._memDebugDict = {}
        
        self.maxLines = maxLines
        self.maxTransfers = maxTransfers
        self.selFTPGet = None # selected getter, for displaying details; None if none
        
        self.dispList = []  # list of displayed ftpGets
        self.getQueue = []  # list of unfinished (ftpGet, stateLabel, ftpCallback) triples

        self._timer = Timer()
        
        self.yscroll = tkinter.Scrollbar (
            master = self,
            orient = "vertical",
        )
        self.text = tkinter.Text (
            master = self,
            yscrollcommand = self.yscroll.set,
            wrap = "none",
            tabs = (8,),
            height = 4,
            width = 50,
        )
        self.yscroll.configure(command=self.text.yview)
        self.text.grid(row=0, column=0, sticky="nsew")
        self.yscroll.grid(row=0, column=1, sticky="ns")
        Bindings.makeReadOnly(self.text)
        if helpURL:
            CtxMenu.addCtxMenu(
                wdg = self.text,
                helpURL = helpURL + "#LogDisplay",
            )
        
        self.rowconfigure(0, weight=1)
        self.columnconfigure(0, weight=1)
        
        detFrame = tkinter.Frame(self)
            
        gr = RO.Wdg.Gridder(detFrame, sticky="ew")
        
        self.fromWdg = RO.Wdg.StrEntry(
            master = detFrame,
            readOnly = True,
            helpURL = helpURL and helpURL + "#From",
            borderwidth = 0,
        )
        gr.gridWdg("From", self.fromWdg, colSpan=3)

        self.toWdg = RO.Wdg.StrEntry(
            master = detFrame,
            readOnly = True,
            helpURL = helpURL and helpURL + "#To",
            borderwidth = 0,
        )
        gr.gridWdg("To", self.toWdg, colSpan=2)
        
        self.stateWdg = RO.Wdg.StrEntry(
            master = detFrame,
            readOnly = True,
            helpURL = helpURL and helpURL + "#State",
            borderwidth = 0,
        )
        self.abortWdg = RO.Wdg.Button(
            master = detFrame,
            text = "Abort",
            command = self._abort,
            helpURL = helpURL and helpURL + "#Abort",
        )
        gr.gridWdg("State", self.stateWdg, colSpan=2)
        
        self.abortWdg.grid(row=1, column=2, rowspan=2, sticky="s")
        
        detFrame.columnconfigure(1, weight=1)
        
        detFrame.grid(row=1, column=0, columnspan=2, sticky="ew")
        
        self.text.bind("<ButtonPress-1>", self._selectEvt)
        self.text.bind("<B1-Motion>", self._selectEvt)
        
        self._updAllStatus()
        
        atexit.register(self._abortAll)

    def getFile(self,
        host,
        fromPath,
        toPath,
        isBinary = True,
        overwrite = False,
        createDir = True,
        callFunc = None,
        dispStr = None,
        username = None,
        password = None,
    ):
        """Get a file
    
        Inputs:
        - host  IP address of ftp host
        - fromPath  full path of file on host to retrieve
        - toPath    full path of destination file
        - isBinary  file is binary? (if False, EOL translation is probably performed)
        - overwrite: if True, overwrites the destination file if it exists;
            otherwise raises ValueError
        - createDir: if True, creates any required directories;
            otherwise raises ValueError
        - callFunc: called whenever more data is read or the state changes;
            receives one argument: an RO.Comm.FTPGet.FTPGet object.
        - dispStr   a string to display while downloading the file;
                    if omitted, an ftp URL (with no username/password) is created
        - username  the usual; *NOT SECURE*
        - password  the usual; *NOT SECURE*
        """
#       print "getFile(%r, %r, %r)" % (host, fromPath, toPath)
        stateLabel = RO.Wdg.StrLabel(self, anchor="w", width=FTPGet.StateStrMaxLen)
        
        ftpGet = FTPGet(
            host = host,
            fromPath = fromPath,
            toPath = toPath,
            isBinary = isBinary,
            overwrite = overwrite,
            createDir = createDir,
            startNow = False,
            dispStr = dispStr,
            username = username,
            password = password,
        )
        self._trackMem(ftpGet, "ftpGet(%s)" % (fromPath,))

        # display item and append to list
        # (in that order so we can test for an empty list before displaying)
        if self.dispList:
            # at least one item is shown
            self.text.insert("end", "\n")
            doAutoSelect = self.selFTPGet in (self.dispList[-1], None)
        else:
            doAutoSelect = True
        self.text.window_create("end", window=stateLabel)
        self.text.insert("end", ftpGet.dispStr)
        self.dispList.append(ftpGet)
        
        self._timer.cancel()

        # append ftpGet to the queue
        ftpCallback = FTPCallback(ftpGet, callFunc)
        self.getQueue.append((ftpGet, stateLabel, ftpCallback))
        
        # purge old display items if necessary
        ind = 0
        selInd = None
        while max(self.maxLines, ind) < len(self.dispList):
            #print "FTPLogWdg.getFile: maxLines=%s, ind=%s, nEntries=%s" % (self.maxLines, ind, len(self.dispList),)

            # only erase entries for files that are finished
            if not self.dispList[ind].isDone:
                #print "FTPLogWdg.getFile: file at ind=%s is not done" % (ind,)
                ind += 1
                continue
            #print "FTPLogWdg.getFile: purging entry at ind=%s" % (ind,)
            
            if (not doAutoSelect) and (self.selFTPGet == self.dispList[ind]):
                selInd = ind
                #print "FTPLogWdg.getFile: purging currently selected file; saving index"

            del(self.dispList[ind])
            self.text.delete("%d.0" % (ind+1,), "%d.0" % (ind+2,))

        # if one of the purged items was selected,
        # select the next down extant item
        # auto scroll
        if doAutoSelect:
            self._selectInd(-1)
            self.text.see("end")
        elif selInd is not None:
            self._selectInd(selInd)
        
        #print "dispList=", self.dispList
        #print "getQueue=", self.getQueue

    
    def _abort(self):
        """Abort the currently selected transaction (if any).
        """
        if self.selFTPGet:
            self.selFTPGet.abort()
    
    def _abortAll(self):
        """Abort all transactions (for use at exit).
        """
        for ftpGet, stateLabel, callFunc in self.getQueue:
            if not ftpGet.isDone:
                ftpGet.abort()

    def _selectEvt(self, evt):
        """Determine the line currently pointed to by the mouse
        and show details for that transaction.
        Intended to handle the mouseDown event.
        """
        self.text.tag_remove("sel", "1.0", "end")
        x, y = evt.x, evt.y
        mousePosStr = "@%d,%d" % (x, y)
        indStr = self.text.index(mousePosStr)
        ind = int(indStr.split(".")[0]) - 1
        self._selectInd(ind)
        return "break"
    
    def _selectInd(self, ind):
        """Display details for the ftpGet at self.dispList[ind]
        and selects the associated line in the displayed list.
        If ind is None then displays no info and deselects all.
        """
        self.text.tag_remove('sel', '1.0', 'end')
        try:
            self.selFTPGet = self.dispList[ind]
            if ind < 0:
                lineNum = len(self.dispList) + 1 + ind
            else:
                lineNum = ind + 1
            self.text.tag_add('sel', '%s.0' % lineNum, '%s.0 lineend' % lineNum)
        except IndexError:
            self.selFTPGet = None
        self._updDetailStatus()

    def _trackMem(self, obj, objName):
        """Print a message when an object is deleted.
        """
        if not _DebugMem:
            return
        objID = id(obj)
        def refGone(ref=None, objID=objID, objName=objName):
            print("%s deleting %s" % (self.__class__.__name__, objName,))
            del(self._memDebugDict[objID])

        self._memDebugDict[objID] = weakref.ref(obj, refGone)
        del(obj)
    
    def _updAllStatus(self):
        """Update status for running transfers
        and start new transfers if there is room
        """
        newGetQueue = list()
        nRunning = 0
        for ftpGet, stateLabel, ftpCallback in self.getQueue:
            if ftpGet.isDone:
                ftpCallback()
            else:
                newGetQueue.append((ftpGet, stateLabel, ftpCallback))
                state = ftpGet.state
                if state == ftpGet.Queued:
                    if nRunning < self.maxTransfers:
                        ftpGet.start()
                        nRunning += 1
                        ftpCallback()
                elif state in (ftpGet.Running, ftpGet.Connecting):
                    nRunning += 1
            self._updOneStatus(ftpGet, stateLabel)
        self.getQueue = newGetQueue
    
        self._updDetailStatus()
         
        self._timer.start(_StatusInterval, self._updAllStatus)
        
    def _updOneStatus(self, ftpGet, stateLabel):
        """Update the status of one transfer"""
        state = ftpGet.state
        if state == ftpGet.Running:
            if ftpGet.totBytes:
                pctDone = int(round(100 * ftpGet.readBytes / float(ftpGet.totBytes)))
                stateLabel["text"] = "%3d %%" % pctDone
            else:
                kbRead = ftpGet.readBytes / 1024
                stateLabel["text"] = "%d kB" % kbRead
        else:
            # display state
            if state == ftpGet.Failed:
                severity = RO.Constants.sevError
            else:
                severity = RO.Constants.sevNormal
            stateLabel.set(state, severity=severity)
    
    def _updDetailStatus(self):
        """Update the detail status for self.selFTPGet"""
        if not self.selFTPGet:
            self.fromWdg.set("")
            self.toWdg.set("")
            self.stateWdg.set("")
            if self.abortWdg.winfo_ismapped():
                self.abortWdg.grid_remove()
            return

        ftpGet = self.selFTPGet
        currState = ftpGet.state
        
        # show or hide abort button, appropriately
        if ftpGet.isAbortable:
            if not self.abortWdg.winfo_ismapped():
                self.abortWdg.grid()
        else:
            if self.abortWdg.winfo_ismapped():
                self.abortWdg.grid_remove()

        stateStr = currState
        severity = RO.Constants.sevNormal
        if currState == ftpGet.Running:
            if ftpGet.totBytes:
                stateStr = "read %s of %s bytes" % (ftpGet.readBytes, ftpGet.totBytes)
            else:
                stateStr = "read %s bytes" % (ftpGet.readBytes,)
        else:
            if currState == ftpGet.Failed:
                stateStr = "Failed: %s" % (ftpGet.getException())
                severity = RO.Constants.sevError
            elif currState in (ftpGet.Aborting, ftpGet.Aborted):
                severity = RO.Constants.sevWarning

        self.stateWdg.set(stateStr, severity=severity)
        self.fromWdg.set(ftpGet.dispStr)
        self.toWdg.set(ftpGet.toPath)
Ejemplo n.º 12
0
class StatusConfigInputWdg(RO.Wdg.InputContFrame):
    InstName = "GIFS"
    HelpPrefix = HelpURL + "#"

    # category names
    ConfigCat = RO.Wdg.StatusConfigGridder.ConfigCat
    EnvironCat = 'environ'

    def __init__(self, master, stateTracker, **kargs):
        """Create a new widget to show status for and configure GIFS

        Inputs:
        - master: parent widget
        - stateTracker: an RO.Wdg.StateTracker
        """
        RO.Wdg.InputContFrame.__init__(self,
                                       master=master,
                                       stateTracker=stateTracker,
                                       **kargs)
        self.model = GIFSModel.getModel()
        self.tuiModel = TUI.TUIModel.getModel()
        self.updateStdPresetsTimer = Timer()

        self.gridder = RO.Wdg.StatusConfigGridder(
            master=self,
            sticky="w",
            numStatusCols=3,
        )

        blankLabel = Tkinter.Label(self, width=_DataWidth)
        blankLabel.grid(row=0, column=1, columnspan=2)

        self.magnifier = StageControls(
            gridder=self.gridder,
            label="Magnifier",
            configKey=self.model.magnifierConfig,
            statusKey=self.model.magnifierStatus,
        )

        self.lenslets = StageControls(gridder=self.gridder,
                                      label="Lenslets",
                                      configKey=self.model.lensletsConfig,
                                      statusKey=self.model.lensletsStatus,
                                      descr="lenslet array")

        self.calMirror = CalMirrorControls(
            gridder=self.gridder,
            statusKey=self.model.calMirrorStatus,
        )

        self.filter = FilterControls(
            gridder=self.gridder,
            label="Filter Wheel",
            configKey=self.model.filterNames,
            statusKey=self.model.filterStatus,
        )

        self.collimator = StageControls(
            gridder=self.gridder,
            label="Collimator",
            configKey=self.model.collimatorConfig,
            statusKey=self.model.collimatorStatus,
            showOther=True,
        )

        self.disperser = StageControls(
            gridder=self.gridder,
            label="Disperser",
            configKey=self.model.disperserConfig,
            statusKey=self.model.disperserStatus,
        )

        self.ccdTempWdg = RO.Wdg.FloatLabel(master=self,
                                            formatStr="%0.1f K",
                                            helpText="CCD temperature (K)")
        self.heaterPowerWdg = RO.Wdg.FloatLabel(
            master=self,
            formatStr="%0.1f %%",
            helpText="CCD heater power (%)",
        )
        self.gridder.gridWdg(
            label="CCD Temp",
            dataWdg=(self.ccdTempWdg, self.heaterPowerWdg),
        )

        self.model.ccdTemp.addROWdg(self.ccdTempWdg)
        self.model.heaterPower.addROWdg(self.heaterPowerWdg)

        moveFmtFunc = RO.InputCont.BasicFmt(nameSep=" move=", )

        # set up the input container set
        self.inputCont = RO.InputCont.ContList(conts=[
            RO.InputCont.WdgCont(
                name="calmirror",
                wdgs=self.calMirror.userWdg,
                formatFunc=RO.InputCont.BasicFmt(nameSep=" "),
            ),
            RO.InputCont.WdgCont(
                name="collimator",
                wdgs=self.collimator.userWdg,
                formatFunc=moveFmtFunc,
            ),
            RO.InputCont.WdgCont(
                name="disperser",
                wdgs=self.disperser.userWdg,
                formatFunc=moveFmtFunc,
            ),
            RO.InputCont.WdgCont(
                name="filter",
                wdgs=self.filter.userWdg,
                formatFunc=moveFmtFunc,
            ),
            RO.InputCont.WdgCont(
                name="lenslets",
                wdgs=self.lenslets.userWdg,
                formatFunc=moveFmtFunc,
            ),
            RO.InputCont.WdgCont(
                name="magnifier",
                wdgs=self.magnifier.userWdg,
                formatFunc=moveFmtFunc,
            ),
        ], )

        self._inputContNameKeyVarDict = dict(
            calmirror=self.model.calMirrorPresets,
            collimator=self.model.collimatorPresets,
            disperser=self.model.disperserPresets,
            filter=self.model.filterPresets,
            lenslets=self.model.lensletPresets,
            magnifier=self.model.magnifierPresets,
        )

        self.presetsWdg = RO.Wdg.InputContPresetsWdg(
            master=self,
            sysName="%sConfig" % (self.InstName, ),
            userPresetsDict=self.tuiModel.userPresetsDict,
            inputCont=self.inputCont,
            helpText="use and manage named presets",
            helpURL=self.HelpPrefix + "Presets",
        )
        self.gridder.gridWdg(
            "Presets",
            cfgWdg=self.presetsWdg,
        )

        self.gridder.allGridded()

        # for presets data use a timer to increase the chance that all keywords have been seen
        # before the method is called
        def callUpdPresets(*args, **kwargs):
            self.updateStdPresetsTimer.start(0.1, self.updateStdPresets)

        for keyVar in self._inputContNameKeyVarDict.itervalues():
            keyVar.addCallback(callUpdPresets)

        def repaint(evt):
            self.restoreDefault()

        self.bind("<Map>", repaint)

    def updateStdPresets(self):
        """Update standard presets, if the data is available
        """
        self.updateStdPresetsTimer.cancel()
        nameList = self.model.namePresets.get()[0]
        if None in nameList:
            return
        numPresets = len(nameList)
        dataDict = dict()
        for inputContName, keyVar in self._inputContNameKeyVarDict.iteritems():
            valList = keyVar.get()[0]
            if len(valList) != numPresets or None in valList:
                return
            dataDict[inputContName] = valList

        # stdPresets is a dict of name: dict of input container name: value
        stdPresets = dict()
        for i, name in enumerate(nameList):
            contDict = dict()
            for inputContName, valList in dataDict.iteritems():
                if not valList[i]:
                    continue
                contDict[inputContName] = valList[i]
            if contDict:
                stdPresets[name] = contDict
        self.presetsWdg.setStdPresets(stdPresets)
Ejemplo n.º 13
0
class StatusConfigInputWdg (RO.Wdg.InputContFrame):
    InstName = "GIFS"
    HelpPrefix = HelpURL + "#"

    # category names
    ConfigCat = RO.Wdg.StatusConfigGridder.ConfigCat
    EnvironCat = 'environ'

    def __init__(self,
        master,
        stateTracker,
    **kargs):
        """Create a new widget to show status for and configure GIFS

        Inputs:
        - master: parent widget
        - stateTracker: an RO.Wdg.StateTracker
        """
        RO.Wdg.InputContFrame.__init__(self, master=master, stateTracker=stateTracker, **kargs)
        self.model = GIFSModel.getModel()
        self.tuiModel = TUI.TUIModel.getModel()
        self.updateStdPresetsTimer = Timer()
        
        self.gridder = RO.Wdg.StatusConfigGridder(
            master = self,
            sticky = "w",
            numStatusCols = 3,
        )

        blankLabel = Tkinter.Label(self, width=_DataWidth)
        blankLabel.grid(row=0, column=1, columnspan=2)

        self.magnifier = StageControls(
            gridder = self.gridder,
            label = "Magnifier",
            configKey = self.model.magnifierConfig,
            statusKey = self.model.magnifierStatus,
        )
    
        self.lenslets = StageControls(
            gridder = self.gridder,
            label = "Lenslets",
            configKey = self.model.lensletsConfig,
            statusKey = self.model.lensletsStatus,
            descr = "lenslet array"
        )

        self.calMirror = CalMirrorControls(
            gridder = self.gridder,
            statusKey = self.model.calMirrorStatus,
        )

        self.filter = FilterControls(
            gridder = self.gridder,
            label = "Filter Wheel",
            configKey = self.model.filterNames,
            statusKey = self.model.filterStatus,
        )

        self.collimator = StageControls(
            gridder = self.gridder,
            label = "Collimator",
            configKey = self.model.collimatorConfig,
            statusKey = self.model.collimatorStatus,
            showOther = True,
        )

        self.disperser = StageControls(
            gridder = self.gridder,
            label = "Disperser",
            configKey = self.model.disperserConfig,
            statusKey = self.model.disperserStatus,
        )

        self.ccdTempWdg = RO.Wdg.FloatLabel(
            master = self,
            formatStr = "%0.1f K",
            helpText = "CCD temperature (K)"
        )
        self.heaterPowerWdg = RO.Wdg.FloatLabel(
            master = self,
            formatStr = "%0.1f %%",
            helpText = "CCD heater power (%)",
        )
        self.gridder.gridWdg(
            label = "CCD Temp",
            dataWdg = (self.ccdTempWdg, self.heaterPowerWdg),
        )

        self.model.ccdTemp.addROWdg(self.ccdTempWdg)
        self.model.heaterPower.addROWdg(self.heaterPowerWdg)

        moveFmtFunc = RO.InputCont.BasicFmt(
            nameSep = " move=",
        )

        # set up the input container set
        self.inputCont = RO.InputCont.ContList (
            conts = [
                RO.InputCont.WdgCont (
                    name = "calmirror",
                    wdgs = self.calMirror.userWdg,
                    formatFunc = RO.InputCont.BasicFmt(nameSep=" "),
                ),
                RO.InputCont.WdgCont (
                    name = "collimator",
                    wdgs = self.collimator.userWdg,
                    formatFunc = moveFmtFunc,
                ),
                RO.InputCont.WdgCont (
                    name = "disperser",
                    wdgs = self.disperser.userWdg,
                    formatFunc = moveFmtFunc,
                ),
                RO.InputCont.WdgCont (
                    name = "filter",
                    wdgs = self.filter.userWdg,
                    formatFunc = moveFmtFunc,
                ),
                RO.InputCont.WdgCont (
                    name = "lenslets",
                    wdgs = self.lenslets.userWdg,
                    formatFunc = moveFmtFunc,
                ),
                RO.InputCont.WdgCont (
                    name = "magnifier",
                    wdgs = self.magnifier.userWdg,
                    formatFunc = moveFmtFunc,
                ),
            ],
        )

        self._inputContNameKeyVarDict = dict(
            calmirror = self.model.calMirrorPresets,
            collimator = self.model.collimatorPresets,
            disperser = self.model.disperserPresets,
            filter = self.model.filterPresets,
            lenslets = self.model.lensletPresets,
            magnifier = self.model.magnifierPresets,
        )
        
        self.presetsWdg = RO.Wdg.InputContPresetsWdg(
            master = self,
            sysName = "%sConfig" % (self.InstName,),
            userPresetsDict = self.tuiModel.userPresetsDict,
            inputCont = self.inputCont,
            helpText = "use and manage named presets",
            helpURL = self.HelpPrefix + "Presets",
        )
        self.gridder.gridWdg(
            "Presets",
            cfgWdg = self.presetsWdg,
        )

        self.gridder.allGridded()

        # for presets data use a timer to increase the chance that all keywords have been seen
        # before the method is called
        def callUpdPresets(*args, **kwargs):
            self.updateStdPresetsTimer.start(0.1, self.updateStdPresets)
        for keyVar in self._inputContNameKeyVarDict.itervalues():
            keyVar.addCallback(callUpdPresets)
        
        def repaint(evt):
            self.restoreDefault()
        self.bind("<Map>", repaint)

    def updateStdPresets(self):
        """Update standard presets, if the data is available
        """
        self.updateStdPresetsTimer.cancel()
        nameList = self.model.namePresets.get()[0]
        if None in nameList:
            return
        numPresets = len(nameList)
        dataDict = dict()
        for inputContName, keyVar in self._inputContNameKeyVarDict.iteritems():
            valList = keyVar.get()[0]
            if len(valList) != numPresets or None in valList:
                return
            dataDict[inputContName] = valList

        # stdPresets is a dict of name: dict of input container name: value
        stdPresets = dict()
        for i, name in enumerate(nameList):
            contDict = dict()
            for inputContName, valList in dataDict.iteritems():
                if not valList[i]:
                    continue
                contDict[inputContName] = valList[i]
            if contDict:
                stdPresets[name] = contDict
        self.presetsWdg.setStdPresets(stdPresets)
Ejemplo n.º 14
0
class TestGuiderWdg(Tkinter.Frame):
    def __init__(self, testDispatcher, master):
        Tkinter.Frame.__init__(self, master)
        self.testDispatcher = testDispatcher

        random.seed(0)
   
        self.tuiModel = self.testDispatcher.tuiModel
        self.pollTimer = Timer()
        self.oldPendingCmd = None
        self.fileNum = 0

        gr = RO.Wdg.Gridder(self, sticky="ew")
    
        self.guideWdg = AgileGuideWindow.AgileGuideWdg(self)
        gr.gridWdg(False, self.guideWdg, colSpan=10)
        
        self.imageAvailWdg = RO.Wdg.Button(
            master = self,
            text = "Image is Available",
            callFunc = self.dispatchFileData,
        )
        gr.gridWdg(None, self.imageAvailWdg)
        
        self.starPosWdgSet = []
        for ii in range(2):
            letter = ("X", "Y")[ii]
            starPosWdg = RO.Wdg.FloatEntry(
                master = self,
                label = "Star Pos %s" % (letter,),
                minValue = 0,
                defValue = 100 * (ii + 1),
                maxValue = 5000,
                autoIsCurrent = True,
                autoSetDefault = True,
                helpText = "Star %s position in binned pixels" % (letter,),
            )
            self.starPosWdgSet.append(starPosWdg)
        gr.gridWdg("Star Pos", self.starPosWdgSet, "pix")

        self.centroidRadWdg = RO.Wdg.IntEntry(
            master = self,
            label = "Centroid Rad",
            minValue = 5,
            maxValue = 1024,
            defValue = 10,
            defMenu = "Default",
            autoIsCurrent = True,
            autoSetDefault = True,
            helpText = "Radius of region to centroid in binned pixels; don't skimp",
        )
        gr.gridWdg(self.centroidRadWdg.label, self.centroidRadWdg, "arcsec", sticky="ew")

        self.numToFindWdg = RO.Wdg.IntEntry(
            master = self,
            label = "Num To Find",
            minValue = 0,
            maxValue = 100,
            defValue = 5,
            defMenu = "Default",
            autoIsCurrent = True,
            autoSetDefault = True,
            helpText = "Number of stars to find (0 for findstars to fail)",
        )
        gr.gridWdg(self.numToFindWdg.label, self.numToFindWdg)
        
        self.centroidOKWdg = RO.Wdg.Checkbutton(
            master = self,
            text = "Centroid OK",
            defValue = True,
            helpText = "Should centroid command succeed?",
        )
        gr.gridWdg(None, self.centroidOKWdg)
        
        self.offsetOKWdg = RO.Wdg.Checkbutton(
            master = self,
            text = "Offset OK",
            defValue = True,
            helpText = "Should offset command succeed?",
        )
        gr.gridWdg(None, self.offsetOKWdg)
        
        self.axesTrackingWdg = RO.Wdg.Checkbutton(
            master = self,
            text = "Axes Tracking",
            defValue = True,
            callFunc = self.axesTrackingCallback,
            helpText = "Are axes tracking?",
        )
        gr.gridWdg(None, self.axesTrackingWdg)
        
        self.isInstAgileWdg = RO.Wdg.Checkbutton(
            master = self,
            text = "Is Curr Inst Agile?",
            defValue = True,
            callFunc = self.isInstAgileCallback,
            helpText = "Is the current instrument Agile?",
        )
        gr.gridWdg(None, self.isInstAgileWdg)

        self.useWrongCmdrWdg = RO.Wdg.Checkbutton(
            master = self,
            text = "Use Wrong Cmdr",
            defValue = False,
            helpText = "Should replies be for a different cmdr?",
        )
        gr.gridWdg(None, self.useWrongCmdrWdg)

        self.useWrongCmdIDWdg = RO.Wdg.Checkbutton(
            master = self,
            text = "Use Wrong Cmd ID",
            defValue = False,
            helpText = "Should replies be for a different command?",
        )
        gr.gridWdg(None, self.useWrongCmdIDWdg)

        self.useWrongActorWdg = RO.Wdg.Checkbutton(
            master = self,
            text = "Use Wrong Actor",
            defValue = False,
            helpText = "Should replies be for a different actor?",
        )
        gr.gridWdg(None, self.useWrongActorWdg)
        
        self.grid_columnconfigure(9, weight=1)
        
        tccData = (
            "inst=Agile",
            "iimScale=-27784.4, 27569.0",
            "axisCmdState=Tracking, Tracking, Tracking",
        )
        self.testDispatcher.dispatch(tccData, actor="tcc")
        self.testDispatcher.dispatch("bin=1", actor="agile")
        
        self.pollPendingCmd()

    def axesTrackingCallback(self, wdg=None):
        if self.axesTrackingWdg.getBool():
            tccData = "axisCmdState=Tracking, Tracking, Tracking"
        else:
            tccData = "axisCmdState=Tracking, Halted, Tracking"
        self.testDispatcher.dispatch(tccData, actor="tcc")

    def isInstAgileCallback(self, wdg=None):
        if self.isInstAgileWdg.getBool():
            tccData = "inst=Agile"
        else:
            tccData = "inst=SPICam"
        self.testDispatcher.dispatch(tccData, actor="tcc")
        
    def dispatchFileData(self, wdg=None):
        keyArgs = self.getDispatchKeyArgs("agileExpose", cmdID=0)
        fileName = "image%d" % (self.fileNum,)
        self.fileNum += 1
        filesKeyword = makeFilesKeyword(cmdr=keyArgs["cmdr"], fileName=fileName)
        self.testDispatcher.dispatch(filesKeyword, msgCode=":", **keyArgs)

    def dispatchFindData(self, wdg=None):
        keyArgs = self.getDispatchKeyArgs("afocus")

        numToFind = self.numToFindWdg.getNum()

        if numToFind < 1:
            self.testDispatcher.dispatch("text='No stars found'", msgCode="f", **keyArgs)
            return

        mainXYPos = [wdg.getNum() for wdg in self.starPosWdgSet]
        centroidRad = self.centroidRadWdg.getNum()
        findData = makeFindData(numFound=numToFind, mainXYPos=mainXYPos, centroidRad=centroidRad)
        self.testDispatcher.dispatch(findData, msgCode="i", **keyArgs)
        self.testDispatcher.dispatch("", msgCode=":", **keyArgs)

    def dispatchCentroidData(self, wdg=None):
        keyArgs = self.getDispatchKeyArgs("afocus")

        if not self.centroidOKWdg.getBool():
            self.testDispatcher.dispatch("text='No stars found'", msgCode="f", **keyArgs)
            return
        
        xyPos = [wdg.getNum() for wdg in self.starPosWdgSet]
        centroidRad = self.centroidRadWdg.getNum()
        centroidData = makeStarKeyword(isFind=False, xyPos=xyPos, randRange=10, centroidRad=centroidRad)
        self.testDispatcher.dispatch(centroidData, msgCode=":", **keyArgs)

    def getDispatchKeyArgs(self, actor, cmdID=None):
        """Get keyword arguments for the test dispatcher's dispatch command
        """
        if self.useWrongCmdrWdg.getBool():
            cmdr = "APO.other"
        else:
            cmdr = self.tuiModel.getCmdr()

        if cmdID == None:
            if self.guideWdg.pendingCmd:
                cmdID = self.guideWdg.pendingCmd.cmdID or 0
            else:
                cmdID = 0

        if self.useWrongCmdIDWdg.getBool():
            cmdID += 1000
        
        if self.useWrongActorWdg.getBool():
            actor = "other"
        else:
            actor = actor

        return dict(cmdr=cmdr, cmdID=cmdID, actor=actor)

    def pollPendingCmd(self):
        """Poll to see if there's a new pending command and respond accordingly
        """
        self.pollTimer.cancel()

        if self.guideWdg.pendingCmd != self.oldPendingCmd:
            self.oldPendingCmd = self.guideWdg.pendingCmd
            if not self.oldPendingCmd.isDone():
                self.replyToCommand()

        self.pollTimer.start(1.0, self.pollPendingCmd)

    def replyToCommand(self):
        """Issue the appropriate replly to a pending command
        """
#         print "replyToCommand", self.oldPendingCmd
        actor = self.oldPendingCmd.actor.lower()
        cmdStr = self.oldPendingCmd.cmdStr
        cmdID = self.oldPendingCmd.cmdID

        keyArgs = self.getDispatchKeyArgs(actor)
        
        if actor == "tcc":
            if self.offsetOKWdg.getBool():
                self.testDispatcher.dispatch("", msgCode=":", **keyArgs)
            else:
                self.testDispatcher.dispatch("text='Offset failed'", msgCode="f", **keyArgs)
        elif actor == "afocus":
            if cmdStr.startswith("centroid"):
                self.dispatchCentroidData()
            elif cmdStr.startswith("find"):
                self.dispatchFindData()
            else:
                print "Unknown afocus command:", cmdStr
        else:
            print "Unknown actor:", actor
Ejemplo n.º 15
0
class _BalloonHelp:
    """Show balloon help for any widget that has a helpText attribute
    
    Help is shown delayMS after the mouse enters a widget or moves within a widget.
    If help was showing within 0.6 sec of moving to a new widget then the help
    for the new widget is shown immediately.
    
    Help is hidden if the user clicks or types. However, the help timer is started again
    if the mouse moves within the widget.
    """
    def __init__(self, delayMS=600):
        """Construct a _BalloonHelp
        
        Inputs:
        - delayMS: delay time before help is shown
        """
        self._isShowing = False
        self._delayMS = delayMS
        self._showTimer = Timer()
        self._leaveTimer = Timer()
        self._msgWin = tkinter.Toplevel()
        self._msgWin.overrideredirect(True)
        self._msgWdg = tkinter.Message(self._msgWin, bg="light yellow")
        self._msgWdg.pack()
        self._msgWin.withdraw()
        self._msgWdg.bind_all('<Motion>', self._start)
        self._msgWdg.bind_all('<Leave>', self._leave)
        self._msgWdg.bind_all('<ButtonPress>', self._stop)
        self._msgWdg.bind_all('<KeyPress>', self._stop)
        self._msgWdg.bind_all('<Tab>', self._stop, add=True)
        self._msgWin.bind("<Configure>", self._configure)

    def _configure(self, evt=None):
        """Callback for window Configure event
        
        Using this flickers less than calling this from show (even using a short time delay).
        Note: using self._isShowing is paranoia; the <Configure> event is only triggered
        by show (which changes the message).
        """
        if self._isShowing:
            self._msgWin.tkraise()
            self._msgWin.deiconify()

    def _leave(self, evt=None):
        """Mouse has left a widget; start the leave timer if help is showing and stop showing help
        """
        if self._isShowing:
            self._leaveTimer.start(0.6, self._leaveDone)
        self._stop()

    def _leaveDone(self):
        """No-op for leave timer; can add a print statement for diagnostics
        """
        pass

    def _start(self, evt):
        """Start a timer to show the help in a bit.
        
        If the help window is already showing, redisplay it immediately
        """
        if self._isShowing:
            return
        self._isShowing = True

        try:
            if evt.widget.helpText and not self._showTimer.isActive:
                # widget has help and the show timer is not already running
                justLeft = self._leaveTimer.cancel()
                if justLeft:
                    # recently left another widget while showing help; show help for this widget right away
                    delay = 0.001
                else:
                    # not recently showing help; wait the usual time to show help
                    delay = self._delayMS / 1000.0
                self._showTimer.start(delay, self._show, evt)
        except AttributeError:
            pass

    def _show(self, evt):
        """Show help
        """
        self._isShowing = True
        x, y = evt.x_root, evt.y_root
        self._msgWin.geometry("+%d+%d" % (x + 10, y + 10))
        self._msgWdg["text"] = evt.widget.helpText

    def _stop(self, evt=None):
        """Stop the timer and hide the help
        """
        self._isShowing = False
        self._showTimer.cancel()
        self._msgWin.withdraw()
Ejemplo n.º 16
0
class DropletRunner(object):
    """Run a script as a droplet (an application onto which you drop file) with a log window.
    
    Data the script writes to sys.stdout and sys.stderr is written to a log window;
    stderr output is shown in red.    

    On Mac OS X additional files may be dropped on the application icon once the first batch is processed.
    I don't know how to support this on other platforms.
    """
    def __init__(self, scriptPath, title=None, initialText=None, **keyArgs):
        """Construct and run a DropletRunner
        
        Inputs:
        - scriptPath: path to script to run when files are dropped on the application
        - title: title for log window; if None then generated from scriptPath
        - initialText: initial text to display in log window
        **keyArgs: all other keyword arguments are sent to the RO.Wdg.LogWdg constructor
        """
        self.isRunning = False
        self.scriptPath = os.path.abspath(scriptPath)
        if not os.path.isfile(scriptPath):
            raise RuntimeError("Cannot find script %r" % (self.scriptPath,))

        self.tkRoot = tkinter.Tk()
        self._timer = Timer()
        
        if title is None:
            title = os.path.splitext(os.path.basename(scriptPath))[0]
        self.tkRoot.title(title)

        if RO.OS.PlatformName == "mac":
            self.tkRoot.createcommand('::tk::mac::OpenDocument', self._macOpenDocument)
            # the second argument is a process ID (approximately) if run as an Applet;
            # the conditional handles operation from the command line
            if len(sys.argv) > 1 and sys.argv[1].startswith("-"):
                filePathList = sys.argv[2:]
            else:
                filePathList = sys.argv[1:]
        else:
            filePathList = sys.argv[1:]

        self.logWdg = LogWdg.LogWdg(self.tkRoot, **keyArgs)
        self.logWdg.grid(row=0, column=0, sticky="nsew")
        self.tkRoot.grid_rowconfigure(0, weight=1)
        self.tkRoot.grid_columnconfigure(0, weight=1)
        
        if initialText:
            self.logWdg.addOutput(initialText)

        if filePathList:
            self.runFiles(filePathList)

        self.tkRoot.mainloop()

    def runFiles(self, filePathList):
        """Run the script with the specified files
        """
#        print "runFiles(filePathList=%s)" % (filePathList,)
        self.isRunning = True
        argList = [sys.executable, self.scriptPath] + list(filePathList)
        self.subProc = subprocess.Popen(argList, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        self.tkRoot.tk.createfilehandler(self.subProc.stderr, tkinter.READABLE, self._readStdErr)
        self.tkRoot.tk.createfilehandler(self.subProc.stdout, tkinter.READABLE, self._readStdOut)
        self._poll()

    def _macOpenDocument(self, *filePathList):
        """Handle Mac OpenDocument event
        """
        self.runFiles(filePathList)

    def _poll(self):
        """Poll for subprocess completion
        """
        if self.subProc.returncode is not None:
            self._cleanup()
        else:
            self._timer(0.1, self._poll)
    
    def _readStdOut(self, *dumArgs):
        """Read and log data from script's stdout
        """
        self.logWdg.addOutput(self.subProc.stdout.read())
        if self.subProc.poll() is not None:
            self._cleanup()

    def _readStdErr(self, *dumArgs):
        """Read and log data from script's stderr
        """
        self.logWdg.addOutput(self.subProc.stderr.read(), severity=RO.Constants.sevError)
        if self.subProc.poll() is not None:
            self._cleanup()

    def _cleanup(self):
        """Close Tk file handlers and print any final data from the subprocess
        """
        self._timer.cancel()
        if self.isRunning:
            self.isRunning = False
            self.tkRoot.tk.deletefilehandler(self.subProc.stdout)
            self.tkRoot.tk.deletefilehandler(self.subProc.stderr)
            outData, errData = self.subProc.communicate()
            if outData:
                self.logWdg.addOutput(outData)
            if errData:
                self.logWdg.addOutput(errData, severity=RO.Constants.sevError)
Ejemplo n.º 17
0
class _BalloonHelp:
    """Show balloon help for any widget that has a helpText attribute
    
    Help is shown delayMS after the mouse enters a widget or moves within a widget.
    If help was showing within 0.6 sec of moving to a new widget then the help
    for the new widget is shown immediately.
    
    Help is hidden if the user clicks or types. However, the help timer is started again
    if the mouse moves within the widget.
    """
    def __init__(self, delayMS = 600):
        """Construct a _BalloonHelp
        
        Inputs:
        - delayMS: delay time before help is shown
        """
        self._isShowing = False
        self._delayMS = delayMS
        self._showTimer = Timer()
        self._leaveTimer = Timer()
        self._msgWin = tkinter.Toplevel()
        self._msgWin.overrideredirect(True)
        self._msgWdg = tkinter.Message(self._msgWin, bg="light yellow")
        self._msgWdg.pack()
        self._msgWin.withdraw()
        self._msgWdg.bind_all('<Motion>', self._start)
        self._msgWdg.bind_all('<Leave>', self._leave)
        self._msgWdg.bind_all('<ButtonPress>', self._stop)
        self._msgWdg.bind_all('<KeyPress>', self._stop)
        self._msgWdg.bind_all('<Tab>', self._stop, add=True)
        self._msgWin.bind("<Configure>", self._configure)
    
    def _configure(self, evt=None):
        """Callback for window Configure event
        
        Using this flickers less than calling this from show (even using a short time delay).
        Note: using self._isShowing is paranoia; the <Configure> event is only triggered
        by show (which changes the message).
        """
        if self._isShowing:
            self._msgWin.tkraise()
            self._msgWin.deiconify()
    
    def _leave(self, evt=None):
        """Mouse has left a widget; start the leave timer if help is showing and stop showing help
        """
        if self._isShowing:
            self._leaveTimer.start(0.6, self._leaveDone)
        self._stop()
    
    def _leaveDone(self):
        """No-op for leave timer; can add a print statement for diagnostics
        """
        pass

    def _start(self, evt):
        """Start a timer to show the help in a bit.
        
        If the help window is already showing, redisplay it immediately
        """
        if self._isShowing:
            return
        self._isShowing = True

        try:
            if evt.widget.helpText and not self._showTimer.isActive:
                # widget has help and the show timer is not already running
                justLeft = self._leaveTimer.cancel()
                if justLeft:
                    # recently left another widget while showing help; show help for this widget right away
                    delay = 0.001
                else:
                    # not recently showing help; wait the usual time to show help
                    delay = self._delayMS / 1000.0
                self._showTimer.start(delay, self._show, evt)
        except AttributeError:
            pass
    
    def _show(self, evt):
        """Show help
        """
        self._isShowing = True
        x, y = evt.x_root, evt.y_root
        self._msgWin.geometry("+%d+%d" % (x+10, y+10))
        self._msgWdg["text"] = evt.widget.helpText
    
    def _stop(self, evt=None):
        """Stop the timer and hide the help
        """
        self._isShowing = False
        self._showTimer.cancel()
        self._msgWin.withdraw()
Ejemplo n.º 18
0
class PrefEditor(object):
    """Basic preferences editor. Works for string, numeric and boolean data
    (PrefVar, StrPrefVar, IntPrefVar, FloatPrefVar, BoolPrefVar).
    """
    def __init__(self,
        prefVar,
        master,
        row = 0,
        column = 0,
    ):
        self.master = master
        self.prefVar = prefVar

        # create and set a variable to contain the edited value
        self.editVar = tkinter.StringVar()
        self.editVar.set(self.prefVar.getValueStr())
        
        # save initial value, in case we have to restore it
        self.initialValue = self.getCurrentValue()
        
        # save last good value, for use if a typed char is rejected
        self.mostRecentValidValue = self.editVar.get()
        
        # a list (in grid order) of (widget name, sticky setting)
        wdgInfo = (
            ("labelWdg", "e"),
            ("changedWdg", ""),
            ("editWdg", "w"),
            ("unitsWdg", "w"),
        )
        self.labelWdg = tkinter.Label(self.master, text = self.prefVar.name)
        self._addCtxMenu(self.labelWdg)
        
        self.changedVar = tkinter.StringVar()
        self.changedWdg = tkinter.Label(self.master, width=1, textvariable=self.changedVar)
        self._addCtxMenu(self.changedWdg)

        self.editWdg = self._getEditWdg()
        # self.rangeWdg = self._getRangeWdg()

        if self.prefVar.units:
            self.unitsWdg = tkinter.Label(self.master, text = self.prefVar.name)
            self._addCtxMenu(self.unitsWdg)
        else:
            self.unitsWdg = None

        # grid the widgets
        for wdgName, sticky in wdgInfo:
            wdg = getattr(self, wdgName)
            if wdg:
                wdg.grid(row=row, column=column, sticky=sticky)
            column += 1
        
        self.timer = Timer()
        
        self._setupCallbacks()
        
    def getCurrentValue(self):
        """Returns the current value of the preference variable
        (not necessarily the value shown in the value editor).
        """
        return self.prefVar.getValueStr()

    def getDefValue(self):
        """Returns the current value of the preference variable
        (not necessarily the value shown in the value editor).
        """
        return self.prefVar.getDefValueStr()

    def getEditValue(self):
        """Returns the value from the editor widget"""
        return self.editVar.get()
    
    def getInitialValue(self):
        return self.initialValue
    
    def setVariable(self):
        """Sets the preference variable to the edit value"""
        self.prefVar.setValue(self.getEditValue())
        self.updateChanged()
        
    def showValue(self, value):
        self.editVar.set(value)
        self.updateEditor()
    
    def showCurrentValue(self):
        self.showValue(self.getCurrentValue())
    
    def showDefaultValue(self):
        self.showValue(self.getDefValue())
    
    def showInitialValue(self):
        self.showValue(self.getInitialValue())
    
    def unappliedChanges(self):
        """Returns true if the user has changed the value and it has not been applied"""
        return self.getEditValue() != self.prefVar.getValueStr()
    
    def updateEditor(self):
        """Called after editVal is changed (and verified)"""
        pass

    def updateChanged(self):
        """Updates the "value changed" indicator.
        """
        self.timer.cancel()
        editValue = self.getEditValue()
#       print "updateChanged called"
#       print "editValue = %r" % editValue
#       print "currentValue = %r" % self.getCurrentValue()
#       print "initialValue = %r" % self.getInitialValue()
#       print "defaultValue = %r" % self.getDefValue()
        if editValue == self.getCurrentValue():
            self.changedVar.set("")
        else:
            self.changedVar.set("!")

    def _addCtxMenu(self, wdg):
        """Convenience function; adds the usual contextual menu to a widget"""
        RO.Wdg.addCtxMenu (
            wdg = wdg,
            helpURL = self.prefVar.helpURL,
            configFunc = self._configCtxMenu,
        )
        wdg.helpText = self.prefVar.helpText
    
    def _editCallback(self, *args):
        """Called whenever the edited value changes.
        Uses after to avoid the problem of the user entering
        an invalid character that is immediately rejected;
        that rejection doesn't show up in editVar.get
        and so the changed indicator falsely turns on.
        """
#       print "_editCallback; afterID=", self.afterID
        self.timer.start(0.001, self.updateChanged)
        
    def _setupCallbacks(self):
        """Set up a callback to call self.updateChanged
        whenever the edit value changes.
        """
        self.editVar.trace_variable("w", self._editCallback)

    def _getEditWdg(self):
        if self.prefVar.validValues:
            # use a pop-up list of values
            # first generate a list of strings representing the values
            valueList = [self.prefVar.asStr(val) for val in self.prefVar.validValues]
            # now return a menu containing those values
            wdg = RO.Wdg.OptionMenu(
                master = self.master,
                var= self.editVar,
                items = valueList,
                helpText = self.prefVar.helpText,
                helpURL = self.prefVar.helpURL,
            )
            wdg.ctxSetConfigFunc(self._configCtxMenu)
            wdg.set(self.getInitialValue())
        else:
            wdg = self.prefVar.getEditWdg(self.master, self.editVar, self._configCtxMenu)
        return wdg

    def _getRangeWdg(self):
        return tkinter.Label(self.master, text = self.prefVar.getRangeStr())
    
    def _getShowMenu(self):
        mbut = tkinter.Menubutton(self.master,
            indicatoron=1,
            direction="below",
            borderwidth=2,
            relief="raised",
            highlightthickness=2,
        )
        mnu = tkinter.Menu(mbut, tearoff=0)
        mnu.add_command(label="Current", command=self.showCurrentValue)
        mnu.add_command(label="Initial", command=self.showInitialValue)
        mnu.add_command(label="Default", command=self.showDefaultValue)
        mnu.add_separator()
        mnu.add_command(label="Apply", command=self.setVariable)
        mbut["menu"] = mnu
        return mbut

    def _configCtxMenu(self, mnu):
        def summaryFromVal(val):
            try:
                return self.prefVar.asSummary(val)
            except Exception as e:
                sys.stderr.write("could not get summary of %r for %s: %s\n" % (val, self.prefVar.name, e))
                return "???"
        
        # basic current/initial/default menu items
        mnu.add_command(label="Current (%s)" % (summaryFromVal(self.getCurrentValue()),), command=self.showCurrentValue)
        mnu.add_command(label="Initial (%s)" % (summaryFromVal(self.getInitialValue()),), command=self.showInitialValue)
        mnu.add_command(label="Default (%s)" % (summaryFromVal(self.getDefValue()),), command=self.showDefaultValue)
        mnu.add_separator()

        # minimum and maximum values, if present
        try:
            didAdd = False
            if self.prefVar.minValue is not None:
                mnu.add_command(label="Minimum (%s)" % (summaryFromVal(self.prefVar.minValue),), command=self.showCurrentValue)
                didAdd = True
            if self.prefVar.maxValue is not None:
                mnu.add_command(label="Maximum (%s)" % (summaryFromVal(self.prefVar.maxValue),), command=self.showCurrentValue)
                didAdd = True
            if didAdd:
                mnu.add_separator()
        except AttributeError:
            pass
        
        # apply menu item       
        mnu.add_command(label="Apply", command=self.setVariable)
        if self.prefVar.helpURL:
            mnu.add_separator()
            return True
        return False
Ejemplo n.º 19
0
class ObjPosWdg(RO.Wdg.InputContFrame):
    """A widget for specifying object positions

    Inputs:
    - master        master Tk widget -- typically a frame or window
    - userModel     a TUI.TCC.UserModel; specify only if global model
                    not wanted (e.g. for checking catalog values)
    - **kargs       keyword arguments for Tkinter.Frame
    """
    def __init__ (self,
        master = None,
        userModel = None,
     **kargs):
        RO.Wdg.InputContFrame.__init__(self, master, **kargs)
        gr = RO.Wdg.Gridder(self, sticky="w")
        
        # start out by not checking object position
        # set this true after all widgets are painted
        # and the formatting functions have had their test run
        self.checkObjPos = 0
        
        self._azAltRefreshTimer = Timer()
        
        self.objNameWdg = RO.Wdg.StrEntry(self,
            helpText = "Object name (optional)",
            helpURL = _HelpPrefix + "NameWdg",
            width=25,
        )
        self.objName = gr.gridWdg (
            label = "Name",
            dataWdg = self.objNameWdg,
            colSpan = 3,
        )
        lastCol = gr.getNextCol() - 2
        self.columnconfigure(lastCol, weight=1)
        
        objPos1UnitsVar = Tkinter.StringVar()
        self.objPos1 = gr.gridWdg (
            label = "",
            dataWdg = RO.Wdg.DMSEntry(self,
                minValue = 0,
                maxValue = 359.99999999,
                defValue = None,
                unitsVar=objPos1UnitsVar,
                isHours = 0,    # this will vary so no initial value is actually needed
                helpText = "Object position",
                helpURL = _HelpPrefix + "PosWdg",
            ),
            units = objPos1UnitsVar,
        )
        
        objPos2UnitsVar = Tkinter.StringVar()
        self.objPos2 = gr.gridWdg (
            label = "",
            dataWdg = RO.Wdg.DMSEntry(self,
                minValue = 0,
                maxValue = 90,
                defValue = None,
                unitsVar=objPos2UnitsVar,
                isHours = 0,    # always in degrees
                helpText = "Object position",
                helpURL = _HelpPrefix + "PosWdg",
            ),
            units = objPos2UnitsVar,
        )

        self.coordSysWdg = CoordSysWdg.CoordSysWdg(
            master = self,
            userModel = userModel,
        )
        gr.gridWdg (
            label = "CSys",
            dataWdg = self.coordSysWdg,
            colSpan = 3,
        )

        self.rotWdg = RotWdg.RotWdg(
            master = self,
            userModel = userModel,
        )
        gr.gridWdg (
            label = "Rot",
            dataWdg = self.rotWdg,
            colSpan = 3,
        )
        
        azAltFrame = Tkinter.Frame(self)
        
        self.azWdg = RO.Wdg.FloatLabel (
            master = azAltFrame,
            precision = 2,
            width = 6,
            helpText = "azimuth for proposed object",
            helpURL = _HelpPrefix + "Azimuth",
        )
        self.azWdg.pack(side="left")
        Tkinter.Label(azAltFrame,
            text="%s  Alt" % (RO.StringUtil.DegStr,)).pack(side="left")
        
        self.altWdg = RO.Wdg.FloatLabel (
            master = azAltFrame,
            precision = 2,
            width = 6,
            helpText = "altitude for proposed object",
            helpURL = _HelpPrefix + "Altitude",
        )
        self.altWdg.pack(side="left")
        Tkinter.Label(azAltFrame, text=RO.StringUtil.DegStr).pack(side="left")

        gr.gridWdg (
            label = "Az",
            dataWdg = azAltFrame,
            colSpan = 3,
        )

        self.airmassWdg = RO.Wdg.FloatLabel (
            master = self,
            precision = 3,
            width = 6,
            helpText = "airmass for proposed object",
            helpURL = _HelpPrefix + "Airmass",
        )
        gr.gridWdg (
            label = "Airmass",
            dataWdg = self.airmassWdg,
        )
    
        # create a set of input widget containers
        # this makes it easy to retrieve a command
        # and also to get and set all data using a value dictionary
        
        # note: the coordsys widget must be FIRST
        # because it has to be set (when restoring from a value dict)
        # before pos1 is set, to set the isHours flag correctly
        def formatObjPos(inputCont):
            wdgList = inputCont.getWdgList()

            # format data using the widgets
            valList = []
            for wdg in wdgList:
                if wdg.getString() == '':
                    raise ValueError, "must specify position"
                
                val = wdg.getNum()
                if wdg.getIsHours():
                    val = val * 15.0
                valList.append(val)
            return 'track %.7f, %.7f' % tuple(valList)
        
        def formatAll(inputCont):
            # container order is coordsys, objpos, rotator, name (optional)
            strList = inputCont.getStringList()
            return strList[1] + ' ' + strList[0] + ''.join(strList[2:])

        def vmsQuoteStr(astr):
            return RO.StringUtil.quoteStr(astr, '"')

        self.inputCont = RO.InputCont.ContList (
            conts = [
                self.coordSysWdg.inputCont,
                RO.InputCont.WdgCont (
                    name = "ObjPos",
                    wdgs = (self.objPos1.dataWdg, self.objPos2.dataWdg),
                    formatFunc = formatObjPos,
                ),
                RO.InputCont.WdgCont (
                    name = "Name",
                    wdgs = self.objNameWdg,
                    formatFunc = RO.InputCont.VMSQualFmt(vmsQuoteStr),
                ),
                self.rotWdg.inputCont,
            ],
            formatFunc = formatAll,
        )
        
        self.userModel = userModel or TUI.TCC.UserModel.getModel()
        self.userModel.coordSysName.addCallback(self._coordSysChanged)
        self.userModel.potentialTarget.addCallback(self.setAzAltAirmass)
        
        self.tccModel = TUI.TCC.TCCModel.getModel()
        self.tccModel.azLim.addCallback(self._azLimChanged)
        self.tccModel.altLim.addCallback(self._altLimChanged)

        # initialize display
        self.restoreDefault()
        
        self.objNameWdg.focus_set()
    
    def _azLimChanged(self, azLim, isCurrent, **kargs):
#       print "_azLimitChanged(azLim=%r, isCurrent=%s)" % (azLim, isCurrent)
        coordSys = self.userModel.coordSysName.get()
        if coordSys != RO.CoordSys.Mount:
            return
        
        self.objPos1.dataWdg.setRange(*azLim[0:2])
    
    def _altLimChanged(self, altLim, isCurrent, **kargs):
#       print "_altLimitChanged(altLim=%r, isCurrent=%s)" % (altLim, isCurrent)
        coordSys = self.userModel.coordSysName.get()
        if coordSys != RO.CoordSys.Mount:
            return
        
        self.objPos2.dataWdg.setRange(*altLim[0:2])
    
    def _coordSysChanged (self, coordSys):
        """Update the display when the coordinate system is changed.
        """
#       print "ObjPosWdg._coordSysChanged(coordSys=%r)" % (coordSys,)
        pos1IsHours = 1
        csysObj = RO.CoordSys.getSysConst(coordSys)
        pos1IsHours = csysObj.eqInHours()
        posLabels = csysObj.posLabels()

        if coordSys in RO.CoordSys.AzAlt:
            if coordSys == RO.CoordSys.Mount:
                azLim, isCurrent = self.tccModel.azLim.get()
                pos1Range = azLim[0:2]
                altLim, isCurrent = self.tccModel.altLim.get()
                pos2Range = altLim[0:2]
            else:
                pos1Range = (0, 360)
                pos2Range = (0, 90)
        elif pos1IsHours:
            pos1Range = (0, 24)
            pos2Range = (-90, 90)
        else:
            # no such coordsys, so makes a good sanity check
            raise RuntimeError, "ObjPosWdg bug: cannot handle coordinate system %r" % (coordSys,)
        
        self.objPos1.labelWdg["text"] = posLabels[0]
        self.objPos2.labelWdg["text"] = posLabels[1]
        self.objPos1.dataWdg.setIsHours(pos1IsHours)
        self.objPos1.dataWdg.setRange(*pos1Range)
        self.objPos2.dataWdg.setRange(*pos2Range)
    
    def getSummary(self):
        """Returns (name, pos1, pos2, csys), all as the strings shown in the widgets
        (not the numeric values).
        It would be slightly nicer if the summary could be derived from the value dictionary
        but this is quite tricky to do right."""
        name = self.objNameWdg.get()
        pos1 = self.objPos1.dataWdg.getString()
        pos2 = self.objPos2.dataWdg.getString()
        csys = self.userModel.coordSysName.get()
        return (name, pos1, pos2, csys)
    
    def neatenDisplay(self):
        self.objPos1.dataWdg.neatenDisplay()
        self.objPos2.dataWdg.neatenDisplay()
    
    def setAzAltAirmass(self, *args, **kargs):
#       print "ObjPosWdg.setAzAltAirmass"
        self._azAltRefreshTimer.cancel()

        target = self.userModel.potentialTarget.get()
        if target == None:
            self.azWdg.set(None)
            self.altWdg.set(None)
            self.airmassWdg.set(None)
            return
        
        azalt = target.getAzAlt()
        if azalt == None:
            self.azWdg.set(None)
            self.altWdg.set(None)
            self.airmassWdg.set(None)
            return

        az, alt = azalt
        airmass = RO.Astro.Sph.airmass(alt)
        altData, limCurrent = self.tccModel.altLim.get()
        altSeverity = RO.Constants.sevNormal
        minAlt = altData[0]
        if minAlt != None:
            if alt < minAlt:
                altSeverity = RO.Constants.sevError
        
        self.azWdg.set(az)
        self.altWdg.set(alt, severity = altSeverity)
        self.airmassWdg.set(airmass)
        self._azAltRefreshTimer.start(_AzAltRefreshDelay, self.setAzAltAirmass)
Ejemplo n.º 20
0
class TimeBar(ProgressBar):
    """Progress bar to display elapsed or remaining time in seconds.
    Inputs:
    - countUp: if True, counts up, else counts down
    - autoStop: automatically stop when the limit is reached
    - updateInterval: how often to update the display (sec)
    **kargs: other arguments for ProgressBar, including:
      - value: initial time;
        typically 0 for a count up timer, maxValue for a countdown timer
        if omitted then 0 is shown and the bar does not progress until you call start.
      - minvalue: minimum time; typically 0
      - maxValue: maximum time
    """
    def __init__ (self,
        master,
        countUp = False,
        valueFormat = ("%3.0f sec", "??? sec"),
        autoStop = False,
        updateInterval = 0.1,
    **kargs):
        ProgressBar.__init__(self,
            master = master,
            valueFormat = valueFormat,
            **kargs
        )
        self._autoStop = bool(autoStop)
        self._countUp = bool(countUp)

        self._updateInterval = updateInterval
        self._updateTimer = Timer()
        self._startTime = None

        if "value" in kargs:
            self.start(kargs["value"])
    
    def clear(self):
        """Set the bar length to zero, clear the numeric time display and stop the timer.
        """
        self._updateTimer.cancel()
        ProgressBar.clear(self)
        self._startTime = None
    
    def pause(self, value = None):
        """Pause the timer.
        
        Inputs:
        - value: the value at which to pause; if omitted then the current value is used
        
        Error conditions: does nothing if not running.
        """
        if self._updateTimer.cancel():
            # update timer was running
            if value:
                self.setValue(value)
            else:
                self._updateTime(reschedule = False)
    
    def resume(self):
        """Resume the timer from the current value.

        Does nothing if not paused or running.
        """
        if self._startTime is None:
            return
        self._startUpdate()
    
    def start(self, value = None, newMin = None, newMax = None, countUp = None):
        """Start the timer.

        Inputs:
        - value: starting value; if None, set to 0 if counting up, max if counting down
        - newMin: minimum value; if None then the existing value is used
        - newMax: maximum value: if None then the existing value is used
        - countUp: if True/False then count up/down; if None then the existing value is used
        
        Typically you will only specify newMax or nothing.
        """
        if newMin is not None:
            self.minValue = float(newMin)
        if newMax is not None:
            self.maxValue = float(newMax)
        if countUp is not None:
            self._countUp = bool(countUp)

        if value is not None:
            value = float(value)
        elif self._countUp:
            value = 0.0
        else:
            value = self.maxValue
        self.setValue(value)
        
        self._startUpdate()
    
    def _startUpdate(self):
        """Starts updating from the current value.
        """
        if self._countUp:
            self._startTime = time.time() - self.value
        else:
            self._startTime = time.time() - (self.maxValue - self.value)
        self._updateTime()

    def _updateTime(self, reschedule = True):
        """Automatically update the elapsed time display.
        Call once to get things going.
        """
        # print "_updateTime called"
        # cancel pending update, if any
        self._updateTimer.cancel()
        
        if self._startTime is None:
            raise RuntimeError("bug! nothing to update")
        
        # update displayed value
        if self._countUp:
            value = time.time() - self._startTime
            if (self._autoStop and value >= self.maxValue):
                self.setValue(self.maxValue)
                return
        else:
            value = (self._startTime + self.maxValue) - time.time()
            if (self._autoStop and value <= 0.0):
                self.setValue(0)
                return
        
        self.setValue(value)

        # if requested, schedule next update
        if reschedule:
            self._updateTimer.start(self._updateInterval, self._updateTime)