#!/usr/bin/env python
#
# Copyright 2012,2013 Free Software Foundation, Inc.
#
# This file is part of GNU Radio
#
# GNU Radio is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
#
# GNU Radio is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with GNU Radio; see the file COPYING.  If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street,
# Boston, MA 02110-1301, USA.
#

from gnuradio import gr, ctrlport

from PyQt4 import QtCore,Qt
import PyQt4.QtGui as QtGui
import os, sys, time

import Ice
from gnuradio.ctrlport.IceRadioClient import *
from gnuradio.ctrlport.GrDataPlotter import *
from gnuradio.ctrlport import GNURadio

class RateDialog(QtGui.QDialog):
    def __init__(self, delay, parent=None):
        super(RateDialog, self).__init__(parent)
        self.gridLayout = QtGui.QGridLayout(self)
        self.setWindowTitle("Update Delay (ms)");
        self.delay = QtGui.QLineEdit(self);
        self.delay.setText(str(delay));
        self.buttonBox = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel)
        self.gridLayout.addWidget(self.delay);
        self.gridLayout.addWidget(self.buttonBox);
        self.buttonBox.accepted.connect(self.accept)
        self.buttonBox.rejected.connect(self.reject)
    def accept(self):
        self.done(1);
    def reject(self):
        self.done(0);

