diff options
author | ghostop14 <ghostop14@gmail.com> | 2020-02-02 13:52:39 -0500 |
---|---|---|
committer | devnulling <devnulling@users.noreply.github.com> | 2020-02-19 15:46:54 -0800 |
commit | 5cd7b4cd472e9dca41f19e2cdfed4393374c9fe0 (patch) | |
tree | cb6a607483d03510b90d43c5956e88b691744fff /gr-qtgui/python/qtgui/digitalnumbercontrol.py | |
parent | bebed18070bb3366c53f8d1b775c5ed5959859ea (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.py | 346 |
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) + |