summaryrefslogtreecommitdiff
path: root/gr-qtgui/python/qtgui/compass.py
diff options
context:
space:
mode:
Diffstat (limited to 'gr-qtgui/python/qtgui/compass.py')
-rw-r--r--gr-qtgui/python/qtgui/compass.py290
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])