class MAINWindow(QtGui.QMainWindow):
    def minimumSizeHint(self):
        return Qtgui.QSize(800,600)

    def __init__(self, radio, port, interface):
        
        super(MAINWindow, self).__init__()
        self.updateRate = 1000;
        self.conns = []
        self.plots = []
        self.knobprops = []
        self.interface = interface

        self.mdiArea = QtGui.QMdiArea()
        self.mdiArea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
        self.mdiArea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
        self.setCentralWidget(self.mdiArea)

        self.mdiArea.subWindowActivated.connect(self.updateMenus)
        self.windowMapper = QtCore.QSignalMapper(self)
        self.windowMapper.mapped[QtGui.QWidget].connect(self.setActiveSubWindow)

        self.createActions()
        self.createMenus()
        self.createToolBars()
        self.createStatusBar()
        self.updateMenus()

        self.setWindowTitle("GNU Radio Control Port Monitor")
        self.setUnifiedTitleAndToolBarOnMac(True)

        self.newCon(radio, port)
        icon = QtGui.QIcon(ctrlport.__path__[0] + "/icon.png" )
        self.setWindowIcon(icon)

        # Locally turn off ControlPort export from GR. This prevents
        # our GR-based plotters from launching their own ControlPort
        # instance (and possibly causing a port collision if one has
        # been specified).
        os.environ['GR_CONF_CONTROLPORT_ON'] = 'False'

    def setUpdateRate(self,nur):
        self.updateRate = int(nur);
        for c in self.conns:
            c.updateRate = self.updateRate;
            c.timer.setInterval(self.updateRate);

    def newCon(self, radio=None, port=None):
        child = MForm(radio, port, len(self.conns), self.updateRate, self)
        if(child.radio is not None):
            child.setWindowTitle(str(child.radio))
            self.mdiArea.addSubWindow(child)
            child.showMaximized()
        self.conns.append(child)
        self.plots.append([])

    def propertiesMenu(self, key, radio, uid):
        r = str(radio).split(" ")
        title = "{0}:{1}".format(r[3], r[5])

        props = radio.properties([key])
        pmin = props[key].min.value
        pmax = props[key].max.value
        if pmin == []:
            pmin = None
        else:
            pmin = 1.1*pmin
        if pmax == []:
            pmax = None
        else:
            pmax = 1.1*pmax

        # Use display option mask of item to set up available plot
        # types and default options.
        disp = self.knobprops[uid][key].display
        cplx = disp & gr.DISPOPTCPLX | disp & gr.DISPXY
        strip = disp & gr.DISPOPTSTRIP
        stem = disp & gr.DISPOPTSTEM
        log = disp & gr.DISPOPTLOG
        scatter = disp & gr.DISPOPTSCATTER

        def newUpdaterProxy():
            self.newUpdater(key, radio)

        def newPlotterFProxy():
            self.newPlotF(key, uid, title, pmin, pmax,
                          log, strip, stem)

        def newPlotterCProxy():
            self.newPlotC(key, uid, title, pmin, pmax,
                          log, strip, stem)

        def newPlotterConstProxy():
            self.newPlotConst(key, uid, title, pmin, pmax, scatter)

        def newPlotterPsdFProxy():
            self.newPlotPsdF(key, uid, title)

        def newPlotterPsdCProxy():
            self.newPlotPsdC(key, uid, title)

        def newPlotterRasterFProxy():
            self.newPlotRasterF(key, uid, title, pmin, pmax)

        def newPlotterRasterBProxy():
            self.newPlotRasterB(key, uid, title, pmin, pmax)

        menu = QtGui.QMenu(self)
        menu.setTitle("Item Actions")
        menu.setTearOffEnabled(False)

        # object properties
        menu.addAction("Properties", newUpdaterProxy)

        # displays available
        if(cplx == 0):
            menu.addAction("Plot Time", newPlotterFProxy)
            menu.addAction("Plot PSD", newPlotterPsdFProxy)
            menu.addAction("Plot Raster (real)", newPlotterRasterFProxy)
            #menu.addAction("Plot Raster (bits)", newPlotterRasterBProxy)
        else:
            menu.addAction("Plot Time", newPlotterCProxy)
            menu.addAction("Plot PSD", newPlotterPsdCProxy)
            menu.addAction("Plot Constellation", newPlotterConstProxy)

        menu.popup(QtGui.QCursor.pos())

    def newUpdater(self, key, radio):
        updater = UpdaterWindow(key, radio, None)
        updater.setWindowTitle("Updater: " + key)
        updater.setModal(False)
        updater.exec_()

    def newSub(self, e):
        tag = str(e.text(0))
        tree = e.treeWidget().parent()
        uid = tree.uid
        knobprop = self.knobprops[uid][tag]

        r = str(tree.radio).split(" ")
        title = "{0}:{1}".format(r[3], r[5])
        pmin = knobprop.min.value
        pmax = knobprop.max.value
        if pmin == []:
            pmin = None
        else:
            pmin = 1.1*pmin
        if pmax == []:
            pmax = None
        else:
            pmax = 1.1*pmax

        disp = knobprop.display
        if(disp & gr.DISPTIME):
            strip = disp & gr.DISPOPTSTRIP
            stem = disp & gr.DISPOPTSTEM
            log = disp & gr.DISPOPTLOG
            if(disp & gr.DISPOPTCPLX == 0):
                self.newPlotF(tag, uid, title, pmin, pmax,
                              log, strip, stem)
            else:
                self.newPlotC(tag, uid, title, pmin, pmax,
                              log, strip, stem)
            
        elif(disp & gr.DISPXY):
            scatter = disp & gr.DISPOPTSCATTER
            self.newPlotConst(tag, uid, title, pmin, pmax, scatter)
            
        elif(disp & gr.DISPPSD):
            if(disp & gr.DISPOPTCPLX == 0):
                self.newPlotPsdF(tag, uid, title)
            else:
                self.newPlotPsdC(tag, uid, title)

    def startDrag(self, e):
        drag = QtGui.QDrag(self)
        mime_data = QtCore.QMimeData()

        tag = str(e.text(0))
        tree = e.treeWidget().parent()
        knobprop = self.knobprops[tree.uid][tag]
        disp = knobprop.display
        iscomplex = (disp & gr.DISPOPTCPLX) or (disp & gr.DISPXY)
        
        if(disp != gr.DISPNULL):
            data = "PlotData:::{0}:::{1}".format(tag, iscomplex)
        else:
            data = "OtherData:::{0}:::{1}".format(tag, iscomplex)

        mime_data.setText(data)
        drag.setMimeData(mime_data)

        drop = drag.start()

    def createPlot(self, plot, uid, title):
        plot.start()
        self.plots[uid].append(plot)

        self.mdiArea.addSubWindow(plot)
        plot.setWindowTitle("{0}: {1}".format(title, plot.name()))
        self.connect(plot.qwidget(),
                     QtCore.SIGNAL('destroyed(QObject*)'),
                     self.destroyPlot)

        # when the plot is updated via drag-and-drop, we need to be
        # notified of the new qwidget that's created so we can
        # properly destroy it.
        plot.plotupdated.connect(self.plotUpdated)

        plot.show()

    def plotUpdated(self, q):
        # the plot has been updated with a new qwidget; make sure this
        # gets dies to the destroyPlot function.
        for i, plots in enumerate(self.plots):
            for p in plots:
                if(p == q):
                    #plots.remove(p)
                    #plots.append(q)
                    self.connect(q.qwidget(),
                                 QtCore.SIGNAL('destroyed(QObject*)'),
                                 self.destroyPlot)
                    break

    def destroyPlot(self, obj):
        for plots in self.plots:
            for p in plots:
                if p.qwidget() == obj:
                    plots.remove(p)
                    break

    def newPlotConst(self, tag, uid, title="", pmin=None, pmax=None,
                     scatter=False):
        plot = GrDataPlotterConst(tag, 32e6, pmin, pmax)
        plot.scatter(scatter)
        self.createPlot(plot, uid, title)
    
    def newPlotF(self, tag, uid, title="", pmin=None, pmax=None, 
                 logy=False, stripchart=False, stem=False):
        plot = GrDataPlotterF(tag, 32e6, pmin, pmax, stripchart)
        plot.semilogy(logy)
        plot.stem(stem)
        self.createPlot(plot, uid, title)

    def newPlotC(self, tag, uid, title="", pmin=None, pmax=None,
                 logy=False, stripchart=False, stem=False):
        plot = GrDataPlotterC(tag, 32e6, pmin, pmax, stripchart)
        plot.semilogy(logy)
        plot.stem(stem)
        self.createPlot(plot, uid, title)

    def newPlotPsdF(self, tag, uid, title="", pmin=None, pmax=None):
        plot = GrDataPlotterPsdF(tag, 32e6, pmin, pmax)
        self.createPlot(plot, uid, title)

    def newPlotPsdC(self, tag, uid, title="", pmin=None, pmax=None):
        plot = GrDataPlotterPsdC(tag, 32e6, pmin, pmax)
        self.createPlot(plot, uid, title)

    def newPlotRasterF(self, tag, uid, title="", pmin=None, pmax=None):
        plot = GrTimeRasterF(tag, 32e6, pmin, pmax)
        self.createPlot(plot, uid, title)

    def newPlotRasterB(self, tag, uid, title="", pmin=None, pmax=None):
        plot = GrTimeRasterB(tag, 32e6, pmin, pmax)
        self.createPlot(plot, uid, title)

    def update(self, knobs, uid):
        #sys.stderr.write("KNOB KEYS: {0}\n".format(knobs.keys()))
        for plot in self.plots[uid]:
            data = []
            for n in plot.knobnames:
                data.append(knobs[n].value)
            plot.update(data)
            plot.stop()
            plot.wait()
            plot.start()

    def setActiveSubWindow(self, window):
        if window:
            self.mdiArea.setActiveSubWindow(window)


    def createActions(self):
        self.newConAct = QtGui.QAction("&New Connection",
                self, shortcut=QtGui.QKeySequence.New,
                statusTip="Create a new file", triggered=self.newCon)
        #self.newAct = QtGui.QAction(QtGui.QIcon(':/images/new.png'), "&New Plot",
        self.newPlotAct = QtGui.QAction("&New Plot",
                self,
                statusTip="Create a new file", triggered=self.newPlotF)

        self.exitAct = QtGui.QAction("E&xit", self, shortcut="Ctrl+Q",
                statusTip="Exit the application",
                triggered=QtGui.qApp.closeAllWindows)

        self.closeAct = QtGui.QAction("Cl&ose", self, shortcut="Ctrl+F4",
                statusTip="Close the active window",
                triggered=self.mdiArea.closeActiveSubWindow)

        self.closeAllAct = QtGui.QAction("Close &All", self,
                statusTip="Close all the windows",
                triggered=self.mdiArea.closeAllSubWindows)

        self.urAct = QtGui.QAction("Update Rate", self, shortcut="F5",
                statusTip="Change Update Rate",
                triggered=self.updateRateShow)

        qks = QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_T);
        self.tileAct = QtGui.QAction("&Tile", self,
                statusTip="Tile the windows",
                triggered=self.mdiArea.tileSubWindows,
                shortcut=qks)

        qks = QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_C);
        self.cascadeAct = QtGui.QAction("&Cascade", self,
                statusTip="Cascade the windows", shortcut=qks,
                triggered=self.mdiArea.cascadeSubWindows)

        self.nextAct = QtGui.QAction("Ne&xt", self,
                shortcut=QtGui.QKeySequence.NextChild,
                statusTip="Move the focus to the next window",
                triggered=self.mdiArea.activateNextSubWindow)

        self.previousAct = QtGui.QAction("Pre&vious", self,
                shortcut=QtGui.QKeySequence.PreviousChild,
                statusTip="Move the focus to the previous window",
                triggered=self.mdiArea.activatePreviousSubWindow)

        self.separatorAct = QtGui.QAction(self)
        self.separatorAct.setSeparator(True)

        self.aboutAct = QtGui.QAction("&About", self,
                statusTip="Show the application's About box",
                triggered=self.about)

        self.aboutQtAct = QtGui.QAction("About &Qt", self,
                statusTip="Show the Qt library's About box",
                triggered=QtGui.qApp.aboutQt)

    def createMenus(self):
        self.fileMenu = self.menuBar().addMenu("&File")
        self.fileMenu.addAction(self.newConAct)
        self.fileMenu.addAction(self.newPlotAct)
        self.fileMenu.addAction(self.urAct)
        self.fileMenu.addSeparator()
        self.fileMenu.addAction(self.exitAct)

        self.windowMenu = self.menuBar().addMenu("&Window")
        self.updateWindowMenu()
        self.windowMenu.aboutToShow.connect(self.updateWindowMenu)

        self.menuBar().addSeparator()

        self.helpMenu = self.menuBar().addMenu("&Help")
        self.helpMenu.addAction(self.aboutAct)
        self.helpMenu.addAction(self.aboutQtAct)

    def updateRateShow(self):
        askrate = RateDialog(self.updateRate, self);
        if askrate.exec_():
            ur = float(str(askrate.delay.text()));
            self.setUpdateRate(ur);
            return;
        else:
            return;

    def createToolBars(self):
        self.fileToolBar = self.addToolBar("File")
        self.fileToolBar.addAction(self.newConAct)
        self.fileToolBar.addAction(self.newPlotAct)
        self.fileToolBar.addAction(self.urAct)

        self.fileToolBar = self.addToolBar("Window")
        self.fileToolBar.addAction(self.tileAct)
        self.fileToolBar.addAction(self.cascadeAct)

    def createStatusBar(self):
        self.statusBar().showMessage("Ready")


    def activeMdiChild(self):
        activeSubWindow = self.mdiArea.activeSubWindow()
        if activeSubWindow:
            return activeSubWindow.widget()
        return None

    def updateMenus(self):
        hasMdiChild = (self.activeMdiChild() is not None)
        self.closeAct.setEnabled(hasMdiChild)
        self.closeAllAct.setEnabled(hasMdiChild)
        self.tileAct.setEnabled(hasMdiChild)
        self.cascadeAct.setEnabled(hasMdiChild)
        self.nextAct.setEnabled(hasMdiChild)
        self.previousAct.setEnabled(hasMdiChild)
        self.separatorAct.setVisible(hasMdiChild)

    def updateWindowMenu(self):
        self.windowMenu.clear()
        self.windowMenu.addAction(self.closeAct)
        self.windowMenu.addAction(self.closeAllAct)
        self.windowMenu.addSeparator()
        self.windowMenu.addAction(self.tileAct)
        self.windowMenu.addAction(self.cascadeAct)
        self.windowMenu.addSeparator()
        self.windowMenu.addAction(self.nextAct)
        self.windowMenu.addAction(self.previousAct)
        self.windowMenu.addAction(self.separatorAct)

    def about(self):
        about_info = \
