diff options
Diffstat (limited to 'gr-qtgui/python/qtgui/compass.py')
-rw-r--r-- | gr-qtgui/python/qtgui/compass.py | 290 |
1 files changed, 290 insertions, 0 deletions
diff --git a/gr-qtgui/python/qtgui/compass.py b/gr-qtgui/python/qtgui/compass.py new file mode 100644 index 0000000000..2c8a766f14 --- /dev/null +++ b/gr-qtgui/python/qtgui/compass.py @@ -0,0 +1,290 @@ +#!/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 +# +# + +import time +import numpy +from gnuradio import gr +import pmt + +# First Qt and 2nd Qt are different. You'll get errors if they're both not available, +# hence the import-as to avoid name collisions + +from PyQt5 import Qt +from PyQt5.QtCore import Qt as Qtc +from PyQt5.QtCore import pyqtSignal, QPoint, pyqtProperty +from PyQt5.QtWidgets import QFrame, QWidget, QVBoxLayout, QHBoxLayout, QLabel +from PyQt5.QtGui import QPainter, QPalette, QFont, QFontMetricsF, QPen, QPolygon, QColor, QBrush + +NeedleFull = 1 +NeedleIndicator = 0 +NeedleMirrored = 2 + +class LabeledCompass(QFrame): + def __init__(self, lbl, min_size, update_time, setDebug=False, + needleType=NeedleFull, position=1, backgroundColor='default'): + # Positions: 1 = above, 2=below, 3=left, 4=right + QFrame.__init__(self) + self.numberControl = Compass(min_size, update_time, setDebug, + needleType, position, backgroundColor) + + if position < 3: + layout = QVBoxLayout() + else: + layout = QHBoxLayout() + + self.lbl = lbl + self.lblcontrol = QLabel(lbl, self) + self.lblcontrol.setAlignment(Qtc.AlignCenter) + + # add top or left + if lbl: + if position == 1 or position == 3: + layout.addWidget(self.lblcontrol) + else: + self.hasLabel = False + + layout.addWidget(self.numberControl) + + # Add bottom or right + if lbl: + if position == 2 or position == 4: + layout.addWidget(self.lblcontrol) + + layout.setAlignment(Qtc.AlignCenter | Qtc.AlignVCenter) + self.setLayout(layout) + + if lbl: + self.setMinimumSize(min_size+30, min_size+35) + else: + self.setMinimumSize(min_size, min_size) + + self.show() + def change_angle(self, angle): + self.numberControl.change_angle(angle) + + def setColors(self, backgroundColor='default', needleTip='red', needleBody='black', + scaleColor='black'): + self.numberControl.setColors(backgroundColor, needleTip, needleBody, scaleColor) + +class Compass(QWidget): + angleChanged = pyqtSignal(float) + + def __init__(self, min_size, update_time, setDebug=False, needleType=NeedleFull, + position=1, backgroundColor='default'): + QWidget.__init__(self, None) + + # Set parameters + self.debug = setDebug + self.needleType = needleType + self.update_period = update_time + self.last = time.time() + self.next_angle = 0 + + self._angle = 0.0 + self._margins = 2 + self._pointText = {0: "0", 45: "45", 90: "90", 135: "135", 180: "180", + 225: "225", 270: "270", 315: "315"} + + self.setMinimumSize(min_size, min_size) + self.setSizePolicy(Qt.QSizePolicy.Expanding, Qt.QSizePolicy.Expanding) + + self.backgroundColor = backgroundColor + self.needleTipColor = 'red' + self.needleBodyColor = 'black' + self.scaleColor = 'black' + + def setColors(self, backgroundColor='default', needleTipColor='red', needleBodyColor='black', + scaleColor='black'): + self.backgroundColor = backgroundColor + self.needleTipColor = needleTipColor + self.needleBodyColor = needleBodyColor + self.scaleColor = scaleColor + + super().update() + + def paintEvent(self, event): + painter = QPainter() + painter.begin(self) + painter.setRenderHint(QPainter.Antialiasing) + + if self.backgroundColor == 'default': + painter.fillRect(event.rect(), self.palette().brush(QPalette.Window)) + else: + size = self.size() + center_x = size.width()/2 + diameter = size.height() + brush = QBrush(QColor(self.backgroundColor), Qtc.SolidPattern) + painter.setBrush(brush) + painter.setPen(QPen(QColor(self.scaleColor), 2)) + painter.setRenderHint(QPainter.Antialiasing) + painter.drawEllipse(center_x-diameter/2+1, 1, diameter-4, diameter-4) + + self.drawMarkings(painter) + self.drawNeedle(painter) + + painter.end() + + def drawMarkings(self, painter): + painter.save() + painter.translate(self.width()/2, self.height()/2) + scale = min((self.width() - self._margins)/120.0, + (self.height() - self._margins)/120.0) + painter.scale(scale, scale) + + font = QFont(self.font()) + font.setPixelSize(8) + metrics = QFontMetricsF(font) + + painter.setFont(font) + painter.setPen(QPen(QColor(self.scaleColor))) + tickInterval = 5 + i = 0 + while i < 360: + + if i % 45 == 0: + painter.drawLine(0, -40, 0, -50) + painter.drawText(-metrics.width(self._pointText[i])/2.0, -52, self._pointText[i]) + else: + painter.drawLine(0, -45, 0, -50) + + painter.rotate(tickInterval) + i += tickInterval + + painter.restore() + + def drawNeedle(self, painter): + painter.save() + # Set up painter + painter.translate(self.width()/2, self.height()/2) + scale = min((self.width() - self._margins)/120.0, + (self.height() - self._margins)/120.0) + painter.scale(scale, scale) + painter.setPen(QPen(Qtc.NoPen)) + + # Rotate surface for painting + intAngle = int(round(self._angle)) + painter.rotate(intAngle) + + # Draw the full black needle first if needed + if self.needleType == NeedleFull: + needleTailBrush = self.palette().brush(QPalette.Shadow) + needleTailColor = QColor(self.needleBodyColor) + needleTailBrush.setColor(needleTailColor) + painter.setBrush(needleTailBrush) + + painter.drawPolygon(QPolygon([QPoint(-6, 0), QPoint(0, -45), QPoint(6, 0), + QPoint(0, 45), QPoint(-6, 0)])) + + # Now draw the red tip (on top of the black needle) + needleTipBrush = self.palette().brush(QPalette.Highlight) + needleTipColor = QColor(self.needleTipColor) + needleTipBrush.setColor(needleTipColor) + painter.setBrush(needleTipBrush) + + # First QPoint is the center bottom apex of the needle + painter.drawPolygon(QPolygon([QPoint(-3, -24), QPoint(0, -45), QPoint(3, -23), + QPoint(0, -30), QPoint(-3, -23)])) + + if self.needleType == NeedleMirrored: + # Rotate + # Need to account for the initial rotation to see how much more to rotate it. + if (intAngle == 90 or intAngle == -90 or intAngle == 270): + mirrorRotation = 180 + else: + mirrorRotation = 180 - intAngle - intAngle + painter.rotate(mirrorRotation) + + # Paint shadowed indicator + needleTipBrush = self.palette().brush(QPalette.Highlight) + needleTipColor = Qtc.gray + needleTipBrush.setColor(needleTipColor) + painter.setBrush(needleTipBrush) + + painter.drawPolygon( + QPolygon([QPoint(-3, -25), QPoint(0, -45), QPoint(3, -25), + QPoint(0, -30), QPoint(-3, -25)]) + ) + + painter.restore() + + def angle(self): + return self._angle + + def change_angle(self, angle): + if angle != self._angle: + if self.debug: + gr.log.info(("Compass angle: " + str(angle))) + + if angle < 0.0: + angle = 360.0 + angle # Angle will already be negative + + self._angle = angle + self.angleChanged.emit(angle) + self.update() + + angle = pyqtProperty(float, angle, change_angle) + + +class GrCompass(gr.sync_block, LabeledCompass): + """ + This block takes angle in degrees as input and displays it on a compass. + Three different needle formats are available, Full, indicator only, + and mirrored (mirrored is useful for direction-finding where an + ambiguity exists in front/back detection angle). + """ + def __init__(self, title, min_size, update_time, setDebug=False, needleType=NeedleFull, + usemsg=False, position=1, backgroundColor='default'): + if usemsg: + gr.sync_block.__init__(self, name="QTCompass", in_sig=[], out_sig=[]) + else: + gr.sync_block.__init__(self, name="QTCompass", in_sig=[numpy.float32], out_sig=[]) + + LabeledCompass.__init__(self, title, min_size, update_time, setDebug, needleType, + position, backgroundColor) + + self.last = time.time() + self.update_period = update_time + self.useMsg = usemsg + + self.next_angle = 0.0 + + self.message_port_register_in(pmt.intern("angle")) + self.set_msg_handler(pmt.intern("angle"), self.msgHandler) + + def msgHandler(self, msg): + try: + new_val = pmt.to_python(pmt.cdr(msg)) + + if type(new_val) == float or type(new_val) == int: + super().change_angle(float(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 setColors(self, backgroundColor='default', needleTip='red', needleBody='black', + scaleColor='black'): + super().setColors(backgroundColor, needleTip, needleBody, scaleColor) + + def work(self, input_items, output_items): + if self.useMsg: + return len(input_items[0]) + + # Average inputs + self.next_angle = numpy.mean(input_items[0]) + + if (time.time() - self.last) > self.update_period: + self.last = time.time() + super().change_angle(self.next_angle) + + # Consume all inputs + return len(input_items[0]) |