summaryrefslogtreecommitdiff
path: root/gr-qtgui/python/qtgui/graphicitem.py
blob: ecc7cc20fc47c0e646ae249070e3a6c1fac36532 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
#!/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 QLabel
from PyQt5.QtGui import QPixmap, QPainter
from PyQt5.QtCore import Qt as Qtc
from PyQt5.QtCore import QSize

import os
import sys

from gnuradio import gr
import pmt

class GrGraphicItem(gr.sync_block, QLabel):
    """
    This block displays the selected graphic item.  You can pass a
    filename as a string in a message to change the image on the fly.
    overlays can also be added by passing in a message with a
    dictionary of a list of dictionaries in the car portion of the
    message.  Each dictionary should have the following keys:
    'filename','x','y', and an optional 'scalefactor'.
    Setting the x/y attributes to -1,-1 will remove an overlay.
    Otherwise items are indexed by filename and can be animated
    throughout the background image.
    """
    def __init__(self, image_file, scaleImage=True, fixedSize=False, setWidth=0, setHeight=0):
        gr.sync_block.__init__(self, name="GrGraphicsItem", in_sig=None, out_sig=None)
        QLabel.__init__(self)

        if not os.path.isfile(image_file):
            gr.log.error("ERROR: Unable to find file " + image_file)
            sys.exit(1)

        try:
            self.pixmap = QPixmap(image_file)
            self.originalPixmap = QPixmap(image_file)
        except OSError as e:
            gr.log.error("ERROR: " + e.strerror)
            sys.exit(1)

        self.image_file = image_file
        self.scaleImage = scaleImage
        self.fixedSize = fixedSize
        self.setWidth = setWidth
        self.setHeight = setHeight
        super().setPixmap(self.pixmap)
        super().setMinimumSize(1, 1)
        self.overlays = {}

        self.message_port_register_in(pmt.intern("filename"))
        self.set_msg_handler(pmt.intern("filename"), self.msgHandler)

        self.message_port_register_in(pmt.intern("overlay"))
        self.set_msg_handler(pmt.intern("overlay"), self.overlayHandler)

    def overlayHandler(self, msg):
        try:
            overlayitem = pmt.to_python(pmt.car(msg))
            if overlayitem is None:
                gr.log.error('Overlay message contains None in the car portion '
                'of the message.  Please pass in a dictionary or list of dictionaries in this '
                'portion of the message.  Each dictionary should have the following keys: '
                'filename,x,y.  Use x=y=-1 to remove an overlay item.')
                return

            if type(overlayitem) is dict:
                itemlist = []
                itemlist.append(overlayitem)
            elif type(overlayitem) is list:
                itemlist = overlayitem
            else:
                gr.log.error("Overlay message type is not correct.  Please pass in "
                      "a dictionary or list of dictionaries in this portion of the message.  Each "
                      "dictionary should have the following keys: filename,x,y.  Use x=y=-1 to "
                      "remove an overlay item.")
                return

            # Check each dict item to make sure it's valid.
            for curitem in itemlist:
                if type(curitem) == dict:
                    if 'filename' not in curitem:
                        gr.log.error("Dictionary item did not contain the 'filename' key.")
                        gr.log.error("Received " + str(curitem))
                        continue

                    if 'x' not in curitem:
                        gr.log.error("The dictionary for filename " +
                              curitem['filename'] + " did not contain an 'x' key.")
                        gr.log.error("Received " + str(curitem))
                        continue

                    if 'y' not in curitem:
                        gr.log.error("The dictionary for filename " +
                              curitem['filename'] + " did not contain an 'y' key.")
                        gr.log.error("Received " + str(curitem))
                        continue

                    if not os.path.isfile(curitem['filename']):
                        gr.log.error("Unable to find overlay file " +
                              curitem['filename'])
                        gr.log.error("Received " + str(curitem))
                        continue

                    # Now either add/update our list or remove the item.
                    if curitem['x'] == -1 and curitem['y'] == -1:
                        try:
                            del self.overlays[curitem['filename']]  # remove item
                        except:
                            pass
                    else:
                        self.overlays[curitem['filename']] = curitem

                self.updateGraphic()
        except Exception as e:
            gr.log.error("Error with overlay message conversion: %s" % str(e))

    def updateGraphic(self):
        if (len(self.overlays.keys()) == 0):
            try:
                super().setPixmap(self.pixmap)
            except Exception as e:
                gr.log.error("Error updating graphic: %s" % str(e))
                return
        else:
            # Need to deal with overlays
            tmpPxmap = self.pixmap.copy(self.pixmap.rect())
            painter = QPainter(tmpPxmap)
            for curkey in self.overlays.keys():
                curOverlay = self.overlays[curkey]
                try:
                    newOverlay = QPixmap(curkey)
                    if 'scalefactor' in curOverlay:
                        scale = curOverlay['scalefactor']
                        w = newOverlay.width()
                        h = newOverlay.height()
                        newOverlay = newOverlay.scaled(int(w*scale), int(h*scale),
                                                       Qtc.KeepAspectRatio)
                    painter.drawPixmap(curOverlay['x'], curOverlay['y'], newOverlay)
                except Exception as e:
                    gr.log.error("Error adding overlay: %s" % str(e))
                    return

            painter.end()

            super().setPixmap(tmpPxmap)

    def msgHandler(self, msg):
        try:
            new_val = pmt.to_python(pmt.cdr(msg))
            image_file = new_val
            if type(new_val) == str:
                if not os.path.isfile(image_file):
                    gr.log.error("ERROR: Unable to find file " + image_file)
                    return

                try:
                    self.pixmap = QPixmap(image_file)
                    self.image_file = image_file
                except OSError as e:
                    gr.log.error("ERROR: " + e.strerror)
                    return

                self.updateGraphic()
            else:
                gr.log.error("Value received was not an int or "
                      "a bool: %s" % str(type(new_val)))

        except Exception as e:
            gr.log.error("Error with message conversion: %s" % str(e))

    def minimumSizeHint(self):
        return QSize(self.pixmap.width(),self.pixmap.height())

    def resizeEvent(self, event):
        if self.scaleImage:
            w = super().width()
            h = super().height()

            self.pixmap = self.originalPixmap.scaled(w, h, Qtc.KeepAspectRatio)
        elif self.fixedSize and self.setWidth > 0 and self.setHeight > 0:
            self.pixmap = self.originalPixmap.scaled(self.setWidth, self.setHeight,
                                                     Qtc.KeepAspectRatio)

        self.updateGraphic()