#!/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 PyQt4 import QtCore,Qt
import PyQt4.QtGui as QtGui
import os, sys, time, struct

from gnuradio import gr, ctrlport
from gnuradio.ctrlport.GrDataPlotter import *

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, radioclient):

        super(MAINWindow, self).__init__()
        self.radioclient = radioclient
        self.updateRate = 1000;
        self.conns = []
        self.plots = []
        self.knobprops = []

        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(radioclient)
        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, csomeBool):
        child = MForm(self.radioclient, len(self.conns), parent = self, dialogprompt = not csomeBool)
        if(child.radioclient is not None):
            child.setWindowTitle(str(child.radioclient))
            self.mdiArea.addSubWindow(child)
            self.mdiArea.currentSubWindow().showMaximized()
        self.conns.append(child)
        self.plots.append([])

    def propertiesMenu(self, key, radio, uid):
        title = str(radio)

        props = radio.properties([key])

        pmin,pmax = get_minmax(props[key])

        # 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, strip)

        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]

        strr = str(tree.radioclient)
        print(strr)
#         r = strr.split(" ")
        title = strr #title = "{0}:{1}".format(r[3], r[5])
        pmin,pmax = get_minmax(knobprop)

        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, stripchart=False):
        plot = GrDataPlotterConst(tag, 32e6, pmin, pmax, stripchart)
        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:
                d = knobs[n].value
                # TODO: FIX COMPLEX!
#                 if(type(d) == GNURadio.complex):
#                     d = [d.re, d.im]

                # If it's a byte stream, Python thinks it's a string.
                # Unpack and convert to floats for plotting.
                if(type(d) == str and n.find('probe2_b') == 0):
                    d = struct.unpack(len(d)*'b', d)
                    d = [float(di) for di in d]

                data.append(d)
            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.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.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.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 = radio.printProperties(self.props)

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

        # Test here to make sure that a 'set' function exists
        try:
            radio.setKnobs(radio.getKnobs([key]))
            has_set = True
        except:
            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.getKnobs([key])
            val = rv[key].value
            if(type(val) == ControlPort.complex):
                val = val.re + val.im*1j

            self.textInput.setText(str(val))
            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)
            else:
                self._set_slider_value = None

            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())
        elif(type(self.sv.value) == ControlPort.complex):
            t = str(self.textInput.text())
            t = complex(t.strip("(").strip(")").replace(" ", ""))
            val = ControlPort.complex()
            val.re = t.real
            val.im = t.imag
        else:
            sys.stderr.write("set type not supported! ({0})\n".format(type(self.sv.value)))
            return

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

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


class MForm(QtGui.QWidget):
    def update(self):
        # TODO: revisit this try-except block, figure out what it's doing, and if we need to keep it. at very lease makes debugging dificult
        if True: #try:
            st = time.time();
            knobs = self.radioclient.getKnobs([])
            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:
        #try:
        self.table.updateItems(knobs, self.knobprops)
        #except:
        #    self.knobprops = self.radioclient.properties([])
        #    print("knobsprops1:", len(self.knobprops))

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


    def __init__(self, radioclient, uid=0, updateRate=2000, parent=None, dialogprompt = False):

        super(MForm, self).__init__()
        self.radioclient = radioclient
#         print("before radioclient.getHost()", radioclient.getHost(), radioclient.getPort(), "prompt", prompt)
        if(dialogprompt or radioclient.getHost() is None or radioclient.getPort() is None):
#             print("before ConInfoDialog")
            askinfo = ConInfoDialog(self);
            if askinfo.exec_():
                host = str(askinfo.host.text());
                port = str(askinfo.port.text());
#                 print("before radioclient.newConnection host: %s port: %s"%(host,port))
                newradio = self.radioclient.newConnection(host, port)
                if newradio is None:
                    print("Error making a %s connection to %s:%s from %s" % (radioclient.getName(), host, port, radioclient))
                else:
                    self.radioclient = newradio

            else:
                self.radioclient = Nonclient = None
                return


        self.uid = uid
        self.parent = parent
        self.horizontalLayout = QtGui.QVBoxLayout(self)
        self.gridLayout = QtGui.QGridLayout()

        self.knobprops = self.radioclient.properties([])
        #print("props5:", self.knobprops)
        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.radioclient, self.uid)


def get_minmax(p):
    pmin = p.min.value
    pmax = p.max.value

    # Find min/max or real or imag for GNURadio::complex
    # TODO: fix complex
    if(type(pmin) == ControlPort.complex):
        pmin = min(pmin.re, pmin.im)
    if(type(pmax) == ControlPort.complex):
        pmax = max(pmax.re, pmax.im)

    # If it's a byte stream, Python thinks it's a string.
    try:
        if(type(pmin) == str):
            pmin = struct.unpack('b', pmin)[0]
        if(type(pmax) == str):
            pmax = struct.unpack('b', pmax)[0]
    except struct.error:
        pmin = []
        pmax = []

    if pmin == []:
        pmin = None
    else:
        pmin = 1.1*float(pmin)
    if pmax == []:
        pmax = None
    else:
        pmax = 1.1*float(pmax)

    return pmin, pmax

class MyApp(object):
    def __init__(self, args):
        from gnuradio.ctrlport.GNURadioControlPortClient import GNURadioControlPortClient
        GNURadioControlPortClient(args, 'thrift', self.run, QtGui.QApplication(sys.argv).exec_)

    def run(self, client):
        MAINWindow(client).show()

MyApp(sys.argv)