'''Copyright 2012 Free Software Foundation, Inc.\n
This program is part of GNU Radio.\n
GNU Radio is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version.\n
GNU Radio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.\n
You should have received a copy of the GNU General Public License along with GNU Radio; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Boston, MA 02110-1301, USA.'''

        QtGui.QMessageBox.about(None, "gr-ctrlport-monitor", about_info)


class ConInfoDialog(QtGui.QDialog):
    def __init__(self, parent=None):
        super(ConInfoDialog, self).__init__(parent)

        self.gridLayout = QtGui.QGridLayout(self)
        

        self.host = QtGui.QLineEdit(self);
        self.port = QtGui.QLineEdit(self);
        self.host.setText("localhost");
        self.port.setText("43243");

        self.buttonBox = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel)

        self.gridLayout.addWidget(self.host);
        self.gridLayout.addWidget(self.port);
        self.gridLayout.addWidget(self.buttonBox);

        self.buttonBox.accepted.connect(self.accept)
        self.buttonBox.rejected.connect(self.reject)


    def accept(self):
        self.done(1);

    def reject(self):
        self.done(0);


class UpdaterWindow(QtGui.QDialog):
    def __init__(self, key, radio, parent):
        QtGui.QDialog.__init__(self, parent)

        self.key = key;
        self.radio = radio

        self.resize(300,200)
        self.layout = QtGui.QVBoxLayout()

        self.props = radio.properties([key])[key]
        info = str(self.props)

        self.infoLabel = QtGui.QLabel(info)
        self.layout.addWidget(self.infoLabel)

        # Test here to make sure that a 'set' function
        try:
            a = radio.set(radio.get([key]))
            has_set = True
        except Ice.UnknownException:
            has_set = False

        if(has_set is False):
            self.cancelButton = QtGui.QPushButton("Ok")
            self.cancelButton.connect(self.cancelButton, QtCore.SIGNAL('clicked()'), self.reject)

            self.buttonlayout = QtGui.QHBoxLayout()
            self.buttonlayout.addWidget(self.cancelButton)
            self.layout.addLayout(self.buttonlayout)
 
        else: # we have a set function
            self.textInput = QtGui.QLineEdit()
            self.layout.addWidget(self.textInput)

            self.applyButton = QtGui.QPushButton("Apply")
            self.setButton = QtGui.QPushButton("OK")
            self.cancelButton = QtGui.QPushButton("Cancel")

            rv = radio.get([key])
            self.textInput.setText(str(rv[key].value))
            self.sv = rv[key]

            self.applyButton.connect(self.applyButton, QtCore.SIGNAL('clicked()'), self._apply)
            self.setButton.connect(self.setButton, QtCore.SIGNAL('clicked()'), self._set)
            self.cancelButton.connect(self.cancelButton, QtCore.SIGNAL('clicked()'), self.reject)
 
            self.is_num = ((type(self.sv.value)==float) or (type(self.sv.value)==int))
            if(self.is_num):
                self.sliderlayout = QtGui.QHBoxLayout()

                self.slider = QtGui.QSlider(QtCore.Qt.Horizontal)

                self.sliderlayout.addWidget(QtGui.QLabel(str(self.props.min.value)))
                self.sliderlayout.addWidget(self.slider)
                self.sliderlayout.addWidget(QtGui.QLabel(str(self.props.max.value)))

                self.steps = 10000
                self.valspan = self.props.max.value - self.props.min.value
            
                self.slider.setRange(0, 10000)
                self._set_slider_value(self.sv.value)

                self.connect(self.slider, QtCore.SIGNAL("sliderReleased()"), self._slide)

                self.layout.addLayout(self.sliderlayout)

                self.buttonlayout = QtGui.QHBoxLayout()
                self.buttonlayout.addWidget(self.applyButton)
                self.buttonlayout.addWidget(self.setButton)
                self.buttonlayout.addWidget(self.cancelButton)
                self.layout.addLayout(self.buttonlayout)

        # set layout and go...
        self.setLayout(self.layout)
            
    def _set_slider_value(self, val):
        self.slider.setValue(self.steps*(val-self.props.min.value)/self.valspan)
             
    def _slide(self):
        val = (self.slider.value()*self.valspan + self.props.min.value)/float(self.steps)
        self.textInput.setText(str(val))

    def _apply(self):
        if(type(self.sv.value) == str):
            val = str(self.textInput.text())
        elif(type(self.sv.value) == int):
            val = int(round(float(self.textInput.text())))
        elif(type(self.sv.value) == float):
            val = float(self.textInput.text())
        else:
            sys.stderr.write("set type not supported! ({0})\n".format(type(self.sv.value)))
            sys.exit(-1)

        self.sv.value = val
        km = {}
        km[self.key] = self.sv
        self.radio.set(km)
        self._set_slider_value(self.sv.value)

    def _set(self):
        self._apply()
        self.done(0)


