コード例 #1
0
 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)
コード例 #2
0
ファイル: Generic.py プロジェクト: Subaru-PFS/tron_actorcore
 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)
コード例 #3
0
ファイル: PrefEditor.py プロジェクト: goodeveryone/awsl
    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()
コード例 #4
0
ファイル: UsersWindow.py プロジェクト: migueldvb/TUI
    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)
コード例 #5
0
 def setMsg(self,
            msgStr,
            severity=RO.Constants.sevNormal,
            isTemp=False,
            duration=None):
     """Display a new message.
     
     Inputs:
     - msgStr    the new string to display
     - severity  one of RO.Constants.sevNormal (default), sevWarning or sevError
     - isTemp    if true, message is temporary and can be cleared with clearTempMsg;
                 if false, any existing temp info is ditched
     - duration  the amount of time (msec) to leave a temporary message;
                 if omitted, there is no time limit;
                 ignored if isTemp false
     
     Returns None if a permanent message, else a unique positive message ID.
     """
     self.displayWdg.set(msgStr, severity=severity)
     if isTemp:
         self.currID = next(self.tempIDGen)
         if duration is not None:
             Timer(duration / 1000.0, self.clearTempMsg, self.currID)
     else:
         self.permMsg = msgStr
         self.permSeverity = severity
         self.currID = None
     return self.currID
コード例 #6
0
ファイル: DropletRunner.py プロジェクト: astromaddie/RO-py3
    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()
コード例 #7
0
 def displayNext():
     global ind, testData
     val = testData[ind]
     print("\nvalue = %r, isCurrent = %s" % tuple(val))
     for wdg in wdgSet:
         wdg.set(*val)
     ind += 1
     if ind < len(testData):
         Timer(1.2, displayNext)
コード例 #8
0
 def addRandomValues(line, interval=0.1):
     """Add random values to the specified strip chart line
     Inputs:
     - line: strip chart line
     - interval: interval between updates (sec)
     """
     var = varDict[line]
     line.addPoint(next(var))
     Timer(interval, addRandomValues, line, interval)
コード例 #9
0
ファイル: TCPConnection.py プロジェクト: astromaddie/RO-py3
 def runTest():
     global clientConn
     try:
         testStr = next(strIter)
         print("Client writing %r" % (testStr,))
         clientConn.writeLine(testStr)
         Timer(0.001, runTest)
     except StopIteration:
         pass
コード例 #10
0
ファイル: TestDispatcher.py プロジェクト: migueldvb/TUI
 def _dispatchIter(self, dataDictIter):
     """Dispatch and iterator over dataDictSet; see runDataDictSet for details
     """
     try:
         dataDict = dataDictIter.next()
         delay = dataDict.pop("delay")
     except StopIteration:
         print "Test finished"
         return
     self.dispatch(**dataDict)
     Timer(delay, self._dispatchIter, dataDictIter)
コード例 #11
0
    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"])
コード例 #12
0
ファイル: TkSocket.py プロジェクト: Subaru-PFS/tron_actorcore
 def runTest():
     global clientSocket
     try:
         testStr = next(strIter)
         print("Client writing %r" % (testStr,))
         if binary:
             clientSocket.write(testStr)
         else:
             clientSocket.writeLine(testStr)
         Timer(0.001, runTest)
     except StopIteration:
         pass
コード例 #13
0
ファイル: BalloonHelp.py プロジェクト: astromaddie/RO-py3
 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)
コード例 #14
0
ファイル: BalloonHelp.py プロジェクト: astromaddie/RO-py3
 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)
コード例 #15
0
ファイル: UsersWindow.py プロジェクト: migueldvb/TUI
    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)
コード例 #16
0
    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()
コード例 #17
0
ファイル: UsersWindow.py プロジェクト: migueldvb/TUI
    def dispatchNext():
        try:
            newDataDict = dataIter.next()
        except StopIteration:
            return

        msgDict = {
            "cmdr": ".hub",
            "cmdID": 11,
            "actor": "hub",
            "msgType": "i",
            "data": newDataDict
        }
        kd.dispatch(msgDict)
        Timer(1.0, dispatchNext)
コード例 #18
0
    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()
コード例 #19
0
ファイル: ProgressBar.py プロジェクト: r-owen/RO
    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"])
コード例 #20
0
    def _processNextFile(self, filePathList):
        """Helper for processFileList
        
        The main purpose of this helper is to yield some time between each file
        so the log window can update (without using update_idletasks).
        """
        if filePathList:
            filePath = filePathList[0]
            try:
                self.processFile(filePath)
            except Exception as e:
                self.logWdg.addOutput("%s failed: %s\n" % (filePath, e),
                                      severity=RO.Constants.sevError)
                if self.printTraceback:
                    traceback.print_exc(file=sys.stderr)

        remFilePathList = filePathList[1:]
        if remFilePathList:
            Timer(0.001, self._processNextFile, remFilePathList)
        elif self.doneMsg:
            self.logWdg.addOutput(self.doneMsg,
                                  severity=RO.Constants.sevNormal)
コード例 #21
0
ファイル: PrefEditor.py プロジェクト: goodeveryone/awsl
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
コード例 #22
0
ファイル: StatusConfigInputWdg.py プロジェクト: migueldvb/TUI
    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)
コード例 #23
0
ファイル: MiscWdg.py プロジェクト: r-owen/TUI
    def __init__ (self, master=None, **kargs):
        """Displays miscellaneous information, such as current time and az/alt

        Inputs:
        - master        master Tk widget -- typically a frame or window
        """
        Tkinter.Frame.__init__(self, master=master, **kargs)
        self.tccModel = TUI.TCC.TCCModel.getModel()
        self.gmechModel = TUI.Guide.GMechModel.getModel()
        
        self._clockTimer = Timer()
        
        gr = RO.Wdg.Gridder(self, sticky="e")

        # magic numbers
        AzAltRotPrec = 1    # number of digits past decimal point
        
        self.haWdg = RO.Wdg.DMSLabel(
            master = self,
            precision = 0,
            nFields = 3,
            cvtDegToHrs = 1,
            width = 8,
            helpText = "Hour angle of the object",
            helpURL = _HelpURL,
        )
        gr.gridWdg (
            label = "HA",
            dataWdg = self.haWdg,
            units = "hms",
        )
        
        self.lmstWdg = RO.Wdg.DMSLabel(
            master = self,
            precision = 0,
            nFields = 3,
            width = 8,
            justify="right",
            helpText = "Local mean sidereal time at APO",
            helpURL = _HelpURL,
        )
        gr.gridWdg (
            label = "LMST",
            dataWdg = self.lmstWdg,
            units = "hms",
        )
        
        self.utcWdg = RO.Wdg.StrLabel(
            master = self,
            width = 19,
            helpText = "Coordinated universal time",
            helpURL = _HelpURL,
        )
        gr.gridWdg (
            label = "UTC",
            dataWdg = self.utcWdg,
            colSpan = 2,
        )
        
        # start the second column of widgets
        gr.startNewCol(spacing=1)
        
        self.guideWdg = RO.Wdg.StrLabel(
            master = self,
            width = 13,
            anchor = "w",
            helpText = "State of guiding",
            helpURL = _HelpURL,
        )
        gr.gridWdg (
            label = "Guiding",
            dataWdg = self.guideWdg,
            colSpan = 4,
            units = False,
            sticky = "ew",
        )
        gr._nextCol -= 2 # allow overlap with widget to the right
        self.guideModelDict = {} # guide camera name: guide model
        for guideModel in TUI.Guide.GuideModel.modelIter():
            gcamName = guideModel.gcamName
            if gcamName.endswith("focus"):
                continue
            self.guideModelDict[guideModel.gcamName] = guideModel
            guideModel.locGuideStateSummary.addIndexedCallback(self._updGuideStateSummary, callNow=False)
        self._updGuideStateSummary()

        # airmass and zenith distance
        self.airmassWdg = RO.Wdg.FloatLabel(
            master = self,
            precision=3,
            width=5,
            helpURL = _HelpURL,
        )
        gr.gridWdg (
            label = "Airmass",
            dataWdg = self.airmassWdg,
            units = "",
        )
#       self.tccModel.axePos.addCallback(self.setAxePos)
        
        self.zdWdg = RO.Wdg.FloatLabel(
            master = self,
            precision=AzAltRotPrec,
            helpText = "Zenith distance",
            helpURL = _HelpURL,
            width=5,
        )
        gr.gridWdg (
            label = "ZD",
            dataWdg = self.zdWdg,
            units = RO.StringUtil.DegStr,
        )
        
        # start the third column of widgets
        gr.startNewCol(spacing=1)
        
        self.instNameWdg = RO.Wdg.StrLabel(
            master = self,
            width = 10,
            anchor = "w",
            helpText = "Current instrument",
            helpURL = _HelpURL,
        )
        gr.gridWdg (
            label = "Inst",
            dataWdg = self.instNameWdg,
            colSpan = 3,
            units = False,
            sticky = "w",
        )
        self.tccModel.instName.addCallback(self.updateInstName)
        
        self.secFocusWdg = RO.Wdg.FloatLabel(
            master = self,
            precision=0,
            width=5,
            helpText = "Secondary mirror focus",
            helpURL = _HelpURL,
        )
        gr.gridWdg (
            label = "Focus",
            dataWdg = self.secFocusWdg,
            units = u"\N{MICRO SIGN}m",
        )
        self.tccModel.secFocus.addROWdg(self.secFocusWdg)
        
        self.gcFocusWdg = RO.Wdg.FloatLabel(
            master = self,
            precision=0,
            width=5,
            helpText = "NA2 guide camera focus",
            helpURL = _HelpURL,
        )
        gr.gridWdg (
            label = "GC Focus",
            dataWdg = self.gcFocusWdg,
            units = u"\N{MICRO SIGN}m",
        )
        self.gmechModel.focus.addROWdg(self.gcFocusWdg)
        
        # all widgets are gridded
        gr.allGridded()
        
        # add callbacks that deal with multiple widgets
        self.tccModel.axePos.addCallback(self.setAxePos)
        
        # start clock updates       
        self.updateClock()
        
        # allow the last+1 column to grow to fill the available space
        self.columnconfigure(gr.getMaxNextCol(), weight=1)
コード例 #24
0
ファイル: MiscWdg.py プロジェクト: StarkillerX42/stui
    def __init__(self, master=None, **kargs):
        """Displays miscellaneous information, such as current time and az/alt

        Inputs:
        - master        master Tk widget -- typically a frame or window
        """
        Tkinter.Frame.__init__(self, master=master, **kargs)
        self.tccModel = TUI.Models.getModel("tcc")
        self.guiderModel = TUI.Models.getModel("guider")
        self.mcpModel = TUI.Models.getModel("mcp")
        self.plateDBModel = TUI.Models.getModel("platedb")
        self._cartridgeInfo = [None] * 3  # (cartID, plateID, pointing)
        self._clockTimer = Timer()

        gr = RO.Wdg.Gridder(self, sticky="e")

        self.haWdg = RO.Wdg.DMSLabel(
            master=self,
            precision=0,
            nFields=3,
            cvtDegToHrs=1,
            width=8,
            helpText="Hour angle of the object",
            helpURL=_HelpURL,
        )
        gr.gridWdg("HA", self.haWdg, "hms")

        self.designHAWdg = RO.Wdg.DMSLabel(
            master=self,
            precision=0,
            nFields=3,
            cvtDegToHrs=1,
            width=8,
            helpText="Hour angle the plate was designed for (from platedb)",
            helpURL=_HelpURL,
        )
        gr.gridWdg("Design HA", self.designHAWdg, "hms")

        self.deltaHAWdg = RO.Wdg.DMSLabel(
            master=self,
            precision=0,
            nFields=3,
            cvtDegToHrs=1,
            width=8,
            helpText="Design - current hour angle",
            helpURL=_HelpURL,
        )
        gr.gridWdg("Des-Curr HA", self.deltaHAWdg, "hms")

        self.taiWdg = RO.Wdg.StrLabel(
            master=self,
            width=19,
            helpText="International Atomic Time",
            helpURL=_HelpURL,
        )
        gr.gridWdg("TAI", self.taiWdg, colSpan=2)

        # secondary focus
        self.secFocusWdg = RO.Wdg.FloatLabel(
            master=self,
            precision=0,
            width=5,
            helpText="Secondary mirror focus",
            helpURL=_HelpURL,
        )
        gr.gridWdg(
            label="Focus",
            dataWdg=self.secFocusWdg,
            units=u"\N{MICRO SIGN}m",
        )
        self.tccModel.secFocus.addValueCallback(self.secFocusWdg.set)

        # start the second column of widgets
        gr.startNewCol(spacing=1)

        gr._nextCol -= 2  # allow overlap with widget to the right

        self.airmassWdg = RO.Wdg.FloatLabel(
            master=self,
            precision=3,
            width=5,
            helpText="Airmass",
            helpURL=_HelpURL,
        )
        gr.gridWdg("Airmass", self.airmassWdg)

        self.zdWdg = RO.Wdg.FloatLabel(
            master=self,
            precision=1,
            helpText="Zenith distance (90 - altitude)",
            helpURL=_HelpURL,
            width=5,
        )
        gr.gridWdg("ZD", self.zdWdg, RO.StringUtil.DegStr)

        self.lmstWdg = RO.Wdg.DMSLabel(
            master=self,
            precision=0,
            nFields=3,
            width=8,
            justify="right",
            helpText="Local mean sidereal time at APO",
            helpURL=_HelpURL,
        )
        gr.gridWdg("LMST", self.lmstWdg, "hms")

        self.sjdWdg = RO.Wdg.IntLabel(
            master=self,
            helpText="SDSS MJD (rolls over at TAI MJD-0.3)",
            helpURL=_HelpURL,
            width=6,
        )
        gr.gridWdg("SJD", self.sjdWdg, "days")

        self.scaleWdg = RO.Wdg.FloatLabel(
            master=self,
            precision=1,
            width=8,
            helpText=
            "scale ((plate/nominal - 1) * 1e6); larger is higher resolution",
            helpURL=_HelpURL,
        )
        gr.gridWdg(
            label="Scale",
            dataWdg=self.scaleWdg,
            units="1e6",
        )
        self.tccModel.scaleFac.addCallback(self._scaleFacCallback)

        # start the third column of widgets
        gr.startNewCol(spacing=1)

        self.instNameWdg = RO.Wdg.StrLabel(
            master=self,
            width=10,
            helpText="Current instrument (from the TCC)",
            helpURL=_HelpURL,
        )
        gr.gridWdg("Inst", self.instNameWdg, units=False)
        self.tccModel.inst.addValueCallback(self.instNameWdg.set)

        self.cartridgeIDWdg = RO.Wdg.StrLabel(
            master=self,
            width=13,
            helpText="currently mounted cartridge (from MCP and guider)",
            helpURL=_HelpURL,
        )
        gr.gridWdg("Cartridge", self.cartridgeIDWdg)

        self.plateIDWdg = RO.Wdg.IntLabel(
            master=self,
            width=8,
            helpText="currently mounted plug plate (from the guider)",
            helpURL=_HelpURL,
        )
        gr.gridWdg("Plate", self.plateIDWdg)

        self.platePointingWdg = RO.Wdg.StrLabel(
            master=self,
            width=8,
            helpText="plug-plate pointing (from the guider)",
            helpURL=_HelpURL,
        )
        gr.gridWdg("Pointing", self.platePointingWdg)

        # state of guiding
        self.guideWdg = RO.Wdg.StrLabel(
            master=self,
            anchor="e",
            helpText="State of guiding",
            helpURL=_HelpURL,
        )
        gr.gridWdg(
            label="Guiding",
            dataWdg=self.guideWdg,
            units=False,
            sticky="ew",
        )

        # all widgets are gridded
        gr.allGridded()

        # add callbacks
        self.tccModel.axePos.addCallback(self._setAxePos)
        self.guiderModel.cartridgeLoaded.addCallback(self.setCartridgeInfo)
        self.mcpModel.instrumentNum.addCallback(self.setCartridgeInfo)
        self.plateDBModel.pointingInfo.addCallback(self._setAxePos)
        self.guiderModel.guideState.addCallback(self._guideStateCallback)

        # start clock updates
        self._updateClock()

        # allow the last+1 column to grow to fill the available space
        self.columnconfigure(gr.getMaxNextCol(), weight=1)
コード例 #25
0
ファイル: FTPLogWdg.py プロジェクト: astromaddie/RO-py3
    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 = Timer()

        # 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 != None:
            self._selectInd(selInd)
コード例 #26
0
ファイル: AgileGuideTest.py プロジェクト: r-owen/TUI
    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()
コード例 #27
0
ファイル: ObjPosWdg.py プロジェクト: migueldvb/TUI
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)
コード例 #28
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
コード例 #29
0
ファイル: UsersWindow.py プロジェクト: migueldvb/TUI
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()
コード例 #30
0
ファイル: Generic.py プロジェクト: Subaru-PFS/tron_actorcore
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)
コード例 #31
0
ファイル: FTPLogWdg.py プロジェクト: r-owen/RO
    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)
コード例 #32
0
ファイル: FTPLogWdg.py プロジェクト: r-owen/RO
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)
コード例 #33
0
def _nextGuideOffset(guideOffInfo, delaySec):
    guideOffInfo.update()
    keyVarStr = guideOffInfo.getKeyVarStr()
    testDispatcher.dispatch(keyVarStr, actor="tcc")
    Timer(delaySec, _nextGuideOffset, guideOffInfo, delaySec)
