summaryrefslogtreecommitdiff
path: root/gr-qtgui/python/qtgui/digitalnumbercontrol.py
diff options
context:
space:
mode:
authorghostop14 <ghostop14@gmail.com>2020-02-02 13:52:39 -0500
committerdevnulling <devnulling@users.noreply.github.com>2020-02-19 15:46:54 -0800
commit5cd7b4cd472e9dca41f19e2cdfed4393374c9fe0 (patch)
treecb6a607483d03510b90d43c5956e88b691744fff /gr-qtgui/python/qtgui/digitalnumbercontrol.py
parentbebed18070bb3366c53f8d1b775c5ed5959859ea (diff)
gr-qtgui: Incorporate new GUI controls
These updates expand the user interface capabilities of GNU Radio. This PR includes all of the controls more fully documented here: https://github.com/ghostop14/gr-guiextra
Diffstat (limited to 'gr-qtgui/python/qtgui/digitalnumbercontrol.py')
-rw-r--r--gr-qtgui/python/qtgui/digitalnumbercontrol.py346
1 files changed, 346 insertions, 0 deletions
diff --git a/gr-qtgui/python/qtgui/digitalnumbercontrol.py b/gr-qtgui/python/qtgui/digitalnumbercontrol.py
new file mode 100644
index 0000000000..ea8bdd7eb5
--- /dev/null
+++ b/gr-qtgui/python/qtgui/digitalnumbercontrol.py
@@ -0,0 +1,346 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2020 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+#
+
+from PyQt5.QtWidgets import QFrame, QVBoxLayout, QLabel
+from PyQt5.QtGui import QPainter, QPixmap, QFont, QFontMetrics, QBrush, QColor
+from PyQt5.QtCore import Qt, QSize
+from PyQt5 import QtCore
+from PyQt5.QtCore import Qt as Qtc
+from PyQt5.QtCore import pyqtSignal
+
+from gnuradio import gr
+import pmt
+
+# -------------- Support Classes ---------------------------------
+#
+#
+
+class LabeledDigitalNumberControl(QFrame):
+ def __init__(self, lbl='', min_freq_hz=0, max_freq_hz=6000000000, parent=None,
+ thousands_separator=',', background_color='black', fontColor='white',
+ click_callback=None):
+ QFrame.__init__(self, parent)
+ self.numberControl = DigitalNumberControl(min_freq_hz, max_freq_hz, self,
+ thousands_separator, background_color, fontColor, click_callback)
+
+ layout = QVBoxLayout()
+
+ self.lbl = QLabel(lbl, self)
+ if len:
+ self.hasLabel = True
+ layout.addWidget(self.lbl)
+ else:
+ self.hasLabel = False
+
+ layout.addWidget(self.numberControl)
+ layout.setAlignment(Qtc.AlignCenter | Qtc.AlignVCenter)
+ self.setLayout(layout)
+ self.show()
+
+ def minimumSizeHint(self):
+ if self.hasLabel:
+ return QSize(self.numberControl.minimumWidth()+10, 100)
+ else:
+ return QSize(self.numberControl.minimumWidth()+10, 50)
+
+ def setReadOnly(self, b_read_only):
+ self.numberControl.setReadOnly(b_read_only)
+
+ def setFrequency(self, new_freq):
+ self.numberControl.setFrequency(new_freq)
+
+ def getFrequency(self):
+ return self.numberControl.getFrequency()
+
+class DigitalNumberControl(QFrame):
+ # Notifies to avoid thread conflicts on paints
+ updateInt = pyqtSignal(int)
+ updateFloat = pyqtSignal(float)
+
+ def __init__(self, min_freq_hz=0, max_freq_hz=6000000000, parent=None, thousands_separator=',',
+ background_color='black', fontColor='white', click_callback=None):
+ QFrame.__init__(self, parent)
+
+ self.updateInt.connect(self.onUpdateInt)
+ self.updateFloat.connect(self.onUpdateFloat)
+
+ self.min_freq = int(min_freq_hz)
+ self.max_freq = int(max_freq_hz)
+ self.numDigitsInFreq = len(str(max_freq_hz))
+
+ self.thousands_separator = thousands_separator
+ self.click_callback = click_callback
+
+ self.read_only = False
+
+ self.setColors(QColor(background_color), QColor(fontColor))
+ self.numberFont = QFont("Arial", 12, QFont.Normal)
+
+ self.cur_freq = min_freq_hz
+
+ self.debug_click = False
+
+ # Determine what our width minimum is
+ teststr = ""
+ for i in range(0, self.numDigitsInFreq):
+ teststr += "0"
+
+ fm = QFontMetrics(self.numberFont)
+ if len(self.thousands_separator) > 0:
+ # The -1 makes sure we don't count an extra for 123,456,789. Answer should be 2 not 3.
+ numgroups = int(float(self.numDigitsInFreq-1) / 3.0)
+ if numgroups > 0:
+ for i in range(0, numgroups):
+ teststr += self.thousands_separator
+
+ textstr = teststr
+ else:
+ textstr = teststr
+
+ width = fm.width(textstr)
+
+ self.minwidth = width
+
+ if self.minwidth < 410:
+ self.minwidth = 410
+
+ self.setMaximumHeight(70)
+ self.setMinimumWidth(self.minwidth)
+ # Show the control
+ self.show()
+
+ def minimumSizeHint(self):
+ return QSize(self.minwidth, 50)
+
+ def setReadOnly(self, b_read_only):
+ self.read_only = b_read_only
+
+ def mousePressEvent(self, event):
+ super(DigitalNumberControl, self).mousePressEvent(event)
+ self.offset = event.pos()
+
+ if self.read_only:
+ return
+
+ fm = QFontMetrics(self.numberFont)
+
+ if len(self.thousands_separator) > 0:
+ if self.thousands_separator != ".":
+ textstr = format(self.getFrequency(), self.thousands_separator)
+ else:
+ textstr = format(self.getFrequency(), ",")
+ textstr = textstr.replace(",", ".")
+ else:
+ textstr = str(self.getFrequency())
+
+ width = fm.width(textstr)
+
+ # So we know:
+ # - the width of the text
+ # - The mouse click position relative to 0 (pos relative to string start
+ # will be size().width() - 2 - pos.x
+
+ clickpos = self.size().width() - 2 - self.offset.x()
+
+ found_number = False
+ clicked_thousands = False
+ for i in range(1, len(textstr)+1):
+ width = fm.width(textstr[-i:])
+ charstr = textstr[-i:]
+ widthchar = fm.width(charstr[0])
+ if clickpos >= (width-widthchar) and clickpos <= width:
+ clicked_char = i-1
+
+ clicked_num_index = clicked_char
+
+ found_number = True
+
+ if len(self.thousands_separator) > 0:
+ if charstr[0] != self.thousands_separator:
+ numSeps = charstr.count(self.thousands_separator)
+ clicked_num_index -= numSeps
+ if self.debug_click:
+ gr.log.info("clicked number: " + str(clicked_num_index))
+ else:
+ clicked_thousands = True
+ if self.debug_click:
+ gr.log.info("clicked thousands separator")
+ else:
+ if self.debug_click:
+ gr.log.info("clicked number: " + str(clicked_char))
+
+ # Remember y=0 is at the top so this is reversed
+ clicked_up = False
+ if self.offset.y() > self.size().height()/2:
+ if self.debug_click:
+ gr.log.info('clicked down')
+ else:
+ if self.debug_click:
+ gr.log.info('clicked up')
+ clicked_up = True
+
+ if not clicked_thousands:
+ cur_freq = self.getFrequency()
+ increment = pow(10, clicked_num_index)
+ if clicked_up:
+ cur_freq += increment
+ else:
+ cur_freq -= increment
+
+ self.setFrequency(cur_freq)
+ if self.click_callback is not None:
+ self.click_callback(self.getFrequency())
+
+ break
+
+ if (not found_number) and (not clicked_thousands):
+ # See if we clicked in the high area, if so, increment there.
+ clicked_up = False
+ if self.offset.y() > self.size().height()/2:
+ if self.debug_click:
+ gr.log.info('clicked down in the high area')
+ else:
+ if self.debug_click:
+ gr.log.info('clicked up in the high area')
+ clicked_up = True
+
+ textstr = str(self.getFrequency())
+ numNumbers = len(textstr)
+ increment = pow(10, numNumbers)
+ cur_freq = self.getFrequency()
+ if clicked_up:
+ cur_freq += increment
+ else:
+ cur_freq -= increment
+
+ self.setFrequency(cur_freq)
+ if self.click_callback is not None:
+ self.click_callback(self.getFrequency())
+
+ def setColors(self, background, fontColor):
+ self.background_color = background
+ self.fontColor = fontColor
+
+ def reverseString(self, astring):
+ astring = astring[::-1]
+ return astring
+
+ def onUpdateInt(self, new_freq):
+ if (new_freq >= self.min_freq) and (new_freq <= self.max_freq):
+ self.cur_freq = int(new_freq)
+
+ self.update()
+
+ def onUpdateFloat(self, new_freq):
+ if (new_freq >= self.min_freq) and (new_freq <= self.max_freq):
+ self.cur_freq = int(new_freq)
+
+ self.update()
+
+ def setFrequency(self, new_freq):
+ if type(new_freq) == int:
+ self.updateInt.emit(new_freq)
+ else:
+ self.updateFloat.emit(new_freq)
+
+ def getFrequency(self):
+ return self.cur_freq
+
+ def resizeEvent(self, event):
+ self.pxMap = QPixmap(self.size())
+ self.pxMap.fill(self.background_color)
+
+ self.update()
+
+ def paintEvent(self, event):
+ super().paintEvent(event)
+
+ painter = QPainter(self)
+
+ size = self.size()
+ brush = QBrush()
+ brush.setColor(self.background_color)
+ brush.setStyle(Qt.SolidPattern)
+ rect = QtCore.QRect(2, 2, size.width()-4, size.height()-4)
+ painter.fillRect(rect, brush)
+
+ self.numberFont.setPixelSize(0.9 * size.height())
+ painter.setFont(self.numberFont)
+ painter.setPen(self.fontColor)
+ rect = event.rect()
+
+ if len(self.thousands_separator) > 0:
+ if self.thousands_separator != ".":
+ textstr = format(self.getFrequency(), self.thousands_separator)
+ else:
+ textstr = format(self.getFrequency(), ",")
+ textstr = textstr.replace(",", ".")
+ else:
+ textstr = str(self.getFrequency())
+ rect = QtCore.QRect(0, 0, size.width()-4, size.height())
+ painter.drawText(rect, Qt.AlignRight + Qt.AlignVCenter, textstr)
+
+# ################################################################################
+
+# GNU Radio Class
+class MsgDigitalNumberControl(gr.sync_block, LabeledDigitalNumberControl):
+ def __init__(self, lbl='', min_freq_hz=0, max_freq_hz=6000000000, parent=None,
+ thousands_separator=',', background_color='black', fontColor='white',
+ var_callback=None, outputmsgname='freq'):
+ gr.sync_block.__init__(self, name="MsgDigitalNumberControl",
+ in_sig=None, out_sig=None)
+ LabeledDigitalNumberControl.__init__(self, lbl, min_freq_hz, max_freq_hz, parent,
+ thousands_separator, background_color, fontColor, self.click_callback)
+
+ self.var_callback = var_callback
+ self.outputmsgname = outputmsgname
+
+ self.message_port_register_in(pmt.intern("valuein"))
+ self.set_msg_handler(pmt.intern("valuein"), self.msgHandler)
+ self.message_port_register_out(pmt.intern("valueout"))
+
+ def msgHandler(self, msg):
+ try:
+ new_val = pmt.to_python(pmt.cdr(msg))
+
+ if type(new_val) == float or type(new_val) == int:
+ self.call_var_callback(new_val)
+
+ self.setValue(new_val)
+ else:
+ gr.log.error("Value received was not an int or a float. %s" % str(type(new_val)))
+
+ except Exception as e:
+ gr.log.error("Error with message conversion: %s" % str(e))
+
+ def call_var_callback(self, new_value):
+ if (self.var_callback is not None):
+ if type(self.var_callback) is float:
+ self.var_callback = float(new_value)
+ else:
+ self.var_callback(float(new_value))
+
+ def click_callback(self, new_value):
+ self.call_var_callback(new_value)
+
+ self.message_port_pub(pmt.intern("valueout"), pmt.cons(pmt.intern(self.outputmsgname), pmt.from_float(float(new_value))))
+
+ def setValue(self, new_val):
+ self.setFrequency(new_val)
+
+ self.message_port_pub(pmt.intern("valueout"), pmt.cons(pmt.intern(self.outputmsgname), pmt.from_float(float(self.getFrequency()))))
+
+ def getValue(self):
+ self.getFrequency()
+
+ def setReadOnly(self, b_read_only):
+ super().setReadOnly(b_read_only)
+