class MForm(QtGui.QWidget):
    def update(self):
        try:
            st = time.time();
            knobs = self.radio.get([]);
            ft = time.time();
            latency = ft-st;
            self.parent.statusBar().showMessage("Current GNU Radio Control Port Query Latency: %f ms"%(latency*1000))
            
        except Exception, e:
            sys.stderr.write("ctrlport-monitor: radio.get threw exception ({0}).\n".format(e))
            if(type(self.parent) is MAINWindow):
                # Find window of connection
                remove = []
                for p in self.parent.mdiArea.subWindowList():
                    if self.parent.conns[self.uid] == p.widget():
                        remove.append(p)

                # Find any subplot windows of connection
                for p in self.parent.mdiArea.subWindowList():
                    for plot in self.parent.plots[self.uid]:
                        if plot.qwidget() == p.widget():
                            remove.append(p)
                
                # Clean up local references to these
                self.parent.conns.remove(self.parent.conns[self.uid])
                self.parent.plots.remove(self.parent.plots[self.uid])

                # Remove subwindows for connection and plots
                for r in remove:
                    self.parent.mdiArea.removeSubWindow(r)

                # Clean up self
                self.close()
            else:
                sys.exit(1)
            return

        tableitems = knobs.keys()
            
        #UPDATE TABLE:
        self.table.updateItems(knobs, self.knobprops)

        #UPDATE PLOTS
        self.parent.update(knobs, self.uid)


    def __init__(self, radio=None, port=None, uid=0, updateRate=2000, parent=None):

        super(MForm, self).__init__()

        if(radio == None or port == None):
            askinfo = ConInfoDialog(self);
            if askinfo.exec_():
                host = str(askinfo.host.text());
                port = str(askinfo.port.text());
                radio = parent.interface.getRadio(host, port)
            else:
                self.radio = None
                return
        
        self.uid = uid
        self.parent = parent
        self.horizontalLayout = QtGui.QVBoxLayout(self)
        self.gridLayout = QtGui.QGridLayout()

        self.radio = radio
        self.knobprops = self.radio.properties([])
        self.parent.knobprops.append(self.knobprops)
        self.resize(775,500)
        self.timer = QtCore.QTimer()
        self.constupdatediv = 0
        self.tableupdatediv = 0
        plotsize=250
            
        # make table
        self.table = GrDataPlotterValueTable(uid, self, 0, 0, 400, 200)
        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
        self.table.treeWidget.setSizePolicy(sizePolicy)
        self.table.treeWidget.setEditTriggers(QtGui.QAbstractItemView.EditKeyPressed)
        self.table.treeWidget.setSortingEnabled(True)
        self.table.treeWidget.setDragEnabled(True)

        # add things to layouts
        self.horizontalLayout.addWidget(self.table.treeWidget)

        # set up timer   
        self.connect(self.timer, QtCore.SIGNAL('timeout()'), self.update)
        self.updateRate = updateRate;
        self.timer.start(self.updateRate)

        # set up context menu .. 
        self.table.treeWidget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.table.treeWidget.customContextMenuRequested.connect(self.openMenu)

        # Set up double-click to launch default plotter
        self.connect(self.table.treeWidget,
                     QtCore.SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'),
                     self.parent.newSub);

        # Allow drag/drop event from table item to plotter
        self.connect(self.table.treeWidget,
                     QtCore.SIGNAL('itemPressed(QTreeWidgetItem*, int)'),
                     self.parent.startDrag)
        
    def openMenu(self, pos):
        index = self.table.treeWidget.selectedIndexes()
        item = self.table.treeWidget.itemFromIndex(index[0])
        itemname = str(item.text(0))
        self.parent.propertiesMenu(itemname, self.radio, self.uid)
        

class MyClient(IceRadioClient):
    def __init__(self):
        IceRadioClient.__init__(self, MAINWindow)

sys.exit(MyClient().main(sys.argv))