コード例 #34
0
class StripChartWdg(tkinter.Frame):
    """A widget to changing values in real time as a strip chart
    
    Usage Hints:
    - For each variable quantity to display:
      - Call addLine once to specify the quantity
      - Call addPoint for each new data point you wish to display

    - For each constant line (e.g. limit) to display call addConstantLine
    
    - To make sure a plot includes one or two y values (e.g. 0 or a range of values) call showY

    - To manually scale a Y axis call setYLimits (by default all y axes are autoscaled).
    
    - All supplied times are POSIX timestamps (e.g. as supplied by time.time()).
        You may choose the kind of time displayed on the time axis (e.g. UTC or local time) using cnvTimeFunc
        and the format of that time using dateFormat.
    
    Known Issues:
    matplotlib's defaults present a number of challenges for making a nice strip chart display.
    Some issues and manual solutions are discussed in the main file's document string.
        
    Potentially Useful Attributes:
    - canvas: the matplotlib FigureCanvas
    - figure: the matplotlib Figure
    - subplotArr: list of subplots, from top to bottom; each is a matplotlib Subplot object,
        which is basically an Axes object but specialized to live in a rectangular grid
    - xaxis: the x axis shared by all subplots
    """
    def __init__(
        self,
        master,
        timeRange=3600,
        numSubplots=1,
        width=8,
        height=2,
        showGrid=True,
        dateFormat="%H:%M:%S",
        updateInterval=None,
        cnvTimeFunc=None,
    ):
        """Construct a StripChartWdg with the specified time range
        
        Inputs:
        - master: Tk parent widget
        - timeRange: range of time displayed (seconds)
        - width: width of graph in inches
        - height: height of graph in inches
        - numSubplots: the number of subplots
        - showGrid: if True a grid is shown
        - dateFormat: format for major axis labels, using time.strftime format
        - updateInterval: now often the time axis is updated (seconds); if None a value is calculated
        - cnvTimeFunc: a function that takes a POSIX timestamp (e.g. time.time()) and returns matplotlib days;
            typically an instance of TimeConverter; defaults to TimeConverter(useUTC=False)
        """
        tkinter.Frame.__init__(self, master)

        self._timeRange = timeRange
        self._isVisible = self.winfo_ismapped()
        self._isFirst = True
        if updateInterval is None:
            updateInterval = max(0.1, min(5.0, timeRange / 2000.0))
        self.updateInterval = float(updateInterval)
        #         print "updateInterval=", self.updateInterval

        if cnvTimeFunc is None:
            cnvTimeFunc = TimeConverter(useUTC=False)
        self._cnvTimeFunc = cnvTimeFunc

        # how many time axis updates occur before purging old data
        self._maxPurgeCounter = max(1, int(0.5 + (5.0 / self.updateInterval)))
        self._purgeCounter = 0

        self.figure = matplotlib.figure.Figure(figsize=(width, height),
                                               frameon=True)
        self.canvas = FigureCanvasTkAgg(self.figure, self)
        self.canvas.get_tk_widget().grid(row=0, column=0, sticky="news")
        self.canvas.mpl_connect('draw_event', self._handleDrawEvent)
        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure(0, weight=1)
        bottomSubplot = self.figure.add_subplot(numSubplots, 1, numSubplots)
        self.subplotArr = [self.figure.add_subplot(numSubplots, 1, n+1, sharex=bottomSubplot) \
            for n in range(numSubplots-1)] + [bottomSubplot]
        if showGrid:
            for subplot in self.subplotArr:
                subplot.grid(True)

        self.xaxis = bottomSubplot.xaxis
        bottomSubplot.xaxis_date()
        self.xaxis.set_major_formatter(
            matplotlib.dates.DateFormatter(dateFormat))

        # dictionary of constant line name: (matplotlib Line2D, matplotlib Subplot)
        self._constLineDict = dict()

        for subplot in self.subplotArr:
            subplot._scwLines = []  # a list of contained _Line objects;
            # different than the standard lines property in that:
            # - lines contains Line2D objects
            # - lines contains constant lines as well as data lines
            subplot._scwBackground = None  # background for animation
            subplot.label_outer(
            )  # disable axis labels on all but the bottom subplot
            subplot.set_ylim(auto=True)  # set auto scaling for the y axis

        self.bind("<Map>", self._handleMap)
        self.bind("<Unmap>", self._handleUnmap)
        self._timeAxisTimer = Timer()
        self._updateTimeAxis()

    def addConstantLine(self, y, subplotInd=0, **kargs):
        """Add a new constant to plot
        
        Inputs:
        - y: value of constant line
        - subplotInd: index of subplot
        - All other keyword arguments are sent to the matplotlib Line2D constructor
          to control the appearance of the data. See addLine for more information.
        """
        subplot = self.subplotArr[subplotInd]
        line2d = subplot.axhline(y, **kargs)
        yMin, yMax = subplot.get_ylim()
        if subplot.get_autoscaley_on() and numpy.isfinite(y) and not (yMin <= y
                                                                      <= yMax):
            subplot.relim()
            subplot.autoscale_view(scalex=False, scaley=True)
        return line2d

    def addLine(self, subplotInd=0, **kargs):
        """Add a new quantity to plot
        
        Inputs:
        - subplotInd: index of subplot
        - All other keyword arguments are sent to the matplotlib Line2D constructor
          to control the appearance of the data. Useful arguments include:
          - label: name of line (displayed in a Legend)
          - color: color of line
          - linestyle: style of line (defaults to a solid line); "" for no line, "- -" for dashed, etc.
          - marker: marker shape, e.g. "+"
          Please do not attempt to control other sorts of line properties, such as its data.
          Arguments to avoid include: animated, data, xdata, ydata, zdata, figure.
        """
        subplot = self.subplotArr[subplotInd]
        return _Line(subplot=subplot,
                     cnvTimeFunc=self._cnvTimeFunc,
                     wdg=self,
                     **kargs)

    def clear(self):
        """Clear data in all non-constant lines
        """
        for subplot in self.subplotArr:
            for line in subplot._scwLines:
                line.clear()

    def getDoAutoscale(self, subplotInd=0):
        return self.subplotArr[subplotInd].get_autoscaley_on()

    def removeLine(self, line):
        """Remove an existing line added by addLine or addConstantLine
        
        Raise an exception if the line is not found
        """
        if isinstance(line, _Line):
            # a _Line object needs to be removed from _scwLines as well as the subplot
            line2d = line.line2d
            subplot = line.subplot
            subplot._scwLines.remove(line)
        else:
            # a constant line is just a matplotlib Line2D instance
            line2d = line
            subplot = line.axes

        subplot.lines.remove(line2d)
        if subplot.get_autoscaley_on():
            subplot.relim()
            subplot.autoscale_view(scalex=False, scaley=True)
        self.canvas.draw()

    def setDoAutoscale(self, doAutoscale, subplotInd=0):
        """Turn autoscaling on or off for the specified subplot
        
        You can also turn off autoscaling by calling setYLimits.
        """
        doAutoscale = bool(doAutoscale)
        subplot = self.subplotArr[subplotInd]
        subplot.set_ylim(auto=doAutoscale)
        if doAutoscale:
            subplot.relim()
            subplot.autoscale_view(scalex=False, scaley=True)

    def setYLimits(self, minY, maxY, subplotInd=0):
        """Set y limits for the specified subplot and disable autoscaling.
        
        Note: if you want to autoscale with a minimum range, use showY.
        """
        self.subplotArr[subplotInd].set_ylim(minY, maxY, auto=False)

    def showY(self, y0, y1=None, subplotInd=0):
        """Specify one or two values to always show in the y range.
        
        Inputs:
        - subplotInd: index of subplot
        - y0: first y value to show
        - y1: second y value to show; None to omit

        Warning: setYLimits overrides this method (but the values are remembered in case you turn
        autoscaling back on).
        """
        subplot = self.subplotArr[subplotInd]
        yMin, yMax = subplot.get_ylim()

        if y1 is not None:
            yList = [y0, y1]
        else:
            yList = [y0]
        doRescale = False
        for y in yList:
            subplot.axhline(y, linestyle=" ")
            if subplot.get_autoscaley_on() and numpy.isfinite(y) and not (
                    yMin <= y <= yMax):
                doRescale = True
        if doRescale:
            subplot.relim()
            subplot.autoscale_view(scalex=False, scaley=True)

    def _handleDrawEvent(self, event=None):
        """Handle draw event
        """
        #         print "handleDrawEvent"
        for subplot in self.subplotArr:
            subplot._scwBackground = self.canvas.copy_from_bbox(subplot.bbox)
            for line in subplot._scwLines:
                subplot.draw_artist(line.line2d)
            self.canvas.blit(subplot.bbox)

    def _handleMap(self, evt):
        """Handle map event (widget made visible)
        """
        self._isVisible = True
        self._handleDrawEvent()
        self._updateTimeAxis()

    def _handleUnmap(self, evt):
        """Handle unmap event (widget made not visible)
        """
        self._isVisible = False

    def _updateTimeAxis(self):
        """Update the time axis; calls itself
        """
        tMax = time.time() + self.updateInterval
        tMin = tMax - self._timeRange
        minMplDays = self._cnvTimeFunc(tMin)
        maxMplDays = self._cnvTimeFunc(tMax)

        self._purgeCounter = (self._purgeCounter + 1) % self._maxPurgeCounter
        doPurge = self._purgeCounter == 0

        if doPurge:
            for subplot in self.subplotArr:
                for line in subplot._scwLines:
                    line._purgeOldData(minMplDays)

        if self._isVisible or self._isFirst:
            for subplot in self.subplotArr:
                subplot.set_xlim(minMplDays, maxMplDays)
                if doPurge:
                    if subplot.get_autoscaley_on():
                        # since data is being purged the y limits may have changed
                        subplot.relim()
                        subplot.autoscale_view(scalex=False, scaley=True)
            self._isFirst = False
            self.canvas.draw()
        self._timeAxisTimer.start(self.updateInterval, self._updateTimeAxis)
コード例 #35
0
def _nextSecFocus(secFocus, delaySec):
    keyVarStr = "SecFocus=%0.1f" % (next(secFocus), )
    testDispatcher.dispatch(keyVarStr, actor="tcc")
    Timer(delaySec, _nextSecFocus, secFocus, delaySec)
コード例 #36
0
ファイル: DropletRunner.py プロジェクト: astromaddie/RO-py3
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)
コード例 #37
0
ファイル: SkyWindow.py プロジェクト: migueldvb/TUI
class SkyWdg (Tkinter.Frame):
    TELCURRENT = "telCurrent"
    TELTARGET = "telTarget"
    TELPOTENTIAL = "telPotential"
    CATOBJECT = "catObject"
    AzWrapSpiralDRad = 10
    AzWrapItemRad = 3
    AzWrapMargin = 5
    AzAltMargin = 10
    def __init__(self, master, width=201, height=201):
        Tkinter.Frame.__init__(self, master)
        
        self.tuiModel = TUI.TUIModel.getModel()
        self.tccModel = TUI.TCC.TCCModel.getModel()
        self.userModel = TUI.TCC.UserModel.getModel()

        # instance variables:
        # center: position of center of canvas, in pixels
        # size: size of canvas, in pixels
        # scale: scale of canvas, in pixels per deg
        self.currCatObjID = None

        self._telPotentialAnimTimer = Timer()
        
        self.eastLabelPos = AzAltTarget(azAlt=(90, 0))
        self.northLabelPos = AzAltTarget(azAlt=(180, 0))

        # pane on which to display current star info
        self.currStarDisp = RO.Wdg.StatusBar(master=self)
        self.currStarDisp.grid(row=1, column=0, sticky="ew")
        self.currStarMsgID = None

        # canvas on which to display stars
        self.cnv = Tkinter.Canvas(master=self,
            width=width, height=height,
#           background='black',
            selectborderwidth=0, highlightthickness=0)
        self.cnv.grid(row=0, column=0, sticky="nsew")
        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure(0, weight=1)
        RO.Wdg.addCtxMenu(
            wdg = self.cnv,
            helpURL = _HelpURL,
        )

        # thickness of canvas border;
        # drawable width/height = winfo_width/height - (2 * border)
        self.cnvBorderWidth = int(self.cnv["highlightthickness"]) + int(self.cnv["selectborderwidth"])
        self.cnv.bind('<Configure>', self._configureEvt)
        # self.cnv.tag_bind('star', '<Enter>', self._enterStar)
        # self.cnv.tag_bind('star', '<Leave>', self._leaveStar)
        self.cnv.bind('<Motion>', self._enterStar)
        self.cnv.bind('<Leave>', self._leaveStar)
        # the following prevents the display from blanking
        # when the button is pressed once (I tried trapping and
        # discarding <Button>, as a faster solutionn, but it didn't work)
        self.cnv.bind('<Button>', self._enterStar)
        self.cnv.bind('<Double-Button-1>', self._setPotential)
        self.center = [None,None]
        self.size = [None,None]
        self.azAltRad = None
        self.azAltScale = None
        self.sizeDeg = [180.0, 180.0]
        
        # various dictionaries whose keys are catalog name
        # note: if a catalog is deleted, it is removed from catDict
        # and catPixPosObjDict, but not necessarily the others
        self.catDict = {}   # key=catalog name, value = catalog
        self.catRedrawTimerDict = {}    # key=catalog name, value = tk after id
        self.catColorDict = {}  # key=catalog name, value = color
        self.catPixPosObjDict = {}  # key=catalog name, value = list of (pix pos, obj) pairs
        self.catSRDict = {} # key=catalog name, value = scriptrunner script to redisplay catalog

        self.telCurrent = None
        self.telTarget = None
        self.telPotential = None
        self.azWrapGauge = RO.CanvasUtil.Spiral (
            cnv = self.cnv,
            xctr = 1, yctr = 1,
            begRad = 0, endRad = 0,  # not yet ready to draw; canvas size unknown
            angScale = -1.0,
            angOff = -90.0,
        )
        self._setSize()
        
        # set up automatic update of current and target telescope position
        self.tccModel.axePos.addCallback(self.setTelCurrent)
        self.tccModel.tccPos.addCallback(self.setTelTarget)
        self.tccModel.azLim.addCallback(self.setAzLim)
        
        self.userModel.potentialTarget.addCallback(self.setTelPotential)
        self.userModel.userCatDict.addCallback(self._updUserCatDict)

    def _configureEvt(self, event=None):
        """Handle the <Configure> event.
        """
        self._setSize()
        self.redraw()

    def _setPotential(self, event):
        xyPix = (event.x, event.y)
        catObj = self.findNearestStar(xyPix, maxDistSq = 25.0)
        if catObj != None:
            self.userModel.potentialTarget.set(catObj)
            
            # note: evaluate when needed instead of at init
            # to make sure the window has been generated
            slewWin = self.tuiModel.tlSet.getToplevel("TCC.Slew")
            if slewWin:
                slewWin.makeVisible()
                slewWin.focus_set()

    def _enterStar(self, event):
        xyPix = (event.x, event.y)
        catObj = self.findNearestStar(xyPix, maxDistSq = 25.0)
        if catObj:
            self.currStarMsgID = self.currStarDisp.setMsg(
                msgStr = str(catObj),
                isTemp = True,
            )
        else:
            self.currStarMsgID = self.currStarDisp.clearTempMsg(self.currStarMsgID)
        
    def _leaveStar(self, event):
        self.currStarMsgID = self.currStarDisp.clearTempMsg(self.currStarMsgID)
        self.currCatObjID = None

    def _setSize(self):
        # size and center of canvas
        self.size[0] = self.cnv.winfo_width() - (2 * self.cnvBorderWidth)
        self.size[1] = self.cnv.winfo_height() - (2 * self.cnvBorderWidth)
        winRad = min(self.size) / 2
        for ind in range(2):
            self.center[ind] = (self.size[ind] / 2) + self.cnvBorderWidth
        
        # radius allocated to az/alt display
        self.azAltRad = winRad - (SkyWdg.AzAltMargin + SkyWdg.AzWrapItemRad + \
            SkyWdg.AzWrapSpiralDRad + SkyWdg.AzWrapItemRad + SkyWdg.AzWrapMargin)
        self.azAltRad = max(self.azAltRad, 0)
        self.azAltScale = self.azAltRad / 90.0
        
        # azWrapGauge geometry; beg and end radius only refer to the spiral;
        # AzWrapItemRad provides additional room for the items on the spiral
        begRad = self.azAltRad + SkyWdg.AzAltMargin + SkyWdg.AzWrapItemRad
        begRad = min(max(begRad, 0), winRad)
        endRad = winRad - SkyWdg.AzWrapItemRad - SkyWdg.AzWrapItemRad
        endRad = max(0, begRad, endRad)
        self.azWrapGauge.setGeom(
            xctr = self.center[0],
            yctr = self.center[1],
            begRad = begRad,
            endRad = endRad,
            redraw=0,
        )
#       print "_setSize called; winRad, azAltRad, begRad, endRad=", winRad, self.azAltRad, begRad, endRad

    def _printInfo(self):
        print "SkyWdg"
        print "size   = ", self.size, " pixels (excluding border)"
        print "center = ", self.center, " pixels"
        print "scale  = ", self.azAltScale, " pixels/deg"
        print "border = ", self.cnvBorderWidth, " pixels"

    def setAzLim(self, azLim, isCurrent=True, **kargs):
        """Sets the azimuth limits: minPos, maxPos and other values which are ignored"""
        if not isCurrent:
            return
        self.azWrapGauge.setAngLim(azLim[0], azLim[1], redraw=True)
        self._drawTelCurrent()
        self._drawTelPotential()
        self._drawTelTarget()

    def setTelCurrent(self, azAlt, isCurrent=True, **kargs):
        """Sets the telescope's current position.
        May be used as a keyword variable callback.

        Inputs:
            azAlt: az/alt position, in degrees (extra elements are ignored);
                use None if value is explicitly unknown (NaN)
            isCurrent: the data is current (up-to-date)
        """
#       print "SkyWdg.setTelCurrent: az,alt =", azAlt
        if None in azAlt[0:2]:
            self.telCurrent = None
        else:
            self.telCurrent = AzAltTarget(azAlt[0:2])
        self._drawTelCurrent()

    def setTelTarget(self, azAlt, isCurrent=True, **kargs):
        """Sets the telescope's target position.
        May be used as a keyword variable callback.

        Inputs:
            azAlt: az/alt position, in degrees (extra elements are ignored);
                use None if value is explicitly unknown (NaN)
            isCurrent: the data is current (up-to-date)
        """
#       print "SkyWdg.setTelTarget: az,alt =", azAlt
        if None in azAlt[0:2]:
            self.telTarget = None
        else:
            self.telTarget = AzAltTarget(azAlt[0:2])
        self._drawTelTarget()

    def setTelPotential(self, telTarget=None):
        """Sets or clears the telescope's potential position.
        
        Note: unlike setTelCurrent and setTelTarget;
        the telescope potential position may be a mean position.
        Hence the argument list is different (this function is not
        set up to be a keyword variable callback)

        Inputs:
        - telTarget: a TUI.TCC.TelTarget object; if None,
          the current position (if any) is cleared
        """
#       print "SkyWdg.setTelPotential(%s)" % (telTarget,)
        self.telPotential = telTarget
        self._drawTelPotential()
        if telTarget:
            msgStr = str(telTarget)
        else:
            msgStr = ""
        self.currStarDisp.setMsg(
            msgStr = msgStr,
            isTemp = False,
        )

    def addCatalog(self, catalog):
        """Add a new catalog with a given name.
        If the catalog already exists, it is deleted.
        """
#       print "addCatalog %r" % (catalog.name,)
        catName = catalog.name
        
        if catName in self.catDict:
            self.removeCatalogByName(catName)
        
        self.catDict[catName] = catalog
        self.catPixPosObjDict[catName] = []
        self.catRedrawTimerDict[catName] = Timer()
        self.catColorDict[catName] = catalog.getDispColor()
        
        def updateCat(sr, self=self, catalog=catalog):
            catName = catalog.name
            
            catTag = "cat_%s" % (catName,)
            
            if not catalog.getDoDisplay():
                self.catPixPosObjDict[catName] = []
                self.cnv.delete(catTag)
                return
    
            # if color has changed, update it
            color = catalog.getDispColor()
            oldColor = self.catColorDict.get(catName)
            if color != oldColor:
                self.cnv.itemconfigure(catTag, fill = color, outline = color)
                self.catColorDict[catName] = color
                
#           print "compute %s thread starting" % catName
            yield sr.waitThread(_UpdateCatalog, catalog.objList, self.center, self.azAltScale)
            pixPosObjList = sr.value
#           print "compute %s thread done" % catName

            catName = catalog.name
            catTag = "cat_%s" % (catName,)
    
            self.catPixPosObjDict[catName] = []
            self.cnv.delete(catTag)
    
            color = catalog.getDispColor()      
            rad = 2 # for now, eventually may wish to vary by magnitude or window size or...?
            for pixPos, obj in pixPosObjList:
                self.cnv.create_oval(
                    pixPos[0] - rad,     pixPos[1] - rad,
                    pixPos[0] + rad + 1, pixPos[1] + rad + 1,
                    tag = (SkyWdg.CATOBJECT, catTag),
                    fill = color,
                    outline = color,
                )
            self.catPixPosObjDict[catName] = pixPosObjList
            
            self.catRedrawTimerDict[catName].start(_CatRedrawDelay, self._drawCatalog, catalog)
        
        sr = RO.ScriptRunner.ScriptRunner(
            runFunc = updateCat,
            name = "updateCatalog",
        )
        
        self.catSRDict[catName] = sr
        catalog.addCallback(self._drawCatalog, callNow=True)

    def removeCatalogByName(self, catName):
        """Remove the specified catalog.
        """
#       print "removeCatalogByName %r" % (catName,)
        try:
            cat = self.catDict.pop(catName)
        except KeyError:
            raise RuntimeError("Catalog %r not found" % (catName,))
        
        cat.removeCallback(self._drawCatalog, doRaise=False)
        catTag = "cat_%s" % (catName,)
        self.cnv.delete(catTag)
        
        # cancel script runner and delete entry
        try:
            sr = self.catSRDict.pop(catName)
#           print "removeCatalogByName cancelling and deleting update script for %r" % catName
            sr.cancel()
        except KeyError:
            pass
        
        # cancel pending wakeup and delete entry
        try:
            timer = self.catRedrawTimerDict.pop(catName)
        except KeyError:
            pass
        timer.cancel()
        
        # delete entry in other catalog dictionaries
        for catDict in self.catPixPosObjDict, self.catColorDict:
            try:
                del catDict[catName]
            except KeyError:
                pass

    def findNearestStar(self, xyPix, maxDistSq=9.0e99):
        """Finds the catalog object nearest to xyPix, but only if
        the squared distance is within maxDistSq deg^2
        Returns the catalog object, or None if none found"""
        minStar = None
        minDistSq = maxDistSq
        for pixPosCatObjList in self.catPixPosObjDict.itervalues():
            for objPixPos, catObj in pixPosCatObjList:
                distSq = (objPixPos[0] - xyPix[0])**2 + (objPixPos[1] - xyPix[1])**2
                if distSq < minDistSq:
                    minStar = catObj
                    minDistSq = distSq
        return minStar
    
    def pixFromAzAlt(self, azAlt):
        """Convert a point from az,alt degrees (0 south, 90 east)
        to x,y pixels, such that east is to the left and north is up.
        """
        return self.pixFromDeg(xyDegFromAzAlt(azAlt))

    def azAltFromPix(self, xyPix):
        """Convert a point from x,y pixels to az,alt degrees (0 south, 90 east)
        such that east is to the left and north is up.
        """
        return azAltFromXYDeg(self.degFromPix(xyPix))

    def pixFromDeg(self, xyDeg):
        """convert a point from x,y degrees (x/east left, y/north up)
        to x,y pixels, Tk style (x right, y down).
        """
        xyPix = (
            self.center[0] + (xyDeg[0] * -self.azAltScale),
            self.center[1] + (xyDeg[1] * -self.azAltScale),
        )
        return xyPix

    def degFromPix(self, xyPix):
        """converts a point from x,y pixels, Tk style (x right, y down)
        to x,y degrees (x/east left, y/north up)
        """
        xyDeg = (
            (xyPix[0] - self.center[0]) / -self.azAltScale,
            (xyPix[1] - self.center[1]) / -self.azAltScale,
        )
        return xyDeg
    
    def _updUserCatDict(self, userCatDict):
        """Called when the userCatDict is updated.
        userCatDict is a dictionary of catalog name:TelTarget.Catalog
        """
        # delete any missing catalogs
        initialCatNames = self.catDict.keys()
        for catName in initialCatNames:
            if catName not in userCatDict:
                self.removeCatalogByName(catName)
        
        # add any new or changed catalogs
        # (adding deletes any existing copy)
        for catName, cat in userCatDict.iteritems():
            currCat = self.catDict.get(catName)
            if not currCat or currCat != cat or len(currCat.objList) != len(cat.objList):
                self.addCatalog(cat)

# drawing methods

    def redraw(self):
        """Redraw everything using last recorded geometry info.
        If window size has changed, call _setSize first.
        """
#       print "draw called"
#       self._printInfo()
        # clear canvas
        self.cnv.delete('all')
        
        # draw everything
        self._drawGrid()
        self._drawLabels()
        self.azWrapGauge.draw()
        self._drawTelCurrent()
        self._drawTelTarget()
        self._drawAllCatalogs()

    def _drawAllCatalogs(self):
        """Draw all objects in all catalogs, erasing all stars first.
        """
        self.catPixPosObjDict = {}
        self.cnv.delete(SkyWdg.CATOBJECT)
        for catalog in self.catDict.itervalues():
            self._drawCatalog(catalog)
            
    def _drawCatalog(self, catalog):
        """Draw the next portion of a catalog.
        subind=0 for the first portion, 1 for the next, etc.
        """
#       print "_drawCatalog(%r)" % (catalog.name)

        catName = catalog.name
        
        # cancel update script, if executing
        sr = self.catSRDict[catName]
        if sr.isExecuting():
#           print "_drawCatalog cancelling update script for catalog %r" % catName
            sr.cancel()

        # cancel scheduled wakeup, if any
        self.catRedrawTimerDict.get(catName).cancel()
        
#       print "_drawCatalog starting update script for catalog %r" % catName
        sr.start()

    def _drawGrid(self):
        nCircles = 6
        # color = "green"
        x, y = self.center
        for circNum in range(nCircles):
            rad = self.azAltScale * (90 * (circNum + 1) / nCircles)
            RO.CanvasUtil.ctrCircle (self.cnv, x, y, rad) #, outline = color)
        RO.CanvasUtil.ctrPlus(self.cnv, x, y, rad) #, fill = color)
        RO.CanvasUtil.ctrX(self.cnv, x, y, rad) #, fill = color)
    
    def _drawLabels(self):
        font = Tkinter.Entry()["font"]
        ex, ey = self.pixFromAzAlt(self.eastLabelPos.getAzAlt())
        nx, ny = self.pixFromAzAlt(self.northLabelPos.getAzAlt())
        self.cnv.create_text(ex-8, ey, text=" E", font=font) #, fill="green")
        self.cnv.create_text(nx, ny-5, text="N", font=font) #, fill="green")
    
    def _drawTelCurrent(self):
        self.cnv.delete(SkyWdg.TELCURRENT)
        if self.telCurrent == None:
            return

        color = "red"
        tag = SkyWdg.TELCURRENT

        # draw current telescope position on az/alt grid
        x, y = self.pixFromAzAlt(self.telCurrent.getAzAlt())
        RO.CanvasUtil.ctrCircle (self.cnv, x, y,
            rad=9,
            outline=color,
            tag=tag,
        )

        # draw current telescope position on wrap gauge display
        az, alt = self.telCurrent.getAzAlt()
        x, y = self.azWrapGauge.angToXY(az)
        if None not in (x, y):
            RO.CanvasUtil.ctrCircle (self.cnv, x, y,
                rad=4,
                width = 3,
                outline=color,
                tag=tag,
            )

    def _drawTelTarget(self):
        self.cnv.delete(SkyWdg.TELTARGET)
        if self.telTarget == None:
            return

        color = "red"
        tag = SkyWdg.TELTARGET

        # draw target on az/alt grid
        x, y = self.pixFromAzAlt(self.telTarget.getAzAlt())
#       print "drawing target at", self.telTarget.getAzAlt(), "=", x, y
        RO.CanvasUtil.ctrPlus (self.cnv, x, y,
            rad=12,
            holeRad=3,
            fill=color,
            tag=tag,
        )

        # draw target on wrap gauge
        az, alt = self.telTarget.getAzAlt()
        x, y = self.azWrapGauge.angToXY(az)
        if None not in (x,y):
            RO.CanvasUtil.ctrPlus (self.cnv, x, y,
                rad=4,
                holeRad=0,
                width=3,
                fill=color,
                tag=tag,
            )

    def _drawTelPotential(self):
#       print "_drawTelPotential"
        self.cnv.delete(SkyWdg.TELPOTENTIAL)
        if self.telPotential == None:
            return

        color = "dark green"
        tag = SkyWdg.TELPOTENTIAL

        # draw potential target on az, alt grid
        x, y = self.pixFromAzAlt(self.telPotential.getAzAlt())
#       print "drawing potential at", self.telPotential.getAzAlt(), "=", x, y
        RO.CanvasUtil.ctrX (self.cnv, x, y,
            rad=9,
            holeRad=3,
            fill=color,
            tag=tag,
        )
        
        self._telPotentialAnimTimer.start(_CatRedrawDelay, self._drawTelPotential)
コード例 #38
0
    def __init__(self,
        master,
        timeRange = 3600,
        numSubplots = 1,
        width = 8,
        height = 2,
        showGrid = True,
        dateFormat = "%H:%M:%S",
        updateInterval = None,
        cnvTimeFunc = None,
    ):
        """Construct a StripChartWdg with the specified time range
        
        Inputs:
        - master: Tk parent widget
        - timeRange: range of time displayed (seconds)
        - width: width of graph in inches
        - height: height of graph in inches
        - numSubplots: the number of subplots
        - showGrid: if True a grid is shown
        - dateFormat: format for major axis labels, using time.strftime format
        - updateInterval: now often the time axis is updated (seconds); if None a value is calculated
        - cnvTimeFunc: a function that takes a POSIX timestamp (e.g. time.time()) and returns matplotlib days;
            typically an instance of TimeConverter; defaults to TimeConverter(useUTC=False)
        """
        tkinter.Frame.__init__(self, master)
        
        self._timeRange = timeRange
        self._isVisible = self.winfo_ismapped()
        self._isFirst = True
        if updateInterval is None:
            updateInterval = max(0.1, min(5.0, timeRange / 2000.0))
        self.updateInterval = float(updateInterval)
#         print "updateInterval=", self.updateInterval

        if cnvTimeFunc is None:
            cnvTimeFunc = TimeConverter(useUTC=False)
        self._cnvTimeFunc = cnvTimeFunc

        # how many time axis updates occur before purging old data
        self._maxPurgeCounter = max(1, int(0.5 + (5.0 / self.updateInterval)))
        self._purgeCounter = 0

        self.figure = matplotlib.figure.Figure(figsize=(width, height), frameon=True)
        self.canvas = FigureCanvasTkAgg(self.figure, self)
        self.canvas.get_tk_widget().grid(row=0, column=0, sticky="news")
        self.canvas.mpl_connect('draw_event', self._handleDrawEvent)
        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure(0, weight=1)
        bottomSubplot = self.figure.add_subplot(numSubplots, 1, numSubplots)
        self.subplotArr = [self.figure.add_subplot(numSubplots, 1, n+1, sharex=bottomSubplot) \
            for n in range(numSubplots-1)] + [bottomSubplot]
        if showGrid:
            for subplot in self.subplotArr:
                subplot.grid(True)

        self.xaxis = bottomSubplot.xaxis
        bottomSubplot.xaxis_date()
        self.xaxis.set_major_formatter(matplotlib.dates.DateFormatter(dateFormat))

        # dictionary of constant line name: (matplotlib Line2D, matplotlib Subplot)
        self._constLineDict = dict()

        for subplot in self.subplotArr:
            subplot._scwLines = [] # a list of contained _Line objects;
                # different than the standard lines property in that:
                # - lines contains Line2D objects
                # - lines contains constant lines as well as data lines
            subplot._scwBackground = None # background for animation
            subplot.label_outer() # disable axis labels on all but the bottom subplot
            subplot.set_ylim(auto=True) # set auto scaling for the y axis
        
        self.bind("<Map>", self._handleMap)
        self.bind("<Unmap>", self._handleUnmap)
        self._timeAxisTimer = Timer()
        self._updateTimeAxis()
コード例 #39
0
        ("invalid text", False),
        (0, True),
        ("", True),
        (False, True),
        (1, True),
        (1234567890, True),
        (1234567890, False),
        (1.1, True),
        (1.9, True),
        (-1.1, True),
        (-1.9, True),
        (-0.001, True),
        (-1.9, False),
    ]

    ind = 0

    def displayNext():
        global ind, testData
        val = testData[ind]
        print("\nvalue = %r, isCurrent = %s" % tuple(val))
        for wdg in wdgSet:
            wdg.set(*val)
        ind += 1
        if ind < len(testData):
            Timer(1.2, displayNext)

    Timer(1.2, displayNext)

    root.mainloop()
コード例 #40
0
    def __init__(self, master):
        Tkinter.Frame.__init__(self, master)
        self.expNum = 1
        self.statusTimer = Timer()
        self.camera = arctic.Camera()

        row = 0

        exposeFrame = Tkinter.Frame(self)
        self.expTimeWdg = RO.Wdg.FloatEntry(
            master = exposeFrame,
            defValue = 1,
            minValue = 0,
            helpText = "exposure time (sec)",
        )
        self.expTimeWdg.pack(side="left")
        self.expTypeWdg = RO.Wdg.OptionMenu(
            master = exposeFrame,
            items = ExpTypeDict.keys(),
            defValue = "Object",
            helpText = "exposure type",
        )
        self.expTypeWdg.pack(side="left")
        self.expButton = RO.Wdg.Button(
            master = exposeFrame,
            text = "Expose",
            command = self.doExpose,
            helpText = "start exposure",
        )
        self.expButton.pack(side="left")
        exposeFrame.grid(row=row, column=0)
        row += 1

        binFrame = Tkinter.Frame(self)
        self.binFacColWdg = RO.Wdg.IntEntry(
            master = binFrame,
            defValue = 2,
            autoIsCurrent = True,
            helpText = "x bin factor",
        )
        self.binFacColWdg.pack(side="left")
        self.binFacRowWdg = RO.Wdg.IntEntry(
            master = binFrame,
            defValue = 2,
            autoIsCurrent = True,
            helpText = "y bin factor",
        )
        self.binFacRowWdg.pack(side="left")
        binFrame.grid(row=row, column=0)
        row += 1

        windowFrame = Tkinter.Frame(self)
        self.winStartColWdg = RO.Wdg.IntEntry(
            master = windowFrame,
            defValue = 0,
            autoIsCurrent = True,
            helpText = "window starting column",
        )
        self.winStartColWdg.pack(side="left")
        self.winStartRowWdg = RO.Wdg.IntEntry(
            master = windowFrame,
            defValue = 0,
            autoIsCurrent = True,
            helpText = "window starting row",
        )
        self.winStartRowWdg.pack(side="left")
        self.winWidthWdg = RO.Wdg.IntEntry(
            master = windowFrame,
            defValue = 0,
            autoIsCurrent = True,
            helpText = "window width (unbinned pixels)",
        )
        self.winWidthWdg.pack(side="left")
        self.winHeightWdg = RO.Wdg.IntEntry(
            master = windowFrame,
            defValue = 0,
            autoIsCurrent = True,
            helpText = "window height (unbinned pixels)",
        )
        self.winHeightWdg.pack(side="left")
        windowFrame.grid(row=row, column=0)
        row += 1

        self.fullWindowBtn = RO.Wdg.Button(
            master = self,
            command = self.doSetFullWindow,
            text = "Set Full Window",
            helpText = "set full window",
        )
        self.fullWindowBtn.grid(row=row, column=0)
        row += 1

        self.readoutRateWdg = RO.Wdg.OptionMenu(
            master = self,
            items = ReadoutRateNameEnumDict.keys(),
            defValue = "Medium",
            autoIsCurrent = True,
            helpText = "set readout rate",
        )
        self.readoutRateWdg.grid(row=row, column=0, sticky="w")
        row += 1

        self.readoutAmpsWdg = RO.Wdg.OptionMenu(
            master = self,
            items = ReadoutAmpsNameEnumDict.keys(),
            defValue = "Quad",
            autoIsCurrent = True,
            helpText = "set readout amps",
        )
        self.readoutAmpsWdg.grid(row=row, column=0, sticky="w")
        row += 1

        self.setConfigBtn = RO.Wdg.Button(
            master = self,
            command = self.doSetConfig,
            text = "Set Config",
            helpText = "set config",
        )
        self.setConfigBtn.grid(row=row, column=0, sticky="w")
        row += 1

        self.showInDS9Btn = RO.Wdg.Checkbutton(
            master = self,
            defValue = True,
            text = "Show in DS9",
            helpText = "show image in ds9?",
        )
        self.showInDS9Btn.grid(row=row, column=0, sticky="w")
        row += 1

        self.fileNameWdg = RO.Wdg.StrLabel(
            master = self,
            helpText = "file name",
        )
        self.fileNameWdg.grid(row=row, column=0, sticky="w")
        row += 1

        self.statusWdg = RO.Wdg.StrLabel(master=self)
        self.statusWdg.grid(row=row, column=0, sticky="w")
        row += 1

        self.statusBar = RO.Wdg.StatusBar(master=self)
        self.statusBar.grid(row=row, column=0, sticky="we")
        row += 1

        self.ds9Win = None

        self.getStatus()
コード例 #41
0
ファイル: MiscWdg.py プロジェクト: StarkillerX42/stui
class MiscWdg(Tkinter.Frame):
    InstNameDict = {0: "None"}  # add a value for Eng Cam once known

    def __init__(self, master=None, **kargs):
        """Displays miscellaneous information, such as current time and az/alt

        Inputs:
        - master        master Tk widget -- typically a frame or window
        """
        Tkinter.Frame.__init__(self, master=master, **kargs)
        self.tccModel = TUI.Models.getModel("tcc")
        self.guiderModel = TUI.Models.getModel("guider")
        self.mcpModel = TUI.Models.getModel("mcp")
        self.plateDBModel = TUI.Models.getModel("platedb")
        self._cartridgeInfo = [None] * 3  # (cartID, plateID, pointing)
        self._clockTimer = Timer()

        gr = RO.Wdg.Gridder(self, sticky="e")

        self.haWdg = RO.Wdg.DMSLabel(
            master=self,
            precision=0,
            nFields=3,
            cvtDegToHrs=1,
            width=8,
            helpText="Hour angle of the object",
            helpURL=_HelpURL,
        )
        gr.gridWdg("HA", self.haWdg, "hms")

        self.designHAWdg = RO.Wdg.DMSLabel(
            master=self,
            precision=0,
            nFields=3,
            cvtDegToHrs=1,
            width=8,
            helpText="Hour angle the plate was designed for (from platedb)",
            helpURL=_HelpURL,
        )
        gr.gridWdg("Design HA", self.designHAWdg, "hms")

        self.deltaHAWdg = RO.Wdg.DMSLabel(
            master=self,
            precision=0,
            nFields=3,
            cvtDegToHrs=1,
            width=8,
            helpText="Design - current hour angle",
            helpURL=_HelpURL,
        )
        gr.gridWdg("Des-Curr HA", self.deltaHAWdg, "hms")

        self.taiWdg = RO.Wdg.StrLabel(
            master=self,
            width=19,
            helpText="International Atomic Time",
            helpURL=_HelpURL,
        )
        gr.gridWdg("TAI", self.taiWdg, colSpan=2)

        # secondary focus
        self.secFocusWdg = RO.Wdg.FloatLabel(
            master=self,
            precision=0,
            width=5,
            helpText="Secondary mirror focus",
            helpURL=_HelpURL,
        )
        gr.gridWdg(
            label="Focus",
            dataWdg=self.secFocusWdg,
            units=u"\N{MICRO SIGN}m",
        )
        self.tccModel.secFocus.addValueCallback(self.secFocusWdg.set)

        # start the second column of widgets
        gr.startNewCol(spacing=1)

        gr._nextCol -= 2  # allow overlap with widget to the right

        self.airmassWdg = RO.Wdg.FloatLabel(
            master=self,
            precision=3,
            width=5,
            helpText="Airmass",
            helpURL=_HelpURL,
        )
        gr.gridWdg("Airmass", self.airmassWdg)

        self.zdWdg = RO.Wdg.FloatLabel(
            master=self,
            precision=1,
            helpText="Zenith distance (90 - altitude)",
            helpURL=_HelpURL,
            width=5,
        )
        gr.gridWdg("ZD", self.zdWdg, RO.StringUtil.DegStr)

        self.lmstWdg = RO.Wdg.DMSLabel(
            master=self,
            precision=0,
            nFields=3,
            width=8,
            justify="right",
            helpText="Local mean sidereal time at APO",
            helpURL=_HelpURL,
        )
        gr.gridWdg("LMST", self.lmstWdg, "hms")

        self.sjdWdg = RO.Wdg.IntLabel(
            master=self,
            helpText="SDSS MJD (rolls over at TAI MJD-0.3)",
            helpURL=_HelpURL,
            width=6,
        )
        gr.gridWdg("SJD", self.sjdWdg, "days")

        self.scaleWdg = RO.Wdg.FloatLabel(
            master=self,
            precision=1,
            width=8,
            helpText=
            "scale ((plate/nominal - 1) * 1e6); larger is higher resolution",
            helpURL=_HelpURL,
        )
        gr.gridWdg(
            label="Scale",
            dataWdg=self.scaleWdg,
            units="1e6",
        )
        self.tccModel.scaleFac.addCallback(self._scaleFacCallback)

        # start the third column of widgets
        gr.startNewCol(spacing=1)

        self.instNameWdg = RO.Wdg.StrLabel(
            master=self,
            width=10,
            helpText="Current instrument (from the TCC)",
            helpURL=_HelpURL,
        )
        gr.gridWdg("Inst", self.instNameWdg, units=False)
        self.tccModel.inst.addValueCallback(self.instNameWdg.set)

        self.cartridgeIDWdg = RO.Wdg.StrLabel(
            master=self,
            width=13,
            helpText="currently mounted cartridge (from MCP and guider)",
            helpURL=_HelpURL,
        )
        gr.gridWdg("Cartridge", self.cartridgeIDWdg)

        self.plateIDWdg = RO.Wdg.IntLabel(
            master=self,
            width=8,
            helpText="currently mounted plug plate (from the guider)",
            helpURL=_HelpURL,
        )
        gr.gridWdg("Plate", self.plateIDWdg)

        self.platePointingWdg = RO.Wdg.StrLabel(
            master=self,
            width=8,
            helpText="plug-plate pointing (from the guider)",
            helpURL=_HelpURL,
        )
        gr.gridWdg("Pointing", self.platePointingWdg)

        # state of guiding
        self.guideWdg = RO.Wdg.StrLabel(
            master=self,
            anchor="e",
            helpText="State of guiding",
            helpURL=_HelpURL,
        )
        gr.gridWdg(
            label="Guiding",
            dataWdg=self.guideWdg,
            units=False,
            sticky="ew",
        )

        # all widgets are gridded
        gr.allGridded()

        # add callbacks
        self.tccModel.axePos.addCallback(self._setAxePos)
        self.guiderModel.cartridgeLoaded.addCallback(self.setCartridgeInfo)
        self.mcpModel.instrumentNum.addCallback(self.setCartridgeInfo)
        self.plateDBModel.pointingInfo.addCallback(self._setAxePos)
        self.guiderModel.guideState.addCallback(self._guideStateCallback)

        # start clock updates
        self._updateClock()

        # allow the last+1 column to grow to fill the available space
        self.columnconfigure(gr.getMaxNextCol(), weight=1)

    def _guideStateCallback(self, keyVar):
        """Display guider state
        """
        state = self.guiderModel.guideState[0] or ""
        self.guideWdg.set(state.title(), isCurrent=keyVar.isCurrent)

    def _scaleFacCallback(self, keyVar):
        val = keyVar[0]
        if val is not None:
            val = (val - 1) * 1.0e6
        self.scaleWdg.set(val, keyVar.isCurrent)

    def _setAxePos(self, keyVar=None):
        """Updates ha, dec, zenith distance, airmass and plate design ha
        """
        # axePos values are: (az, alt, rot)
        axePosIsCurrent = self.tccModel.axePos.isCurrent
        az, alt = self.tccModel.axePos[0:2]

        if alt is not None:
            airmass = RO.Astro.Sph.airmass(alt)
            zd = 90.0 - alt
        else:
            airmass = None
            zd = None

        # set zd, airmass widgets
        self.zdWdg.set(zd, isCurrent=axePosIsCurrent)
        self.airmassWdg.set(airmass, isCurrent=axePosIsCurrent)

        # set hour angle (set in degrees, display in hours)
        try:
            (ha, dec), atPole = RO.Astro.Sph.haDecFromAzAlt(
                (az, alt), TUI.TCC.TelConst.Latitude)
            if atPole:
                ha = None
        except (TypeError, ValueError):
            ha = None
        self.haWdg.set(ha, isCurrent=axePosIsCurrent)

        designHA = self._getDesignHA()
        plateInfoIsCurrent = self.plateDBModel.pointingInfo.isCurrent
        self.designHAWdg.set(designHA, plateInfoIsCurrent)

        designHA = self._getDesignHA()
        if None in (ha, designHA):
            deltaHA = None
        else:
            deltaHA = (ha - designHA)
        self.deltaHAWdg.set(deltaHA,
                            isCurrent=axePosIsCurrent and plateInfoIsCurrent)

    def setCartridgeInfo(self, keyVar=None):
        """Set cartridge info based on guider and MCP.
        """
        severity = RO.Constants.sevNormal
        mcpInstNum = self.mcpModel.instrumentNum[0]
        isCurrent = self.mcpModel.instrumentNum.isCurrent
        mcpInstName = self.InstNameDict.get(mcpInstNum)
        cartridgeStr = None
        if mcpInstName:
            # known instrument that is not a cartridge;
            # ignore self.guiderModel.cartridgeLoaded and show no cartridge info
            self._cartridgeInfo = [None] * 3
            cartridgeStr = mcpInstName
        else:
            # MCP thinks a cartridge is mounted or does not know what is mounted;
            # base the output on a combination of mcp instrumentNum and guider cartridgeLoaded
            isCurrent = isCurrent and self.guiderModel.cartridgeLoaded.isCurrent
            self._cartridgeInfo = self.guiderModel.cartridgeLoaded[0:3]
            guiderInstNum = self._cartridgeInfo[0]

            # avoid dictionary lookup since -1 -> Invalid which is None but does not look up properly
            if mcpInstNum in (None, "?"):
                mcpInstName = "?"
            else:
                mcpInstName = str(mcpInstNum)

            if guiderInstNum == mcpInstNum:
                # MCP and guider agree on the loaded cartridge; output the value
                cartridgeStr = mcpInstName
            else:
                if guiderInstNum is None:
                    guiderInstName = "?"
                else:
                    guiderInstName = str(guiderInstNum)
                cartridgeStr = "%s mcp %s gdr" % (mcpInstName, guiderInstName)
                severity = RO.Constants.sevError

        self.cartridgeIDWdg.set(cartridgeStr,
                                isCurrent=isCurrent,
                                severity=severity)
        self.plateIDWdg.set(self._cartridgeInfo[1],
                            isCurrent=isCurrent,
                            severity=severity)
        self.platePointingWdg.set(self._cartridgeInfo[2],
                                  isCurrent=isCurrent,
                                  severity=severity)
        self._setAxePos()

    def _getDesignHA(self):
        for ptgInd, cartInd in ((0, 1), (1, 0), (2, 2)):
            if self.plateDBModel.pointingInfo[ptgInd] != self._cartridgeInfo[
                    cartInd]:
                return None
        return self.plateDBModel.pointingInfo[5]

    def _updateClock(self):
        """Automatically update the time displays in this widget.
        Call once to get things going
        """
        # update utc
        currPythonSeconds = RO.Astro.Tm.getCurrPySec()
        currTAITuple = time.gmtime(currPythonSeconds -
                                   RO.Astro.Tm.getUTCMinusTAI())
        self.taiWdg.set(time.strftime("%Y-%m-%d %H:%M:%S", currTAITuple))

        # update local (at APO) mean sidereal time, in degrees
        currUTCTuple = time.gmtime(currPythonSeconds)
        currUTCMJD = RO.Astro.Tm.mjdFromPyTuple(currUTCTuple)
        currLMST = RO.Astro.Tm.lmstFromUT1(
            currUTCMJD, TUI.TCC.TelConst.Longitude) * RO.PhysConst.HrsPerDeg
        self.lmstWdg.set(currLMST)

        currTAIDays = RO.Astro.Tm.taiFromPySec(currPythonSeconds)
        currSDSSMJD = int(currTAIDays + 0.3)  # assumes int truncates
        self.sjdWdg.set(currSDSSMJD)

        # schedule the next event for the next integer second plus a bit
        clockDelay = 1.01 - (currPythonSeconds % 1.0)
        self._clockTimer.start(clockDelay, self._updateClock)
コード例 #42
0
ファイル: MiscWdg.py プロジェクト: CraigLoomis/stui
    def __init__ (self, master=None, **kargs):
        """Displays miscellaneous information, such as current time and az/alt

        Inputs:
        - master        master Tk widget -- typically a frame or window
        """
        Tkinter.Frame.__init__(self, master=master, **kargs)
        self.tccModel = TUI.Models.getModel("tcc")
        self.guiderModel = TUI.Models.getModel("guider")
        self.mcpModel = TUI.Models.getModel("mcp")
        self.plateDBModel = TUI.Models.getModel("platedb")
        self._cartridgeInfo = [None]*3 # (cartID, plateID, pointing)
        self._clockTimer = Timer()
        
        gr = RO.Wdg.Gridder(self, sticky="e")

        self.haWdg = RO.Wdg.DMSLabel(
            master = self,
            precision = 0,
            nFields = 3,
            cvtDegToHrs = 1,
            width = 8,
            helpText = "Hour angle of the object",
            helpURL = _HelpURL,
        )
        gr.gridWdg("HA", self.haWdg, "hms")
        
        self.designHAWdg = RO.Wdg.DMSLabel(
            master = self,
            precision = 0,
            nFields = 3,
            cvtDegToHrs = 1,
            width = 8,
            helpText = "Hour angle the plate was designed for (from platedb)",
            helpURL = _HelpURL,
        )
        gr.gridWdg("Design HA", self.designHAWdg, "hms")
        
        self.deltaHAWdg = RO.Wdg.DMSLabel(
            master = self,
            precision = 0,
            nFields = 3,
            cvtDegToHrs = 1,
            width = 8,
            helpText = "Design - current hour angle",
            helpURL = _HelpURL,
        )
        gr.gridWdg("Des-Curr HA", self.deltaHAWdg, "hms")
        
        self.taiWdg = RO.Wdg.StrLabel(
            master = self,
            width=19,
            helpText = "International Atomic Time",
            helpURL = _HelpURL,
        )
        gr.gridWdg("TAI", self.taiWdg, colSpan=2)

        # secondary focus
        self.secFocusWdg = RO.Wdg.FloatLabel(
            master = self,
            precision = 0,
            width = 5,
            helpText = "Secondary mirror focus",
            helpURL = _HelpURL,
        )
        gr.gridWdg (
            label = "Focus",
            dataWdg = self.secFocusWdg,
            units = u"\N{MICRO SIGN}m",
        )
        self.tccModel.secFocus.addValueCallback(self.secFocusWdg.set)

        # start the second column of widgets
        gr.startNewCol(spacing=1)
        
        gr._nextCol -= 2 # allow overlap with widget to the right

        self.airmassWdg = RO.Wdg.FloatLabel(
            master = self,
            precision=3,
            width = 5,
            helpText = "Airmass",
            helpURL = _HelpURL,
        )
        gr.gridWdg("Airmass", self.airmassWdg)
        
        self.zdWdg = RO.Wdg.FloatLabel(
            master = self,
            precision = 1,
            helpText = "Zenith distance (90 - altitude)",
            helpURL = _HelpURL,
            width = 5,
        )
        gr.gridWdg("ZD", self.zdWdg, RO.StringUtil.DegStr)

        self.lmstWdg = RO.Wdg.DMSLabel(
            master = self,
            precision = 0,
            nFields = 3,
            width = 8,
            justify="right",
            helpText = "Local mean sidereal time at APO",
            helpURL = _HelpURL,
        )
        gr.gridWdg("LMST", self.lmstWdg, "hms")
        
        self.sjdWdg = RO.Wdg.IntLabel(
            master = self,
            helpText = "SDSS MJD (rolls over at TAI MJD-0.3)",
            helpURL = _HelpURL,
            width = 6,
        )
        gr.gridWdg("SJD", self.sjdWdg, "days")

        self.scaleWdg = RO.Wdg.FloatLabel(
            master = self,
            precision = 1,
            width = 8,
            helpText = "scale ((plate/nominal - 1) * 1e6); larger is higher resolution",
            helpURL = _HelpURL,
        )
        gr.gridWdg (
            label = "Scale",
            dataWdg = self.scaleWdg,
            units = "1e6",
        )
        self.tccModel.scaleFac.addCallback(self._scaleFacCallback)
        
        # start the third column of widgets
        gr.startNewCol(spacing=1)
        
        self.instNameWdg = RO.Wdg.StrLabel(
            master = self,
            width = 10,
            helpText = "Current instrument (from the TCC)",
            helpURL = _HelpURL,
        )
        gr.gridWdg("Inst", self.instNameWdg, units=False)
        self.tccModel.inst.addValueCallback(self.instNameWdg.set)
        
        self.cartridgeIDWdg = RO.Wdg.StrLabel(
            master = self,
            width = 13,
            helpText = "currently mounted cartridge (from MCP and guider)",
            helpURL = _HelpURL,
        )
        gr.gridWdg("Cartridge", self.cartridgeIDWdg)

        self.plateIDWdg = RO.Wdg.IntLabel(
            master = self,
            width = 8,
            helpText = "currently mounted plug plate (from the guider)",
            helpURL = _HelpURL,
        )
        gr.gridWdg("Plate", self.plateIDWdg)

        self.platePointingWdg = RO.Wdg.StrLabel(
            master = self,
            width = 8,
            helpText = "plug-plate pointing (from the guider)",
            helpURL = _HelpURL,
        )
        gr.gridWdg("Pointing", self.platePointingWdg)

        # state of guiding
        self.guideWdg = RO.Wdg.StrLabel(
            master = self,
            anchor = "e",
            helpText = "State of guiding",
            helpURL = _HelpURL,
        )
        gr.gridWdg (
            label = "Guiding",
            dataWdg = self.guideWdg,
            units = False,
            sticky = "ew",
        )
        
        # all widgets are gridded
        gr.allGridded()
        
        # add callbacks
        self.tccModel.axePos.addCallback(self._setAxePos)
        self.guiderModel.cartridgeLoaded.addCallback(self.setCartridgeInfo)
        self.mcpModel.instrumentNum.addCallback(self.setCartridgeInfo)
        self.plateDBModel.pointingInfo.addCallback(self._setAxePos)
        self.guiderModel.guideState.addCallback(self._guideStateCallback)
        
        # start clock updates       
        self._updateClock()
        
        # allow the last+1 column to grow to fill the available space
        self.columnconfigure(gr.getMaxNextCol(), weight=1)
コード例 #43
0
ファイル: MiscWdg.py プロジェクト: r-owen/TUI
class MiscWdg (Tkinter.Frame):
    def __init__ (self, master=None, **kargs):
        """Displays miscellaneous information, such as current time and az/alt

        Inputs:
        - master        master Tk widget -- typically a frame or window
        """
        Tkinter.Frame.__init__(self, master=master, **kargs)
        self.tccModel = TUI.TCC.TCCModel.getModel()
        self.gmechModel = TUI.Guide.GMechModel.getModel()
        
        self._clockTimer = Timer()
        
        gr = RO.Wdg.Gridder(self, sticky="e")

        # magic numbers
        AzAltRotPrec = 1    # number of digits past decimal point
        
        self.haWdg = RO.Wdg.DMSLabel(
            master = self,
            precision = 0,
            nFields = 3,
            cvtDegToHrs = 1,
            width = 8,
            helpText = "Hour angle of the object",
            helpURL = _HelpURL,
        )
        gr.gridWdg (
            label = "HA",
            dataWdg = self.haWdg,
            units = "hms",
        )
        
        self.lmstWdg = RO.Wdg.DMSLabel(
            master = self,
            precision = 0,
            nFields = 3,
            width = 8,
            justify="right",
            helpText = "Local mean sidereal time at APO",
            helpURL = _HelpURL,
        )
        gr.gridWdg (
            label = "LMST",
            dataWdg = self.lmstWdg,
            units = "hms",
        )
        
        self.utcWdg = RO.Wdg.StrLabel(
            master = self,
            width = 19,
            helpText = "Coordinated universal time",
            helpURL = _HelpURL,
        )
        gr.gridWdg (
            label = "UTC",
            dataWdg = self.utcWdg,
            colSpan = 2,
        )
        
        # start the second column of widgets
        gr.startNewCol(spacing=1)
        
        self.guideWdg = RO.Wdg.StrLabel(
            master = self,
            width = 13,
            anchor = "w",
            helpText = "State of guiding",
            helpURL = _HelpURL,
        )
        gr.gridWdg (
            label = "Guiding",
            dataWdg = self.guideWdg,
            colSpan = 4,
            units = False,
            sticky = "ew",
        )
        gr._nextCol -= 2 # allow overlap with widget to the right
        self.guideModelDict = {} # guide camera name: guide model
        for guideModel in TUI.Guide.GuideModel.modelIter():
            gcamName = guideModel.gcamName
            if gcamName.endswith("focus"):
                continue
            self.guideModelDict[guideModel.gcamName] = guideModel
            guideModel.locGuideStateSummary.addIndexedCallback(self._updGuideStateSummary, callNow=False)
        self._updGuideStateSummary()

        # airmass and zenith distance
        self.airmassWdg = RO.Wdg.FloatLabel(
            master = self,
            precision=3,
            width=5,
            helpURL = _HelpURL,
        )
        gr.gridWdg (
            label = "Airmass",
            dataWdg = self.airmassWdg,
            units = "",
        )
#       self.tccModel.axePos.addCallback(self.setAxePos)
        
        self.zdWdg = RO.Wdg.FloatLabel(
            master = self,
            precision=AzAltRotPrec,
            helpText = "Zenith distance",
            helpURL = _HelpURL,
            width=5,
        )
        gr.gridWdg (
            label = "ZD",
            dataWdg = self.zdWdg,
            units = RO.StringUtil.DegStr,
        )
        
        # start the third column of widgets
        gr.startNewCol(spacing=1)
        
        self.instNameWdg = RO.Wdg.StrLabel(
            master = self,
            width = 10,
            anchor = "w",
            helpText = "Current instrument",
            helpURL = _HelpURL,
        )
        gr.gridWdg (
            label = "Inst",
            dataWdg = self.instNameWdg,
            colSpan = 3,
            units = False,
            sticky = "w",
        )
        self.tccModel.instName.addCallback(self.updateInstName)
        
        self.secFocusWdg = RO.Wdg.FloatLabel(
            master = self,
            precision=0,
            width=5,
            helpText = "Secondary mirror focus",
            helpURL = _HelpURL,
        )
        gr.gridWdg (
            label = "Focus",
            dataWdg = self.secFocusWdg,
            units = u"\N{MICRO SIGN}m",
        )
        self.tccModel.secFocus.addROWdg(self.secFocusWdg)
        
        self.gcFocusWdg = RO.Wdg.FloatLabel(
            master = self,
            precision=0,
            width=5,
            helpText = "NA2 guide camera focus",
            helpURL = _HelpURL,
        )
        gr.gridWdg (
            label = "GC Focus",
            dataWdg = self.gcFocusWdg,
            units = u"\N{MICRO SIGN}m",
        )
        self.gmechModel.focus.addROWdg(self.gcFocusWdg)
        
        # all widgets are gridded
        gr.allGridded()
        
        # add callbacks that deal with multiple widgets
        self.tccModel.axePos.addCallback(self.setAxePos)
        
        # start clock updates       
        self.updateClock()
        
        # allow the last+1 column to grow to fill the available space
        self.columnconfigure(gr.getMaxNextCol(), weight=1)

    def updateInstName(self, *args, **kwargs):
        instName, isCurrent = self.tccModel.instName.getInd(0)
        if instName == "?":
            severity = RO.Constants.sevError
        else:
            severity = RO.Constants.sevNormal
        self.instNameWdg.set(instName, severity=severity, isCurrent=isCurrent)
    
    def updateClock(self):
        """Automatically update the time displays in this widget.
        Call once to get things going
        """
        # update utc
        currPythonSeconds = RO.Astro.Tm.getCurrPySec()
        currUTCTuple= time.gmtime(currPythonSeconds)
        self.utcWdg.set(time.strftime("%Y-%m-%d %H:%M:%S", currUTCTuple))
        currUTCMJD = RO.Astro.Tm.mjdFromPyTuple(currUTCTuple)
    
        # update local (at APO) mean sidereal time, in degrees
        currLMST = RO.Astro.Tm.lmstFromUT1(currUTCMJD, TUI.TCC.TelConst.Longitude) * RO.PhysConst.HrsPerDeg
        self.lmstWdg.set(currLMST)
        
        # schedule the next event
        clockDelay = 1.01 - (currPythonSeconds % 1.0)
        self._clockTimer.start(clockDelay, self.updateClock)
    
    def setAxePos(self, axePos, isCurrent=True, keyVar=None):
        """Updates ha, dec, zenith distance and airmass
        axePos values are: (az, alt, rot)
        """
        az, alt = axePos[0:2]

        if alt is not None:
            airmass = RO.Astro.Sph.airmass(alt)
            zd = 90.0 - alt
        else:
            airmass = None
            zd = None
        
        # set zd, airmass widgets
        self.zdWdg.set(zd, isCurrent=isCurrent)
        self.airmassWdg.set(airmass, isCurrent=isCurrent)
        
        # set hour angle (set in degrees, display in hours)
        try:
            (ha, dec), atPole = RO.Astro.Sph.haDecFromAzAlt((az, alt), TUI.TCC.TelConst.Latitude)
            if atPole:
                ha = None
        except (TypeError, ValueError):
            ha = None
        self.haWdg.set(ha, isCurrent=isCurrent)
    
    def _updGuideStateSummary(self, *args, **kargs):
        """Check state of all guiders.
        Display "best" state as follows:
        - is current and not off
        - is current and off
        - not current and not off
        - not current and off
        """
        stateInfo = [] # each element = (is current, not off, state str, actor)
        for gcamName, guideModel in self.guideModelDict.iteritems():
            state, isCurr = guideModel.guideState.getInd(0)
            if state is None:
                stateLow = ""
            else:
                stateLow = state.lower()
            notOff = stateLow != "off"
            stateInfo.append((isCurr, notOff, stateLow, gcamName))
        stateInfo.sort()
        bestCurr, bestNotOff, bestStateLow, bestActor = stateInfo[-1]
        if bestStateLow in ("on", "off"):
            severity = RO.Constants.sevNormal
        else:
            severity = RO.Constants.sevWarning
        if bestStateLow in ("", "off"):
            stateText = bestStateLow.title()
        else:
            stateText = "%s %s" % (bestStateLow.title(), bestActor)
        self.guideWdg.set(stateText, isCurrent = bestCurr, severity = severity)
コード例 #44
0
class AxisStatusWdg(Tkinter.Frame):
    def __init__ (self, master=None, **kargs):
        """Displays information about the axes

        Inputs:
        - master        master Tk widget -- typically a frame or window
        """
        Tkinter.Frame.__init__(self, master=master, **kargs)
        self.tccModel = TUI.Models.getModel("tcc")
        self.prevSounds = [None]*3 # sounds played last time we received AxisCmdState
        self.prevCtrlStatusOK = [None]*3
        self.ctrlBadTime = 0 # time of last "controller bad" sound
        self._soundTimer = Timer()

        # magic numbers
        PosPrec = 3 # number of digits past decimal point
        PosWidth = 5 + PosPrec  # assumes -999.99... degrees is longest field
        AxisCmdStateWidth = 8
        AxisErrCodeWidth = 13
        CtrlStatusWidth = 25

        # commanded state dictionary:
        # - keys are axis commanded state keywords, cast to lowercase
        # - values are the severity
        self._CmdStateDict = {
            "Drifting": RO.Constants.sevWarning,
            "Halted": RO.Constants.sevError,
            "Halting": RO.Constants.sevError,
            "Slewing": RO.Constants.sevWarning,
            "Tracking": RO.Constants.sevNormal,
            "NotAvailable": RO.Constants.sevNormal,
        }

        self.axisInd = range(len(self.tccModel.axisNames))

        # actual axis position widget set
        self.axePosWdgSet = [
            RO.Wdg.FloatLabel(
                master = self,
                precision = PosPrec,
                width = PosWidth,
                helpText = "Current axis position, as reported by the controller",
                helpURL = _HelpURL,
            )
            for axis in self.axisInd
        ]
        self.tccModel.axePos.addValueListCallback([wdg.set for wdg in self.axePosWdgSet])

        # target axis position widget set
        self.tccPosWdgSet = [
            RO.Wdg.FloatLabel(
                master = self,
                precision = PosPrec,
                width = PosWidth,
                helpText = "Target axis position",
                helpURL = _HelpURL,
            )
            for axis in self.axisInd
        ]
        self.tccModel.tccPos.addValueListCallback([wdg.set for wdg in self.tccPosWdgSet])

        # TCC status widget set (e.g. tracking or halted)
        self.axisCmdStateWdgSet = [
            RO.Wdg.StrLabel(
                master = self,
                width = AxisCmdStateWidth,
                helpText = "What the TCC is telling the axis to do",
                helpURL = _HelpURL,
                anchor = "nw",
            )
            for axis in self.axisInd
        ]
        self.tccModel.axisCmdState.addCallback(self._axisCmdStateCallback)
        self.tccModel.pleaseSlew.addCallback(self._pleaseSlewStateCallback)

        self.tccModel.rotExists.addCallback(self._rotExistsCallback)

        # axis error code widet set (why the TCC is not moving the axis)
        self.axisErrCodeWdgSet = [
            RO.Wdg.StrLabel(
                master = self,
                width = AxisErrCodeWidth,
                helpText = "Why the TCC halted the axis",
                helpURL = _HelpURL,
                anchor = "nw",
            )
            for axis in self.axisInd
        ]
        self.tccModel.axisErrCode.addValueListCallback([wdg.set for wdg in self.axisErrCodeWdgSet])

        # controller status widget set (the status word)
        self.ctrlStatusWdgSet = [
            RO.Wdg.StrLabel(
                master = self,
                width = CtrlStatusWidth,
                helpText = "Status reported by the axis controller",
                helpURL = _HelpURL,
                anchor = "nw",
            )
            for axis in self.axisInd
        ]

        # handle Az/Alt/RotCtrlStatus
        for axisInd, axisName in enumerate(self.tccModel.axisNames):
            statusVar = getattr(self.tccModel, axisName.lower() + "Stat")
            statusVar.addCallback(RO.Alg.GenericCallback(self._ctrlStatusCallback, axisInd))

        # grid the axis widgets
        gr = RO.Wdg.Gridder(self, sticky="w")
        for axis in self.axisInd:
            unitsLabel1 = Tkinter.Label(self, text=RO.StringUtil.DegStr)
            unitsLabel2 = Tkinter.Label(self, text=RO.StringUtil.DegStr)
            if axis == 2:
                self.rotUnitsLabel1 = unitsLabel1
                self.rotUnitsLabel2 = unitsLabel2
            gr.gridWdg (
                label = self.tccModel.axisNames[axis],
                dataWdg = (
                    self.axePosWdgSet[axis],
                    unitsLabel1,
                    self.tccPosWdgSet[axis],
                    unitsLabel2,
                    self.axisCmdStateWdgSet[axis],
                    # self.axisErrCodeWdgSet[axis],
                    # self.ctrlStatusWdgSet[axis],
                )
            )

        # widen rotator commanded state widget
        # so there's room to display "NotAvailable"
        # (note that the error code widget will be hidden when this occurs
        # so the text will not overlap anything).
        rotCmdWdg = self.axisCmdStateWdgSet[2]
        rotCmdWdg.grid_configure(columnspan=2)
        rotCmdWdg["width"] = 12

        # allow the last column to grow to fill the available space
        self.columnconfigure(gr.getMaxNextCol(), weight=1)

    def _axisCmdStateCallback(self, keyVar):
        if not keyVar.isCurrent:
            for wdg in self.axisCmdStateWdgSet:
                wdg.setIsCurrent(False)
            return

        axisCmdState = keyVar.valueList

        # set axis commanded state widgets
        for axis in self.axisInd:
            cmdState = axisCmdState[axis]
            severity = self._CmdStateDict.get(cmdState, RO.Constants.sevError)
            self.axisCmdStateWdgSet[axis].set(cmdState, severity=severity)

        # play sounds, if appropriate
        indSoundsToPlay = set() # add new sounds to play to a set to avoid duplicates
        for axis in self.axisInd:
            soundInd, soundFunc = _StateIndSoundDict.get(axisCmdState[axis].lower(), (0, None))
            if soundFunc and (soundFunc != self.prevSounds[axis]) and keyVar.isGenuine:
                indSoundsToPlay.add((soundInd, soundFunc))
            self.prevSounds[axis] = soundFunc

        if indSoundsToPlay:
            indSoundsToPlay = list(indSoundsToPlay)
            indSoundsToPlay.sort()
            soundsToPlay = list(zip(*indSoundsToPlay)[1])
            soundsToPlay.reverse() # since played from back to front
            self._playSounds(soundsToPlay)

    def _pleaseSlewStateCallback(self, keyVar):
        if keyVar.valueList[0]:
            self._playSounds([TUI.PlaySound.pleaseSlew])


    def _ctrlStatusCallback(self, axisInd, keyVar):
#        print "_ctrlStatusCallback(axisInd=%s, keyVar=%s)" % (axisInd, keyVar)
        if axisInd == 2 and not self.tccModel.rotExists[0]:
            # rotator does not exist; this is handled by _rotExistsCallback
            return

        isCurrent = keyVar.isCurrent
        statusWord = keyVar[3]
        statusOK = True

        ctrlStatusWdg = self.ctrlStatusWdgSet[axisInd]

        if statusWord is not None:
            infoList = RO.BitDescr.getDescr(_BitInfo, statusWord)

            # for now simply show the first status;
            # eventually provide a pop-up list showing all status bits
            if infoList:
                info, severity = infoList[0]
                ctrlStatusWdg.set(info, isCurrent, severity=severity)
                if severity == RO.Constants.sevError:
                    statusOK = False
            else:
                ctrlStatusWdg.set("", isCurrent, severity=RO.Constants.sevNormal)
        elif isCurrent:
            ctrlStatusWdg.set("Not responding", isCurrent=isCurrent, severity=RO.Constants.sevError)
            statusOK = False
        else:
            ctrlStatusWdg.setNotCurrent()

        statusNewlyBad = (self.prevCtrlStatusOK[axisInd] and not statusOK)
        self.prevCtrlStatusOK[axisInd] = statusOK

        if statusNewlyBad and keyVar and keyVar.isGenuine \
            and (time.time() - self.ctrlBadTime > _CtrllrWaitSec):
            TUI.PlaySound.axisHalt()
            self.ctrlBadTime = time.time()

    def _rotExistsCallback(self, keyVar):
        if not keyVar.isCurrent:
            return
        rotExists = keyVar[0]
        if rotExists:
            self.rotUnitsLabel1.grid()
            self.rotUnitsLabel2.grid()
            self.axisErrCodeWdgSet[2].grid()
            self.ctrlStatusWdgSet[2].grid()
            self.ctrlStatusWdgSet[2].set("", severity=RO.Constants.sevNormal)
        else:
            self.rotUnitsLabel1.grid_remove()
            self.rotUnitsLabel2.grid_remove()
            self.axisErrCodeWdgSet[2].grid_remove()
            self.ctrlStatusWdgSet[2].grid_remove()

    def _playSounds(self, sounds):
        """Play one or more of a set of sounds; played in order from last to first.
        """
        if not sounds:
            return
        soundFunc = sounds.pop(-1)
        soundFunc()
        if sounds:
            self._soundTimer.start(_SoundInterval, self.playSounds, sounds)
コード例 #45
0
ファイル: BalloonHelp.py プロジェクト: astromaddie/RO-py3
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()
コード例 #46
0
    def __init__ (self, master=None, **kargs):
        """Displays information about the axes

        Inputs:
        - master        master Tk widget -- typically a frame or window
        """
        Tkinter.Frame.__init__(self, master=master, **kargs)
        self.tccModel = TUI.Models.getModel("tcc")
        self.prevSounds = [None]*3 # sounds played last time we received AxisCmdState
        self.prevCtrlStatusOK = [None]*3
        self.ctrlBadTime = 0 # time of last "controller bad" sound
        self._soundTimer = Timer()

        # magic numbers
        PosPrec = 3 # number of digits past decimal point
        PosWidth = 5 + PosPrec  # assumes -999.99... degrees is longest field
        AxisCmdStateWidth = 8
        AxisErrCodeWidth = 13
        CtrlStatusWidth = 25

        # commanded state dictionary:
        # - keys are axis commanded state keywords, cast to lowercase
        # - values are the severity
        self._CmdStateDict = {
            "Drifting": RO.Constants.sevWarning,
            "Halted": RO.Constants.sevError,
            "Halting": RO.Constants.sevError,
            "Slewing": RO.Constants.sevWarning,
            "Tracking": RO.Constants.sevNormal,
            "NotAvailable": RO.Constants.sevNormal,
        }

        self.axisInd = range(len(self.tccModel.axisNames))

        # actual axis position widget set
        self.axePosWdgSet = [
            RO.Wdg.FloatLabel(
                master = self,
                precision = PosPrec,
                width = PosWidth,
                helpText = "Current axis position, as reported by the controller",
                helpURL = _HelpURL,
            )
            for axis in self.axisInd
        ]
        self.tccModel.axePos.addValueListCallback([wdg.set for wdg in self.axePosWdgSet])

        # target axis position widget set
        self.tccPosWdgSet = [
            RO.Wdg.FloatLabel(
                master = self,
                precision = PosPrec,
                width = PosWidth,
                helpText = "Target axis position",
                helpURL = _HelpURL,
            )
            for axis in self.axisInd
        ]
        self.tccModel.tccPos.addValueListCallback([wdg.set for wdg in self.tccPosWdgSet])

        # TCC status widget set (e.g. tracking or halted)
        self.axisCmdStateWdgSet = [
            RO.Wdg.StrLabel(
                master = self,
                width = AxisCmdStateWidth,
                helpText = "What the TCC is telling the axis to do",
                helpURL = _HelpURL,
                anchor = "nw",
            )
            for axis in self.axisInd
        ]
        self.tccModel.axisCmdState.addCallback(self._axisCmdStateCallback)
        self.tccModel.pleaseSlew.addCallback(self._pleaseSlewStateCallback)

        self.tccModel.rotExists.addCallback(self._rotExistsCallback)

        # axis error code widet set (why the TCC is not moving the axis)
        self.axisErrCodeWdgSet = [
            RO.Wdg.StrLabel(
                master = self,
                width = AxisErrCodeWidth,
                helpText = "Why the TCC halted the axis",
                helpURL = _HelpURL,
                anchor = "nw",
            )
            for axis in self.axisInd
        ]
        self.tccModel.axisErrCode.addValueListCallback([wdg.set for wdg in self.axisErrCodeWdgSet])

        # controller status widget set (the status word)
        self.ctrlStatusWdgSet = [
            RO.Wdg.StrLabel(
                master = self,
                width = CtrlStatusWidth,
                helpText = "Status reported by the axis controller",
                helpURL = _HelpURL,
                anchor = "nw",
            )
            for axis in self.axisInd
        ]

        # handle Az/Alt/RotCtrlStatus
        for axisInd, axisName in enumerate(self.tccModel.axisNames):
            statusVar = getattr(self.tccModel, axisName.lower() + "Stat")
            statusVar.addCallback(RO.Alg.GenericCallback(self._ctrlStatusCallback, axisInd))

        # grid the axis widgets
        gr = RO.Wdg.Gridder(self, sticky="w")
        for axis in self.axisInd:
            unitsLabel1 = Tkinter.Label(self, text=RO.StringUtil.DegStr)
            unitsLabel2 = Tkinter.Label(self, text=RO.StringUtil.DegStr)
            if axis == 2:
                self.rotUnitsLabel1 = unitsLabel1
                self.rotUnitsLabel2 = unitsLabel2
            gr.gridWdg (
                label = self.tccModel.axisNames[axis],
                dataWdg = (
                    self.axePosWdgSet[axis],
                    unitsLabel1,
                    self.tccPosWdgSet[axis],
                    unitsLabel2,
                    self.axisCmdStateWdgSet[axis],
                    # self.axisErrCodeWdgSet[axis],
                    # self.ctrlStatusWdgSet[axis],
                )
            )

        # widen rotator commanded state widget
        # so there's room to display "NotAvailable"
        # (note that the error code widget will be hidden when this occurs
        # so the text will not overlap anything).
        rotCmdWdg = self.axisCmdStateWdgSet[2]
        rotCmdWdg.grid_configure(columnspan=2)
        rotCmdWdg["width"] = 12

        # allow the last column to grow to fill the available space
        self.columnconfigure(gr.getMaxNextCol(), weight=1)
コード例 #47
0
ファイル: AxisStatus.py プロジェクト: r-owen/TUI
    def __init__ (self, master=None, **kargs):
        """Displays information about the axes

        Inputs:
        - master        master Tk widget -- typically a frame or window
        """
        Tkinter.Frame.__init__(self, master=master, **kargs)
        self.tccModel = TUI.TCC.TCCModel.getModel()
        self.prevSounds = [None]*3 # sounds played last time we received AxisCmdState
        self.prevCtrlStatusOK = [None]*3
        self.ctrlBadTime = 0 # time of last "controller bad" sound
        self._soundTimer = Timer()

        # magic numbers
        PosPrec = 1 # number of digits past decimal point
        PosWidth = 5 + PosPrec  # assumes -999.99... degrees is longest field
        AxisCmdStateWidth = 8
        AxisErrCodeWidth = 13
        CtrlStatusWidth = 25

        self.axisInd = range(len(self.tccModel.axisNames))
        
        # actual axis position widget set
        self.axePosWdgSet = [
            RO.Wdg.FloatLabel(
                master = self,
                precision = PosPrec,
                width = PosWidth,
                helpText = "Current axis position, as reported by the controller",
                helpURL = _HelpURL,
            )
            for axis in self.axisInd
        ]
        self.tccModel.axePos.addROWdgSet(self.axePosWdgSet)

        # target axis position widget set
        self.tccPosWdgSet = [
            RO.Wdg.FloatLabel(
                master = self,
                precision = PosPrec,
                width = PosWidth,
                helpText = "Target axis position",
                helpURL = _HelpURL,
            )
            for axis in self.axisInd
        ]
        self.tccModel.tccPos.addROWdgSet(self.tccPosWdgSet)
        
        # TCC status widget set (e.g. tracking or halted)
        self.axisCmdStateWdgSet = [
            RO.Wdg.StrLabel(
                master = self,
                width = AxisCmdStateWidth,
                helpText = "What the TCC is telling the axis to do",
                helpURL = _HelpURL,
                anchor = "nw",
            )
            for axis in self.axisInd
        ]
        self.tccModel.axisCmdState.addCallback(self.setAxisCmdState)
    
        self.tccModel.rotExists.addIndexedCallback(self.setRotExists)
        
        # axis error code widet set (why the TCC is not moving the axis)
        self.axisErrCodeWdgSet = [
            RO.Wdg.StrLabel(
                master = self,
                width = AxisErrCodeWidth,
                helpText = "Why the TCC halted the axis",
                helpURL = _HelpURL,
                anchor = "nw",
            )
            for axis in self.axisInd
        ]
        self.tccModel.axisErrCode.addROWdgSet(self.axisErrCodeWdgSet)       
    
        # controller status widget set (the status word)
        self.ctrlStatusWdgSet = [
            RO.Wdg.StrLabel(
                master = self,
                width = CtrlStatusWidth,
                helpText = "Status reported by the axis controller",
                helpURL = _HelpURL,
                anchor = "nw",
            )
            for axis in self.axisInd
        ]
        for axis in self.axisInd:
            self.tccModel.ctrlStatusSet[axis].addIndexedCallback(
                RO.Alg.GenericCallback(self.setCtrlStatus, axis), 3)
        
                
        # grid the axis widgets
        gr = RO.Wdg.Gridder(self, sticky="w")
        for axis in self.axisInd:
            unitsLabel1 = Tkinter.Label(self, text=RO.StringUtil.DegStr)
            unitsLabel2 = Tkinter.Label(self, text=RO.StringUtil.DegStr)
            if axis == 2:
                self.rotUnitsLabel1 = unitsLabel1
                self.rotUnitsLabel2 = unitsLabel2
            gr.gridWdg (
                label = self.tccModel.axisNames[axis],
                dataWdg = (
                    self.axePosWdgSet[axis],
                    unitsLabel1,
                    self.tccPosWdgSet[axis],
                    unitsLabel2,
                    self.axisCmdStateWdgSet[axis],
                    self.axisErrCodeWdgSet[axis],
                    self.ctrlStatusWdgSet[axis],
                )
            )
        
        # widen rotator commanded state widget
        # so there's room to display "NotAvailable"
        # (note that the error code widget will be hidden when this occurs
        # so the text will not overlap anything).
        rotCmdWdg = self.axisCmdStateWdgSet[2]
        rotCmdWdg.grid_configure(columnspan=2)
        rotCmdWdg["width"] = 12

        # allow the last column to grow to fill the available space
        self.columnconfigure(gr.getMaxNextCol(), weight=1)
コード例 #48
0
class SkyWdg(Tkinter.Frame):
    TELCURRENT = "telCurrent"
    TELTARGET = "telTarget"
    TELPOTENTIAL = "telPotential"
    CATOBJECT = "catObject"
    AzWrapSpiralDRad = 10
    AzWrapItemRad = 3
    AzWrapMargin = 5
    AzAltMargin = 10

    def __init__(self, master, width=201, height=201):
        Tkinter.Frame.__init__(self, master)

        self.tuiModel = TUI.TUIModel.getModel()
        self.tccModel = TUI.TCC.TCCModel.getModel()
        self.userModel = TUI.TCC.UserModel.getModel()

        # instance variables:
        # center: position of center of canvas, in pixels
        # size: size of canvas, in pixels
        # scale: scale of canvas, in pixels per deg
        self.currCatObjID = None

        self._telPotentialAnimTimer = Timer()

        self.eastLabelPos = AzAltTarget(azAlt=(90, 0))
        self.northLabelPos = AzAltTarget(azAlt=(180, 0))

        # pane on which to display current star info
        self.currStarDisp = RO.Wdg.StatusBar(master=self)
        self.currStarDisp.grid(row=1, column=0, sticky="ew")
        self.currStarMsgID = None

        # canvas on which to display stars
        self.cnv = Tkinter.Canvas(
            master=self,
            width=width,
            height=height,
            #           background='black',
            selectborderwidth=0,
            highlightthickness=0)
        self.cnv.grid(row=0, column=0, sticky="nsew")
        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure(0, weight=1)
        RO.Wdg.addCtxMenu(
            wdg=self.cnv,
            helpURL=_HelpURL,
        )

        # thickness of canvas border;
        # drawable width/height = winfo_width/height - (2 * border)
        self.cnvBorderWidth = int(self.cnv["highlightthickness"]) + int(
            self.cnv["selectborderwidth"])
        self.cnv.bind('<Configure>', self._configureEvt)
        # self.cnv.tag_bind('star', '<Enter>', self._enterStar)
        # self.cnv.tag_bind('star', '<Leave>', self._leaveStar)
        self.cnv.bind('<Motion>', self._enterStar)
        self.cnv.bind('<Leave>', self._leaveStar)
        # the following prevents the display from blanking
        # when the button is pressed once (I tried trapping and
        # discarding <Button>, as a faster solutionn, but it didn't work)
        self.cnv.bind('<Button>', self._enterStar)
        self.cnv.bind('<Double-Button-1>', self._setPotential)
        self.center = [None, None]
        self.size = [None, None]
        self.azAltRad = None
        self.azAltScale = None
        self.sizeDeg = [180.0, 180.0]

        # various dictionaries whose keys are catalog name
        # note: if a catalog is deleted, it is removed from catDict
        # and catPixPosObjDict, but not necessarily the others
        self.catDict = {}  # key=catalog name, value = catalog
        self.catRedrawTimerDict = {}  # key=catalog name, value = tk after id
        self.catColorDict = {}  # key=catalog name, value = color
        self.catPixPosObjDict = {
        }  # key=catalog name, value = list of (pix pos, obj) pairs
        self.catSRDict = {
        }  # key=catalog name, value = scriptrunner script to redisplay catalog

        self.telCurrent = None
        self.telTarget = None
        self.telPotential = None
        self.azWrapGauge = RO.CanvasUtil.Spiral(
            cnv=self.cnv,
            xctr=1,
            yctr=1,
            begRad=0,
            endRad=0,  # not yet ready to draw; canvas size unknown
            angScale=-1.0,
            angOff=-90.0,
        )
        self._setSize()

        # set up automatic update of current and target telescope position
        self.tccModel.axePos.addCallback(self.setTelCurrent)
        self.tccModel.tccPos.addCallback(self.setTelTarget)
        self.tccModel.azLim.addCallback(self.setAzLim)

        self.userModel.potentialTarget.addCallback(self.setTelPotential)
        self.userModel.userCatDict.addCallback(self._updUserCatDict)

    def _configureEvt(self, event=None):
        """Handle the <Configure> event.
        """
        self._setSize()
        self.redraw()

    def _setPotential(self, event):
        xyPix = (event.x, event.y)
        catObj = self.findNearestStar(xyPix, maxDistSq=25.0)
        if catObj != None:
            self.userModel.potentialTarget.set(catObj)

            # note: evaluate when needed instead of at init
            # to make sure the window has been generated
            slewWin = self.tuiModel.tlSet.getToplevel("TCC.Slew")
            if slewWin:
                slewWin.makeVisible()
                slewWin.focus_set()

    def _enterStar(self, event):
        xyPix = (event.x, event.y)
        catObj = self.findNearestStar(xyPix, maxDistSq=25.0)
        if catObj:
            self.currStarMsgID = self.currStarDisp.setMsg(
                msgStr=str(catObj),
                isTemp=True,
            )
        else:
            self.currStarMsgID = self.currStarDisp.clearTempMsg(
                self.currStarMsgID)

    def _leaveStar(self, event):
        self.currStarMsgID = self.currStarDisp.clearTempMsg(self.currStarMsgID)
        self.currCatObjID = None

    def _setSize(self):
        # size and center of canvas
        self.size[0] = self.cnv.winfo_width() - (2 * self.cnvBorderWidth)
        self.size[1] = self.cnv.winfo_height() - (2 * self.cnvBorderWidth)
        winRad = min(self.size) / 2
        for ind in range(2):
            self.center[ind] = (self.size[ind] / 2) + self.cnvBorderWidth

        # radius allocated to az/alt display
        self.azAltRad = winRad - (SkyWdg.AzAltMargin + SkyWdg.AzWrapItemRad + \
            SkyWdg.AzWrapSpiralDRad + SkyWdg.AzWrapItemRad + SkyWdg.AzWrapMargin)
        self.azAltRad = max(self.azAltRad, 0)
        self.azAltScale = self.azAltRad / 90.0

        # azWrapGauge geometry; beg and end radius only refer to the spiral;
        # AzWrapItemRad provides additional room for the items on the spiral
        begRad = self.azAltRad + SkyWdg.AzAltMargin + SkyWdg.AzWrapItemRad
        begRad = min(max(begRad, 0), winRad)
        endRad = winRad - SkyWdg.AzWrapItemRad - SkyWdg.AzWrapItemRad
        endRad = max(0, begRad, endRad)
        self.azWrapGauge.setGeom(
            xctr=self.center[0],
            yctr=self.center[1],
            begRad=begRad,
            endRad=endRad,
            redraw=0,
        )
#       print "_setSize called; winRad, azAltRad, begRad, endRad=", winRad, self.azAltRad, begRad, endRad

    def _printInfo(self):
        print "SkyWdg"
        print "size   = ", self.size, " pixels (excluding border)"
        print "center = ", self.center, " pixels"
        print "scale  = ", self.azAltScale, " pixels/deg"
        print "border = ", self.cnvBorderWidth, " pixels"

    def setAzLim(self, azLim, isCurrent=True, **kargs):
        """Sets the azimuth limits: minPos, maxPos and other values which are ignored"""
        if not isCurrent:
            return
        self.azWrapGauge.setAngLim(azLim[0], azLim[1], redraw=True)
        self._drawTelCurrent()
        self._drawTelPotential()
        self._drawTelTarget()

    def setTelCurrent(self, azAlt, isCurrent=True, **kargs):
        """Sets the telescope's current position.
        May be used as a keyword variable callback.

        Inputs:
            azAlt: az/alt position, in degrees (extra elements are ignored);
                use None if value is explicitly unknown (NaN)
            isCurrent: the data is current (up-to-date)
        """
        #       print "SkyWdg.setTelCurrent: az,alt =", azAlt
        if None in azAlt[0:2]:
            self.telCurrent = None
        else:
            self.telCurrent = AzAltTarget(azAlt[0:2])
        self._drawTelCurrent()

    def setTelTarget(self, azAlt, isCurrent=True, **kargs):
        """Sets the telescope's target position.
        May be used as a keyword variable callback.

        Inputs:
            azAlt: az/alt position, in degrees (extra elements are ignored);
                use None if value is explicitly unknown (NaN)
            isCurrent: the data is current (up-to-date)
        """
        #       print "SkyWdg.setTelTarget: az,alt =", azAlt
        if None in azAlt[0:2]:
            self.telTarget = None
        else:
            self.telTarget = AzAltTarget(azAlt[0:2])
        self._drawTelTarget()

    def setTelPotential(self, telTarget=None):
        """Sets or clears the telescope's potential position.
        
        Note: unlike setTelCurrent and setTelTarget;
        the telescope potential position may be a mean position.
        Hence the argument list is different (this function is not
        set up to be a keyword variable callback)

        Inputs:
        - telTarget: a TUI.TCC.TelTarget object; if None,
          the current position (if any) is cleared
        """
        #       print "SkyWdg.setTelPotential(%s)" % (telTarget,)
        self.telPotential = telTarget
        self._drawTelPotential()
        if telTarget:
            msgStr = str(telTarget)
        else:
            msgStr = ""
        self.currStarDisp.setMsg(
            msgStr=msgStr,
            isTemp=False,
        )

    def addCatalog(self, catalog):
        """Add a new catalog with a given name.
        If the catalog already exists, it is deleted.
        """
        #       print "addCatalog %r" % (catalog.name,)
        catName = catalog.name

        if catName in self.catDict:
            self.removeCatalogByName(catName)

        self.catDict[catName] = catalog
        self.catPixPosObjDict[catName] = []
        self.catRedrawTimerDict[catName] = Timer()
        self.catColorDict[catName] = catalog.getDispColor()

        def updateCat(sr, self=self, catalog=catalog):
            catName = catalog.name

            catTag = "cat_%s" % (catName, )

            if not catalog.getDoDisplay():
                self.catPixPosObjDict[catName] = []
                self.cnv.delete(catTag)
                return

            # if color has changed, update it
            color = catalog.getDispColor()
            oldColor = self.catColorDict.get(catName)
            if color != oldColor:
                self.cnv.itemconfigure(catTag, fill=color, outline=color)
                self.catColorDict[catName] = color

#           print "compute %s thread starting" % catName
            yield sr.waitThread(_UpdateCatalog, catalog.objList, self.center,
                                self.azAltScale)
            pixPosObjList = sr.value
            #           print "compute %s thread done" % catName

            catName = catalog.name
            catTag = "cat_%s" % (catName, )

            self.catPixPosObjDict[catName] = []
            self.cnv.delete(catTag)

            color = catalog.getDispColor()
            rad = 2  # for now, eventually may wish to vary by magnitude or window size or...?
            for pixPos, obj in pixPosObjList:
                self.cnv.create_oval(
                    pixPos[0] - rad,
                    pixPos[1] - rad,
                    pixPos[0] + rad + 1,
                    pixPos[1] + rad + 1,
                    tag=(SkyWdg.CATOBJECT, catTag),
                    fill=color,
                    outline=color,
                )
            self.catPixPosObjDict[catName] = pixPosObjList

            self.catRedrawTimerDict[catName].start(_CatRedrawDelay,
                                                   self._drawCatalog, catalog)

        sr = RO.ScriptRunner.ScriptRunner(
            runFunc=updateCat,
            name="updateCatalog",
        )

        self.catSRDict[catName] = sr
        catalog.addCallback(self._drawCatalog, callNow=True)

    def removeCatalogByName(self, catName):
        """Remove the specified catalog.
        """
        #       print "removeCatalogByName %r" % (catName,)
        try:
            cat = self.catDict.pop(catName)
        except KeyError:
            raise RuntimeError("Catalog %r not found" % (catName, ))

        cat.removeCallback(self._drawCatalog, doRaise=False)
        catTag = "cat_%s" % (catName, )
        self.cnv.delete(catTag)

        # cancel script runner and delete entry
        try:
            sr = self.catSRDict.pop(catName)
            #           print "removeCatalogByName cancelling and deleting update script for %r" % catName
            sr.cancel()
        except KeyError:
            pass

        # cancel pending wakeup and delete entry
        try:
            timer = self.catRedrawTimerDict.pop(catName)
        except KeyError:
            pass
        timer.cancel()

        # delete entry in other catalog dictionaries
        for catDict in self.catPixPosObjDict, self.catColorDict:
            try:
                del catDict[catName]
            except KeyError:
                pass

    def findNearestStar(self, xyPix, maxDistSq=9.0e99):
        """Finds the catalog object nearest to xyPix, but only if
        the squared distance is within maxDistSq deg^2
        Returns the catalog object, or None if none found"""
        minStar = None
        minDistSq = maxDistSq
        for pixPosCatObjList in self.catPixPosObjDict.itervalues():
            for objPixPos, catObj in pixPosCatObjList:
                distSq = (objPixPos[0] - xyPix[0])**2 + (objPixPos[1] -
                                                         xyPix[1])**2
                if distSq < minDistSq:
                    minStar = catObj
                    minDistSq = distSq
        return minStar

    def pixFromAzAlt(self, azAlt):
        """Convert a point from az,alt degrees (0 south, 90 east)
        to x,y pixels, such that east is to the left and north is up.
        """
        return self.pixFromDeg(xyDegFromAzAlt(azAlt))

    def azAltFromPix(self, xyPix):
        """Convert a point from x,y pixels to az,alt degrees (0 south, 90 east)
        such that east is to the left and north is up.
        """
        return azAltFromXYDeg(self.degFromPix(xyPix))

    def pixFromDeg(self, xyDeg):
        """convert a point from x,y degrees (x/east left, y/north up)
        to x,y pixels, Tk style (x right, y down).
        """
        xyPix = (
            self.center[0] + (xyDeg[0] * -self.azAltScale),
            self.center[1] + (xyDeg[1] * -self.azAltScale),
        )
        return xyPix

    def degFromPix(self, xyPix):
        """converts a point from x,y pixels, Tk style (x right, y down)
        to x,y degrees (x/east left, y/north up)
        """
        xyDeg = (
            (xyPix[0] - self.center[0]) / -self.azAltScale,
            (xyPix[1] - self.center[1]) / -self.azAltScale,
        )
        return xyDeg

    def _updUserCatDict(self, userCatDict):
        """Called when the userCatDict is updated.
        userCatDict is a dictionary of catalog name:TelTarget.Catalog
        """
        # delete any missing catalogs
        initialCatNames = self.catDict.keys()
        for catName in initialCatNames:
            if catName not in userCatDict:
                self.removeCatalogByName(catName)

        # add any new or changed catalogs
        # (adding deletes any existing copy)
        for catName, cat in userCatDict.iteritems():
            currCat = self.catDict.get(catName)
            if not currCat or currCat != cat or len(currCat.objList) != len(
                    cat.objList):
                self.addCatalog(cat)


# drawing methods

    def redraw(self):
        """Redraw everything using last recorded geometry info.
        If window size has changed, call _setSize first.
        """
        #       print "draw called"
        #       self._printInfo()
        # clear canvas
        self.cnv.delete('all')

        # draw everything
        self._drawGrid()
        self._drawLabels()
        self.azWrapGauge.draw()
        self._drawTelCurrent()
        self._drawTelTarget()
        self._drawAllCatalogs()

    def _drawAllCatalogs(self):
        """Draw all objects in all catalogs, erasing all stars first.
        """
        self.catPixPosObjDict = {}
        self.cnv.delete(SkyWdg.CATOBJECT)
        for catalog in self.catDict.itervalues():
            self._drawCatalog(catalog)

    def _drawCatalog(self, catalog):
        """Draw the next portion of a catalog.
        subind=0 for the first portion, 1 for the next, etc.
        """
        #       print "_drawCatalog(%r)" % (catalog.name)

        catName = catalog.name

        # cancel update script, if executing
        sr = self.catSRDict[catName]
        if sr.isExecuting():
            #           print "_drawCatalog cancelling update script for catalog %r" % catName
            sr.cancel()

        # cancel scheduled wakeup, if any
        self.catRedrawTimerDict.get(catName).cancel()

        #       print "_drawCatalog starting update script for catalog %r" % catName
        sr.start()

    def _drawGrid(self):
        nCircles = 6
        # color = "green"
        x, y = self.center
        for circNum in range(nCircles):
            rad = self.azAltScale * (90 * (circNum + 1) / nCircles)
            RO.CanvasUtil.ctrCircle(self.cnv, x, y, rad)  #, outline = color)
        RO.CanvasUtil.ctrPlus(self.cnv, x, y, rad)  #, fill = color)
        RO.CanvasUtil.ctrX(self.cnv, x, y, rad)  #, fill = color)

    def _drawLabels(self):
        font = Tkinter.Entry()["font"]
        ex, ey = self.pixFromAzAlt(self.eastLabelPos.getAzAlt())
        nx, ny = self.pixFromAzAlt(self.northLabelPos.getAzAlt())
        self.cnv.create_text(ex - 8, ey, text=" E",
                             font=font)  #, fill="green")
        self.cnv.create_text(nx, ny - 5, text="N", font=font)  #, fill="green")

    def _drawTelCurrent(self):
        self.cnv.delete(SkyWdg.TELCURRENT)
        if self.telCurrent == None:
            return

        color = "red"
        tag = SkyWdg.TELCURRENT

        # draw current telescope position on az/alt grid
        x, y = self.pixFromAzAlt(self.telCurrent.getAzAlt())
        RO.CanvasUtil.ctrCircle(
            self.cnv,
            x,
            y,
            rad=9,
            outline=color,
            tag=tag,
        )

        # draw current telescope position on wrap gauge display
        az, alt = self.telCurrent.getAzAlt()
        x, y = self.azWrapGauge.angToXY(az)
        if None not in (x, y):
            RO.CanvasUtil.ctrCircle(
                self.cnv,
                x,
                y,
                rad=4,
                width=3,
                outline=color,
                tag=tag,
            )

    def _drawTelTarget(self):
        self.cnv.delete(SkyWdg.TELTARGET)
        if self.telTarget == None:
            return

        color = "red"
        tag = SkyWdg.TELTARGET

        # draw target on az/alt grid
        x, y = self.pixFromAzAlt(self.telTarget.getAzAlt())
        #       print "drawing target at", self.telTarget.getAzAlt(), "=", x, y
        RO.CanvasUtil.ctrPlus(
            self.cnv,
            x,
            y,
            rad=12,
            holeRad=3,
            fill=color,
            tag=tag,
        )

        # draw target on wrap gauge
        az, alt = self.telTarget.getAzAlt()
        x, y = self.azWrapGauge.angToXY(az)
        if None not in (x, y):
            RO.CanvasUtil.ctrPlus(
                self.cnv,
                x,
                y,
                rad=4,
                holeRad=0,
                width=3,
                fill=color,
                tag=tag,
            )

    def _drawTelPotential(self):
        #       print "_drawTelPotential"
        self.cnv.delete(SkyWdg.TELPOTENTIAL)
        if self.telPotential == None:
            return

        color = "dark green"
        tag = SkyWdg.TELPOTENTIAL

        # draw potential target on az, alt grid
        x, y = self.pixFromAzAlt(self.telPotential.getAzAlt())
        #       print "drawing potential at", self.telPotential.getAzAlt(), "=", x, y
        RO.CanvasUtil.ctrX(
            self.cnv,
            x,
            y,
            rad=9,
            holeRad=3,
            fill=color,
            tag=tag,
        )

        self._telPotentialAnimTimer.start(_CatRedrawDelay,
                                          self._drawTelPotential)
コード例 #49
0
ファイル: AxisStatus.py プロジェクト: r-owen/TUI
class AxisStatusWdg(Tkinter.Frame):
    def __init__ (self, master=None, **kargs):
        """Displays information about the axes

        Inputs:
        - master        master Tk widget -- typically a frame or window
        """
        Tkinter.Frame.__init__(self, master=master, **kargs)
        self.tccModel = TUI.TCC.TCCModel.getModel()
        self.prevSounds = [None]*3 # sounds played last time we received AxisCmdState
        self.prevCtrlStatusOK = [None]*3
        self.ctrlBadTime = 0 # time of last "controller bad" sound
        self._soundTimer = Timer()

        # magic numbers
        PosPrec = 1 # number of digits past decimal point
        PosWidth = 5 + PosPrec  # assumes -999.99... degrees is longest field
        AxisCmdStateWidth = 8
        AxisErrCodeWidth = 13
        CtrlStatusWidth = 25

        self.axisInd = range(len(self.tccModel.axisNames))
        
        # actual axis position widget set
        self.axePosWdgSet = [
            RO.Wdg.FloatLabel(
                master = self,
                precision = PosPrec,
                width = PosWidth,
                helpText = "Current axis position, as reported by the controller",
                helpURL = _HelpURL,
            )
            for axis in self.axisInd
        ]
        self.tccModel.axePos.addROWdgSet(self.axePosWdgSet)

        # target axis position widget set
        self.tccPosWdgSet = [
            RO.Wdg.FloatLabel(
                master = self,
                precision = PosPrec,
                width = PosWidth,
                helpText = "Target axis position",
                helpURL = _HelpURL,
            )
            for axis in self.axisInd
        ]
        self.tccModel.tccPos.addROWdgSet(self.tccPosWdgSet)
        
        # TCC status widget set (e.g. tracking or halted)
        self.axisCmdStateWdgSet = [
            RO.Wdg.StrLabel(
                master = self,
                width = AxisCmdStateWidth,
                helpText = "What the TCC is telling the axis to do",
                helpURL = _HelpURL,
                anchor = "nw",
            )
            for axis in self.axisInd
        ]
        self.tccModel.axisCmdState.addCallback(self.setAxisCmdState)
    
        self.tccModel.rotExists.addIndexedCallback(self.setRotExists)
        
        # axis error code widet set (why the TCC is not moving the axis)
        self.axisErrCodeWdgSet = [
            RO.Wdg.StrLabel(
                master = self,
                width = AxisErrCodeWidth,
                helpText = "Why the TCC halted the axis",
                helpURL = _HelpURL,
                anchor = "nw",
            )
            for axis in self.axisInd
        ]
        self.tccModel.axisErrCode.addROWdgSet(self.axisErrCodeWdgSet)       
    
        # controller status widget set (the status word)
        self.ctrlStatusWdgSet = [
            RO.Wdg.StrLabel(
                master = self,
                width = CtrlStatusWidth,
                helpText = "Status reported by the axis controller",
                helpURL = _HelpURL,
                anchor = "nw",
            )
            for axis in self.axisInd
        ]
        for axis in self.axisInd:
            self.tccModel.ctrlStatusSet[axis].addIndexedCallback(
                RO.Alg.GenericCallback(self.setCtrlStatus, axis), 3)
        
                
        # grid the axis widgets
        gr = RO.Wdg.Gridder(self, sticky="w")
        for axis in self.axisInd:
            unitsLabel1 = Tkinter.Label(self, text=RO.StringUtil.DegStr)
            unitsLabel2 = Tkinter.Label(self, text=RO.StringUtil.DegStr)
            if axis == 2:
                self.rotUnitsLabel1 = unitsLabel1
                self.rotUnitsLabel2 = unitsLabel2
            gr.gridWdg (
                label = self.tccModel.axisNames[axis],
                dataWdg = (
                    self.axePosWdgSet[axis],
                    unitsLabel1,
                    self.tccPosWdgSet[axis],
                    unitsLabel2,
                    self.axisCmdStateWdgSet[axis],
                    self.axisErrCodeWdgSet[axis],
                    self.ctrlStatusWdgSet[axis],
                )
            )
        
        # widen rotator commanded state widget
        # so there's room to display "NotAvailable"
        # (note that the error code widget will be hidden when this occurs
        # so the text will not overlap anything).
        rotCmdWdg = self.axisCmdStateWdgSet[2]
        rotCmdWdg.grid_configure(columnspan=2)
        rotCmdWdg["width"] = 12

        # allow the last column to grow to fill the available space
        self.columnconfigure(gr.getMaxNextCol(), weight=1)
    
    def setAxisCmdState(self, axisCmdState, isCurrent, keyVar):
        if not isCurrent:
            for wdg in self.axisCmdStateWdgSet:
                wdg.setIsCurrent(False)
            return

        # set axis commanded state widgets
        for axis in self.axisInd:
            cmdState = axisCmdState[axis]
            severity = _CmdStateDict.get(cmdState.lower(), RO.Constants.sevError)
            self.axisCmdStateWdgSet[axis].set(cmdState, severity=severity)

        # play sounds, if appropriate
        indSoundsToPlay = set() # add new sounds to play to a set to avoid duplicates
        for axis in self.axisInd:
            soundInd, soundFunc = _StateIndSoundDict.get(axisCmdState[axis].lower(), (0, None))
            if soundFunc and (soundFunc != self.prevSounds[axis]) and keyVar.isGenuine():
                indSoundsToPlay.add((soundInd, soundFunc))
            self.prevSounds[axis] = soundFunc
        
        if indSoundsToPlay:
            indSoundsToPlay = list(indSoundsToPlay)
            indSoundsToPlay.sort()
            soundsToPlay = list(zip(*indSoundsToPlay)[1])
            soundsToPlay.reverse() # since played from back to front
            self.playSounds(soundsToPlay)
        
    def setCtrlStatus(self, axis, statusWord, isCurrent=True, keyVar=None, *args):
        # print "setCtrlStatus called with axis, statusWord, isCurrent=", axis, statusWord, isCurrent
        if axis == 2 and not self.tccModel.rotExists[0]:
            # rotator does not exist; this is handled by setRotExists
            return
            
        statusOK = True

        ctrlStatusWdg = self.ctrlStatusWdgSet[axis]

        if statusWord is not None:
            infoList = RO.BitDescr.getDescr(_BitInfo, statusWord)
            
            # for now simply show the first status;
            # eventually provide a pop-up list showing all status bits
            if infoList:
                info, severity = infoList[0]
                ctrlStatusWdg.set(info, isCurrent, severity=severity)
                if severity == RO.Constants.sevError:
                    statusOK = False
            else:
                ctrlStatusWdg.set("", isCurrent, severity=RO.Constants.sevNormal)
        elif isCurrent:
            ctrlStatusWdg.set("Not responding", isCurrent, severity=RO.Constants.sevError)
            statusOK = False
        else:
            ctrlStatusWdg.setNotCurrent()
        
        statusNewlyBad = (self.prevCtrlStatusOK[axis] and not statusOK)
        self.prevCtrlStatusOK[axis] = statusOK
        
        if statusNewlyBad and keyVar and keyVar.isGenuine() \
            and (time.time() - self.ctrlBadTime > _CtrllrWaitSec):
            TUI.PlaySound.axisHalt()
            self.ctrlBadTime = time.time()
    
    def setRotExists(self, rotExists, isCurrent=True, **kargs):
        if not isCurrent:
            return
        if rotExists:
            self.rotUnitsLabel1.grid()
            self.rotUnitsLabel2.grid()
            self.axisErrCodeWdgSet[2].grid()
            self.ctrlStatusWdgSet[2].grid()
            self.ctrlStatusWdgSet[2].set("", severity=RO.Constants.sevNormal)
        else:
            self.rotUnitsLabel1.grid_remove()
            self.rotUnitsLabel2.grid_remove()
            self.axisErrCodeWdgSet[2].grid_remove()
            self.ctrlStatusWdgSet[2].grid_remove()
    
    def playSounds(self, sounds):
        """Play one or more of a set of sounds; played in order from last to first.
        """
        if not sounds:
            return
        soundFunc = sounds.pop(-1)
        soundFunc()
        if sounds:
            self._soundTimer.start(_SoundInterval, self.playSounds, sounds)
コード例 #50
0
    def __init__(self, master, width=201, height=201):
        Tkinter.Frame.__init__(self, master)

        self.tuiModel = TUI.TUIModel.getModel()
        self.tccModel = TUI.TCC.TCCModel.getModel()
        self.userModel = TUI.TCC.UserModel.getModel()

        # instance variables:
        # center: position of center of canvas, in pixels
        # size: size of canvas, in pixels
        # scale: scale of canvas, in pixels per deg
        self.currCatObjID = None

        self._telPotentialAnimTimer = Timer()

        self.eastLabelPos = AzAltTarget(azAlt=(90, 0))
        self.northLabelPos = AzAltTarget(azAlt=(180, 0))

        # pane on which to display current star info
        self.currStarDisp = RO.Wdg.StatusBar(master=self)
        self.currStarDisp.grid(row=1, column=0, sticky="ew")
        self.currStarMsgID = None

        # canvas on which to display stars
        self.cnv = Tkinter.Canvas(
            master=self,
            width=width,
            height=height,
            #           background='black',
            selectborderwidth=0,
            highlightthickness=0)
        self.cnv.grid(row=0, column=0, sticky="nsew")
        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure(0, weight=1)
        RO.Wdg.addCtxMenu(
            wdg=self.cnv,
            helpURL=_HelpURL,
        )

        # thickness of canvas border;
        # drawable width/height = winfo_width/height - (2 * border)
        self.cnvBorderWidth = int(self.cnv["highlightthickness"]) + int(
            self.cnv["selectborderwidth"])
        self.cnv.bind('<Configure>', self._configureEvt)
        # self.cnv.tag_bind('star', '<Enter>', self._enterStar)
        # self.cnv.tag_bind('star', '<Leave>', self._leaveStar)
        self.cnv.bind('<Motion>', self._enterStar)
        self.cnv.bind('<Leave>', self._leaveStar)
        # the following prevents the display from blanking
        # when the button is pressed once (I tried trapping and
        # discarding <Button>, as a faster solutionn, but it didn't work)
        self.cnv.bind('<Button>', self._enterStar)
        self.cnv.bind('<Double-Button-1>', self._setPotential)
        self.center = [None, None]
        self.size = [None, None]
        self.azAltRad = None
        self.azAltScale = None
        self.sizeDeg = [180.0, 180.0]

        # various dictionaries whose keys are catalog name
        # note: if a catalog is deleted, it is removed from catDict
        # and catPixPosObjDict, but not necessarily the others
        self.catDict = {}  # key=catalog name, value = catalog
        self.catRedrawTimerDict = {}  # key=catalog name, value = tk after id
        self.catColorDict = {}  # key=catalog name, value = color
        self.catPixPosObjDict = {
        }  # key=catalog name, value = list of (pix pos, obj) pairs
        self.catSRDict = {
        }  # key=catalog name, value = scriptrunner script to redisplay catalog

        self.telCurrent = None
        self.telTarget = None
        self.telPotential = None
        self.azWrapGauge = RO.CanvasUtil.Spiral(
            cnv=self.cnv,
            xctr=1,
            yctr=1,
            begRad=0,
            endRad=0,  # not yet ready to draw; canvas size unknown
            angScale=-1.0,
            angOff=-90.0,
        )
        self._setSize()

        # set up automatic update of current and target telescope position
        self.tccModel.axePos.addCallback(self.setTelCurrent)
        self.tccModel.tccPos.addCallback(self.setTelTarget)
        self.tccModel.azLim.addCallback(self.setAzLim)

        self.userModel.potentialTarget.addCallback(self.setTelPotential)
        self.userModel.userCatDict.addCallback(self._updUserCatDict)
コード例 #51
0
def _nextStar(starInfo, delaySec):
    starInfo.update()
    keyVarStr = starInfo.getKeyVarStr()
    testDispatcher.dispatch(keyVarStr, actor="gcam")
    Timer(delaySec, _nextStar, starInfo, delaySec)
コード例 #52
0
    def addCatalog(self, catalog):
        """Add a new catalog with a given name.
        If the catalog already exists, it is deleted.
        """
        #       print "addCatalog %r" % (catalog.name,)
        catName = catalog.name

        if catName in self.catDict:
            self.removeCatalogByName(catName)

        self.catDict[catName] = catalog
        self.catPixPosObjDict[catName] = []
        self.catRedrawTimerDict[catName] = Timer()
        self.catColorDict[catName] = catalog.getDispColor()

        def updateCat(sr, self=self, catalog=catalog):
            catName = catalog.name

            catTag = "cat_%s" % (catName, )

            if not catalog.getDoDisplay():
                self.catPixPosObjDict[catName] = []
                self.cnv.delete(catTag)
                return

            # if color has changed, update it
            color = catalog.getDispColor()
            oldColor = self.catColorDict.get(catName)
            if color != oldColor:
                self.cnv.itemconfigure(catTag, fill=color, outline=color)
                self.catColorDict[catName] = color

#           print "compute %s thread starting" % catName
            yield sr.waitThread(_UpdateCatalog, catalog.objList, self.center,
                                self.azAltScale)
            pixPosObjList = sr.value
            #           print "compute %s thread done" % catName

            catName = catalog.name
            catTag = "cat_%s" % (catName, )

            self.catPixPosObjDict[catName] = []
            self.cnv.delete(catTag)

            color = catalog.getDispColor()
            rad = 2  # for now, eventually may wish to vary by magnitude or window size or...?
            for pixPos, obj in pixPosObjList:
                self.cnv.create_oval(
                    pixPos[0] - rad,
                    pixPos[1] - rad,
                    pixPos[0] + rad + 1,
                    pixPos[1] + rad + 1,
                    tag=(SkyWdg.CATOBJECT, catTag),
                    fill=color,
                    outline=color,
                )
            self.catPixPosObjDict[catName] = pixPosObjList

            self.catRedrawTimerDict[catName].start(_CatRedrawDelay,
                                                   self._drawCatalog, catalog)

        sr = RO.ScriptRunner.ScriptRunner(
            runFunc=updateCat,
            name="updateCatalog",
        )

        self.catSRDict[catName] = sr
        catalog.addCallback(self._drawCatalog, callNow=True)
コード例 #53
0
def _nextSecPiston(secPiston, delaySec):
    keyVarStr = "SecOrient=%0.1f, 0, 0, 0, 0" % (next(secPiston), )
    testDispatcher.dispatch(keyVarStr, actor="tcc")
    Timer(delaySec, _nextSecPiston, secPiston, delaySec)
コード例 #54
0
ファイル: BalloonHelp.py プロジェクト: astromaddie/RO-py3
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()
コード例 #55
0
ファイル: ObjPosWdg.py プロジェクト: migueldvb/TUI
    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()
コード例 #56
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)
コード例 #57
0
ファイル: SkyWindow.py プロジェクト: migueldvb/TUI
    def __init__(self, master, width=201, height=201):
        Tkinter.Frame.__init__(self, master)
        
        self.tuiModel = TUI.TUIModel.getModel()
        self.tccModel = TUI.TCC.TCCModel.getModel()
        self.userModel = TUI.TCC.UserModel.getModel()

        # instance variables:
        # center: position of center of canvas, in pixels
        # size: size of canvas, in pixels
        # scale: scale of canvas, in pixels per deg
        self.currCatObjID = None

        self._telPotentialAnimTimer = Timer()
        
        self.eastLabelPos = AzAltTarget(azAlt=(90, 0))
        self.northLabelPos = AzAltTarget(azAlt=(180, 0))

        # pane on which to display current star info
        self.currStarDisp = RO.Wdg.StatusBar(master=self)
        self.currStarDisp.grid(row=1, column=0, sticky="ew")
        self.currStarMsgID = None

        # canvas on which to display stars
        self.cnv = Tkinter.Canvas(master=self,
            width=width, height=height,
#           background='black',
            selectborderwidth=0, highlightthickness=0)
        self.cnv.grid(row=0, column=0, sticky="nsew")
        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure(0, weight=1)
        RO.Wdg.addCtxMenu(
            wdg = self.cnv,
            helpURL = _HelpURL,
        )

        # thickness of canvas border;
        # drawable width/height = winfo_width/height - (2 * border)
        self.cnvBorderWidth = int(self.cnv["highlightthickness"]) + int(self.cnv["selectborderwidth"])
        self.cnv.bind('<Configure>', self._configureEvt)
        # self.cnv.tag_bind('star', '<Enter>', self._enterStar)
        # self.cnv.tag_bind('star', '<Leave>', self._leaveStar)
        self.cnv.bind('<Motion>', self._enterStar)
        self.cnv.bind('<Leave>', self._leaveStar)
        # the following prevents the display from blanking
        # when the button is pressed once (I tried trapping and
        # discarding <Button>, as a faster solutionn, but it didn't work)
        self.cnv.bind('<Button>', self._enterStar)
        self.cnv.bind('<Double-Button-1>', self._setPotential)
        self.center = [None,None]
        self.size = [None,None]
        self.azAltRad = None
        self.azAltScale = None
        self.sizeDeg = [180.0, 180.0]
        
        # various dictionaries whose keys are catalog name
        # note: if a catalog is deleted, it is removed from catDict
        # and catPixPosObjDict, but not necessarily the others
        self.catDict = {}   # key=catalog name, value = catalog
        self.catRedrawTimerDict = {}    # key=catalog name, value = tk after id
        self.catColorDict = {}  # key=catalog name, value = color
        self.catPixPosObjDict = {}  # key=catalog name, value = list of (pix pos, obj) pairs
        self.catSRDict = {} # key=catalog name, value = scriptrunner script to redisplay catalog

        self.telCurrent = None
        self.telTarget = None
        self.telPotential = None
        self.azWrapGauge = RO.CanvasUtil.Spiral (
            cnv = self.cnv,
            xctr = 1, yctr = 1,
            begRad = 0, endRad = 0,  # not yet ready to draw; canvas size unknown
            angScale = -1.0,
            angOff = -90.0,
        )
        self._setSize()
        
        # set up automatic update of current and target telescope position
        self.tccModel.axePos.addCallback(self.setTelCurrent)
        self.tccModel.tccPos.addCallback(self.setTelTarget)
        self.tccModel.azLim.addCallback(self.setAzLim)
        
        self.userModel.potentialTarget.addCallback(self.setTelPotential)
        self.userModel.userCatDict.addCallback(self._updUserCatDict)
コード例 #58
0
ファイル: AgileGuideTest.py プロジェクト: r-owen/TUI
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
コード例 #59
0
class StripChartWdg(tkinter.Frame):
    """A widget to changing values in real time as a strip chart
    
    Usage Hints:
    - For each variable quantity to display:
      - Call addLine once to specify the quantity
      - Call addPoint for each new data point you wish to display

    - For each constant line (e.g. limit) to display call addConstantLine
    
    - To make sure a plot includes one or two y values (e.g. 0 or a range of values) call showY

    - To manually scale a Y axis call setYLimits (by default all y axes are autoscaled).
    
    - All supplied times are POSIX timestamps (e.g. as supplied by time.time()).
        You may choose the kind of time displayed on the time axis (e.g. UTC or local time) using cnvTimeFunc
        and the format of that time using dateFormat.
    
    Known Issues:
    matplotlib's defaults present a number of challenges for making a nice strip chart display.
    Some issues and manual solutions are discussed in the main file's document string.
        
    Potentially Useful Attributes:
    - canvas: the matplotlib FigureCanvas
    - figure: the matplotlib Figure
    - subplotArr: list of subplots, from top to bottom; each is a matplotlib Subplot object,
        which is basically an Axes object but specialized to live in a rectangular grid
    - xaxis: the x axis shared by all subplots
    """
    def __init__(self,
        master,
        timeRange = 3600,
        numSubplots = 1,
        width = 8,
        height = 2,
        showGrid = True,
        dateFormat = "%H:%M:%S",
        updateInterval = None,
        cnvTimeFunc = None,
    ):
        """Construct a StripChartWdg with the specified time range
        
        Inputs:
        - master: Tk parent widget
        - timeRange: range of time displayed (seconds)
        - width: width of graph in inches
        - height: height of graph in inches
        - numSubplots: the number of subplots
        - showGrid: if True a grid is shown
        - dateFormat: format for major axis labels, using time.strftime format
        - updateInterval: now often the time axis is updated (seconds); if None a value is calculated
        - cnvTimeFunc: a function that takes a POSIX timestamp (e.g. time.time()) and returns matplotlib days;
            typically an instance of TimeConverter; defaults to TimeConverter(useUTC=False)
        """
        tkinter.Frame.__init__(self, master)
        
        self._timeRange = timeRange
        self._isVisible = self.winfo_ismapped()
        self._isFirst = True
        if updateInterval is None:
            updateInterval = max(0.1, min(5.0, timeRange / 2000.0))
        self.updateInterval = float(updateInterval)
#         print "updateInterval=", self.updateInterval

        if cnvTimeFunc is None:
            cnvTimeFunc = TimeConverter(useUTC=False)
        self._cnvTimeFunc = cnvTimeFunc

        # how many time axis updates occur before purging old data
        self._maxPurgeCounter = max(1, int(0.5 + (5.0 / self.updateInterval)))
        self._purgeCounter = 0

        self.figure = matplotlib.figure.Figure(figsize=(width, height), frameon=True)
        self.canvas = FigureCanvasTkAgg(self.figure, self)
        self.canvas.get_tk_widget().grid(row=0, column=0, sticky="news")
        self.canvas.mpl_connect('draw_event', self._handleDrawEvent)
        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure(0, weight=1)
        bottomSubplot = self.figure.add_subplot(numSubplots, 1, numSubplots)
        self.subplotArr = [self.figure.add_subplot(numSubplots, 1, n+1, sharex=bottomSubplot) \
            for n in range(numSubplots-1)] + [bottomSubplot]
        if showGrid:
            for subplot in self.subplotArr:
                subplot.grid(True)

        self.xaxis = bottomSubplot.xaxis
        bottomSubplot.xaxis_date()
        self.xaxis.set_major_formatter(matplotlib.dates.DateFormatter(dateFormat))

        # dictionary of constant line name: (matplotlib Line2D, matplotlib Subplot)
        self._constLineDict = dict()

        for subplot in self.subplotArr:
            subplot._scwLines = [] # a list of contained _Line objects;
                # different than the standard lines property in that:
                # - lines contains Line2D objects
                # - lines contains constant lines as well as data lines
            subplot._scwBackground = None # background for animation
            subplot.label_outer() # disable axis labels on all but the bottom subplot
            subplot.set_ylim(auto=True) # set auto scaling for the y axis
        
        self.bind("<Map>", self._handleMap)
        self.bind("<Unmap>", self._handleUnmap)
        self._timeAxisTimer = Timer()
        self._updateTimeAxis()

    def addConstantLine(self, y, subplotInd=0, **kargs):
        """Add a new constant to plot
        
        Inputs:
        - y: value of constant line
        - subplotInd: index of subplot
        - All other keyword arguments are sent to the matplotlib Line2D constructor
          to control the appearance of the data. See addLine for more information.
        """
        subplot = self.subplotArr[subplotInd]
        line2d = subplot.axhline(y, **kargs)
        yMin, yMax = subplot.get_ylim()
        if subplot.get_autoscaley_on() and numpy.isfinite(y) and not (yMin <= y <= yMax):
            subplot.relim()
            subplot.autoscale_view(scalex=False, scaley=True)
        return line2d

    def addLine(self, subplotInd=0, **kargs):
        """Add a new quantity to plot
        
        Inputs:
        - subplotInd: index of subplot
        - All other keyword arguments are sent to the matplotlib Line2D constructor
          to control the appearance of the data. Useful arguments include:
          - label: name of line (displayed in a Legend)
          - color: color of line
          - linestyle: style of line (defaults to a solid line); "" for no line, "- -" for dashed, etc.
          - marker: marker shape, e.g. "+"
          Please do not attempt to control other sorts of line properties, such as its data.
          Arguments to avoid include: animated, data, xdata, ydata, zdata, figure.
        """
        subplot = self.subplotArr[subplotInd]
        return _Line(
            subplot = subplot,
            cnvTimeFunc = self._cnvTimeFunc,
            wdg = self,
        **kargs)
    
    def clear(self):
        """Clear data in all non-constant lines
        """
        for subplot in self.subplotArr:
            for line in subplot._scwLines:
                line.clear()

    def getDoAutoscale(self, subplotInd=0):
        return self.subplotArr[subplotInd].get_autoscaley_on()
    
    def removeLine(self, line):
        """Remove an existing line added by addLine or addConstantLine
        
        Raise an exception if the line is not found
        """
        if isinstance(line, _Line):
            # a _Line object needs to be removed from _scwLines as well as the subplot
            line2d = line.line2d
            subplot = line.subplot
            subplot._scwLines.remove(line)
        else:
            # a constant line is just a matplotlib Line2D instance
            line2d = line
            subplot = line.axes

        subplot.lines.remove(line2d)
        if subplot.get_autoscaley_on():
            subplot.relim()
            subplot.autoscale_view(scalex=False, scaley=True)
        self.canvas.draw()

    def setDoAutoscale(self, doAutoscale, subplotInd=0):
        """Turn autoscaling on or off for the specified subplot
        
        You can also turn off autoscaling by calling setYLimits.
        """
        doAutoscale = bool(doAutoscale)
        subplot = self.subplotArr[subplotInd]
        subplot.set_ylim(auto=doAutoscale)
        if doAutoscale:
            subplot.relim()
            subplot.autoscale_view(scalex=False, scaley=True)
    
    def setYLimits(self, minY, maxY, subplotInd=0):
        """Set y limits for the specified subplot and disable autoscaling.
        
        Note: if you want to autoscale with a minimum range, use showY.
        """
        self.subplotArr[subplotInd].set_ylim(minY, maxY, auto=False)
    
    def showY(self, y0, y1=None, subplotInd=0):
        """Specify one or two values to always show in the y range.
        
        Inputs:
        - subplotInd: index of subplot
        - y0: first y value to show
        - y1: second y value to show; None to omit

        Warning: setYLimits overrides this method (but the values are remembered in case you turn
        autoscaling back on).
        """
        subplot = self.subplotArr[subplotInd]
        yMin, yMax = subplot.get_ylim()
        
        if y1 is not None:
            yList = [y0, y1]
        else:
            yList = [y0]
        doRescale = False
        for y in yList:
            subplot.axhline(y, linestyle=" ")
            if subplot.get_autoscaley_on() and numpy.isfinite(y) and not (yMin <= y <= yMax):
                doRescale = True
        if doRescale:
            subplot.relim()
            subplot.autoscale_view(scalex=False, scaley=True)

    def _handleDrawEvent(self, event=None):
        """Handle draw event
        """
#         print "handleDrawEvent"
        for subplot in self.subplotArr:
            subplot._scwBackground = self.canvas.copy_from_bbox(subplot.bbox)
            for line in subplot._scwLines:
                subplot.draw_artist(line.line2d)
            self.canvas.blit(subplot.bbox)
    
    def _handleMap(self, evt):
        """Handle map event (widget made visible)
        """
        self._isVisible = True
        self._handleDrawEvent()
        self._updateTimeAxis()
    
    def _handleUnmap(self, evt):
        """Handle unmap event (widget made not visible)
        """
        self._isVisible = False
    
    def _updateTimeAxis(self):
        """Update the time axis; calls itself
        """
        tMax = time.time() + self.updateInterval
        tMin = tMax - self._timeRange
        minMplDays = self._cnvTimeFunc(tMin)
        maxMplDays = self._cnvTimeFunc(tMax)
        
        self._purgeCounter = (self._purgeCounter + 1) % self._maxPurgeCounter
        doPurge = self._purgeCounter == 0

        if doPurge:
            for subplot in self.subplotArr:
                for line in subplot._scwLines:
                    line._purgeOldData(minMplDays)
        
        if self._isVisible or self._isFirst:
            for subplot in self.subplotArr:
                subplot.set_xlim(minMplDays, maxMplDays)
                if doPurge:
                    if subplot.get_autoscaley_on():
                        # since data is being purged the y limits may have changed
                        subplot.relim()
                        subplot.autoscale_view(scalex=False, scaley=True)
            self._isFirst = False
            self.canvas.draw()
        self._timeAxisTimer.start(self.updateInterval, self._updateTimeAxis)
コード例 #60
0
ファイル: MiscWdg.py プロジェクト: CraigLoomis/stui
class MiscWdg (Tkinter.Frame):
    InstNameDict = {0: "None"} # add a value for Eng Cam once known
    def __init__ (self, master=None, **kargs):
        """Displays miscellaneous information, such as current time and az/alt

        Inputs:
        - master        master Tk widget -- typically a frame or window
        """
        Tkinter.Frame.__init__(self, master=master, **kargs)
        self.tccModel = TUI.Models.getModel("tcc")
        self.guiderModel = TUI.Models.getModel("guider")
        self.mcpModel = TUI.Models.getModel("mcp")
        self.plateDBModel = TUI.Models.getModel("platedb")
        self._cartridgeInfo = [None]*3 # (cartID, plateID, pointing)
        self._clockTimer = Timer()
        
        gr = RO.Wdg.Gridder(self, sticky="e")

        self.haWdg = RO.Wdg.DMSLabel(
            master = self,
            precision = 0,
            nFields = 3,
            cvtDegToHrs = 1,
            width = 8,
            helpText = "Hour angle of the object",
            helpURL = _HelpURL,
        )
        gr.gridWdg("HA", self.haWdg, "hms")
        
        self.designHAWdg = RO.Wdg.DMSLabel(
            master = self,
            precision = 0,
            nFields = 3,
            cvtDegToHrs = 1,
            width = 8,
            helpText = "Hour angle the plate was designed for (from platedb)",
            helpURL = _HelpURL,
        )
        gr.gridWdg("Design HA", self.designHAWdg, "hms")
        
        self.deltaHAWdg = RO.Wdg.DMSLabel(
            master = self,
            precision = 0,
            nFields = 3,
            cvtDegToHrs = 1,
            width = 8,
            helpText = "Design - current hour angle",
            helpURL = _HelpURL,
        )
        gr.gridWdg("Des-Curr HA", self.deltaHAWdg, "hms")
        
        self.taiWdg = RO.Wdg.StrLabel(
            master = self,
            width=19,
            helpText = "International Atomic Time",
            helpURL = _HelpURL,
        )
        gr.gridWdg("TAI", self.taiWdg, colSpan=2)

        # secondary focus
        self.secFocusWdg = RO.Wdg.FloatLabel(
            master = self,
            precision = 0,
            width = 5,
            helpText = "Secondary mirror focus",
            helpURL = _HelpURL,
        )
        gr.gridWdg (
            label = "Focus",
            dataWdg = self.secFocusWdg,
            units = u"\N{MICRO SIGN}m",
        )
        self.tccModel.secFocus.addValueCallback(self.secFocusWdg.set)

        # start the second column of widgets
        gr.startNewCol(spacing=1)
        
        gr._nextCol -= 2 # allow overlap with widget to the right

        self.airmassWdg = RO.Wdg.FloatLabel(
            master = self,
            precision=3,
            width = 5,
            helpText = "Airmass",
            helpURL = _HelpURL,
        )
        gr.gridWdg("Airmass", self.airmassWdg)
        
        self.zdWdg = RO.Wdg.FloatLabel(
            master = self,
            precision = 1,
            helpText = "Zenith distance (90 - altitude)",
            helpURL = _HelpURL,
            width = 5,
        )
        gr.gridWdg("ZD", self.zdWdg, RO.StringUtil.DegStr)

        self.lmstWdg = RO.Wdg.DMSLabel(
            master = self,
            precision = 0,
            nFields = 3,
            width = 8,
            justify="right",
            helpText = "Local mean sidereal time at APO",
            helpURL = _HelpURL,
        )
        gr.gridWdg("LMST", self.lmstWdg, "hms")
        
        self.sjdWdg = RO.Wdg.IntLabel(
            master = self,
            helpText = "SDSS MJD (rolls over at TAI MJD-0.3)",
            helpURL = _HelpURL,
            width = 6,
        )
        gr.gridWdg("SJD", self.sjdWdg, "days")

        self.scaleWdg = RO.Wdg.FloatLabel(
            master = self,
            precision = 1,
            width = 8,
            helpText = "scale ((plate/nominal - 1) * 1e6); larger is higher resolution",
            helpURL = _HelpURL,
        )
        gr.gridWdg (
            label = "Scale",
            dataWdg = self.scaleWdg,
            units = "1e6",
        )
        self.tccModel.scaleFac.addCallback(self._scaleFacCallback)
        
        # start the third column of widgets
        gr.startNewCol(spacing=1)
        
        self.instNameWdg = RO.Wdg.StrLabel(
            master = self,
            width = 10,
            helpText = "Current instrument (from the TCC)",
            helpURL = _HelpURL,
        )
        gr.gridWdg("Inst", self.instNameWdg, units=False)
        self.tccModel.inst.addValueCallback(self.instNameWdg.set)
        
        self.cartridgeIDWdg = RO.Wdg.StrLabel(
            master = self,
            width = 13,
            helpText = "currently mounted cartridge (from MCP and guider)",
            helpURL = _HelpURL,
        )
        gr.gridWdg("Cartridge", self.cartridgeIDWdg)

        self.plateIDWdg = RO.Wdg.IntLabel(
            master = self,
            width = 8,
            helpText = "currently mounted plug plate (from the guider)",
            helpURL = _HelpURL,
        )
        gr.gridWdg("Plate", self.plateIDWdg)

        self.platePointingWdg = RO.Wdg.StrLabel(
            master = self,
            width = 8,
            helpText = "plug-plate pointing (from the guider)",
            helpURL = _HelpURL,
        )
        gr.gridWdg("Pointing", self.platePointingWdg)

        # state of guiding
        self.guideWdg = RO.Wdg.StrLabel(
            master = self,
            anchor = "e",
            helpText = "State of guiding",
            helpURL = _HelpURL,
        )
        gr.gridWdg (
            label = "Guiding",
            dataWdg = self.guideWdg,
            units = False,
            sticky = "ew",
        )
        
        # all widgets are gridded
        gr.allGridded()
        
        # add callbacks
        self.tccModel.axePos.addCallback(self._setAxePos)
        self.guiderModel.cartridgeLoaded.addCallback(self.setCartridgeInfo)
        self.mcpModel.instrumentNum.addCallback(self.setCartridgeInfo)
        self.plateDBModel.pointingInfo.addCallback(self._setAxePos)
        self.guiderModel.guideState.addCallback(self._guideStateCallback)
        
        # start clock updates       
        self._updateClock()
        
        # allow the last+1 column to grow to fill the available space
        self.columnconfigure(gr.getMaxNextCol(), weight=1)

    def _guideStateCallback(self, keyVar):
        """Display guider state
        """
        state = self.guiderModel.guideState[0] or ""
        self.guideWdg.set(state.title(), isCurrent = keyVar.isCurrent)

    def _scaleFacCallback(self, keyVar):
        val = keyVar[0]
        if val is not None:
            val = (val - 1) * 1.0e6
        self.scaleWdg.set(val, keyVar.isCurrent)
    
    def _setAxePos(self, keyVar=None):
        """Updates ha, dec, zenith distance, airmass and plate design ha
        """
        # axePos values are: (az, alt, rot)
        axePosIsCurrent = self.tccModel.axePos.isCurrent
        az, alt = self.tccModel.axePos[0:2]

        if alt is not None:
            airmass = RO.Astro.Sph.airmass(alt)
            zd = 90.0 - alt
        else:
            airmass = None
            zd = None
        
        # set zd, airmass widgets
        self.zdWdg.set(zd, isCurrent=axePosIsCurrent)
        self.airmassWdg.set(airmass, isCurrent=axePosIsCurrent)
        
        # set hour angle (set in degrees, display in hours)
        try:
            (ha, dec), atPole = RO.Astro.Sph.haDecFromAzAlt((az, alt), TUI.TCC.TelConst.Latitude)
            if atPole:
                ha = None
        except (TypeError, ValueError):
            ha = None
        self.haWdg.set(ha, isCurrent=axePosIsCurrent)

        designHA = self._getDesignHA()
        plateInfoIsCurrent = self.plateDBModel.pointingInfo.isCurrent
        self.designHAWdg.set(designHA, plateInfoIsCurrent)
        
        designHA = self._getDesignHA()
        if None in (ha, designHA):
            deltaHA = None
        else:    
            deltaHA = (ha - designHA)
        self.deltaHAWdg.set(deltaHA, isCurrent=axePosIsCurrent and plateInfoIsCurrent)

    def setCartridgeInfo(self, keyVar=None):
        """Set cartridge info based on guider and MCP.
        """
        severity = RO.Constants.sevNormal
        mcpInstNum = self.mcpModel.instrumentNum[0]
        isCurrent = self.mcpModel.instrumentNum.isCurrent
        mcpInstName = self.InstNameDict.get(mcpInstNum)
        cartridgeStr = None
        if mcpInstName:
            # known instrument that is not a cartridge;
            # ignore self.guiderModel.cartridgeLoaded and show no cartridge info
            self._cartridgeInfo = [None]*3
            cartridgeStr = mcpInstName
        else:
            # MCP thinks a cartridge is mounted or does not know what is mounted;
            # base the output on a combination of mcp instrumentNum and guider cartridgeLoaded
            isCurrent = isCurrent and self.guiderModel.cartridgeLoaded.isCurrent
            self._cartridgeInfo = self.guiderModel.cartridgeLoaded[0:3]
            guiderInstNum = self._cartridgeInfo[0]

            # avoid dictionary lookup since -1 -> Invalid which is None but does not look up properly
            if mcpInstNum in (None, "?"):
                mcpInstName = "?"
            else:
                mcpInstName = str(mcpInstNum)

            if guiderInstNum == mcpInstNum:
                # MCP and guider agree on the loaded cartridge; output the value
                cartridgeStr = mcpInstName
            else:
                if guiderInstNum is None:
                    guiderInstName = "?"
                else:
                    guiderInstName = str(guiderInstNum)
                cartridgeStr = "%s mcp %s gdr" % (mcpInstName, guiderInstName)
                severity = RO.Constants.sevError

        self.cartridgeIDWdg.set(cartridgeStr, isCurrent=isCurrent, severity=severity)
        self.plateIDWdg.set(self._cartridgeInfo[1], isCurrent=isCurrent, severity=severity)
        self.platePointingWdg.set(self._cartridgeInfo[2], isCurrent=isCurrent, severity=severity)
        self._setAxePos()

    def _getDesignHA(self):
        for ptgInd, cartInd in ((0, 1), (1, 0), (2, 2)):
            if self.plateDBModel.pointingInfo[ptgInd] != self._cartridgeInfo[cartInd]:
                return None
        return self.plateDBModel.pointingInfo[5]

    def _updateClock(self):
        """Automatically update the time displays in this widget.
        Call once to get things going
        """
        # update utc
        currPythonSeconds = RO.Astro.Tm.getCurrPySec()
        currTAITuple= time.gmtime(currPythonSeconds - RO.Astro.Tm.getUTCMinusTAI())
        self.taiWdg.set(time.strftime("%Y-%m-%d %H:%M:%S", currTAITuple))
    
        # update local (at APO) mean sidereal time, in degrees
        currUTCTuple= time.gmtime(currPythonSeconds)
        currUTCMJD = RO.Astro.Tm.mjdFromPyTuple(currUTCTuple)
        currLMST = RO.Astro.Tm.lmstFromUT1(currUTCMJD, TUI.TCC.TelConst.Longitude) * RO.PhysConst.HrsPerDeg
        self.lmstWdg.set(currLMST)
        
        currTAIDays = RO.Astro.Tm.taiFromPySec(currPythonSeconds)
        currSDSSMJD = int(currTAIDays + 0.3) # assumes int truncates
        self.sjdWdg.set(currSDSSMJD)
        
        # schedule the next event for the next integer second plus a bit
        clockDelay = 1.01 - (currPythonSeconds % 1.0)
        self._clockTimer.start(clockDelay, self._updateClock)