GRC Qt: Difference between revisions

From GNU Radio
Jump to navigation Jump to search
(GRC Qt: Resize images)
m (GRC Qt: New features)
 
(3 intermediate revisions by the same user not shown)
Line 19: Line 19:
=== Preferences ===
=== Preferences ===


Go to File->Preferences to open the preferences menu.
Go to File->Preferences to open the preferences menu. The settings are saved in .gnuradio/grc.conf. grc/gui_qt/resources/available_preferences.yml specifies which settings are available from GRC Qt.


[[File:grcqt_prefs.png|upright=1|thumb|GRC Qt preferences (December 2023)]]
[[File:grcqt_prefs.png|upright=1|thumb|GRC Qt preferences (December 2023)]]
Line 45: Line 45:
== Code tour ==
== Code tour ==


The following sections are meant to give the reader an understanding of what happens in GRC under the hood when a user interacts with a flowgraph. This will hopefully make it easier for new contributors to familiarise themselves with the GRC code.
The following sections are meant to give the reader an understanding of what happens in GRC under the hood when a user interacts with a flowgraph. This will hopefully make it easier for new contributors to familiarise themselves with the GRC code. Please note that the code snippets below may be outdated.


=== What happens when a block is moved? ===
=== What happens when a block is moved? ===


Once the user clicks and drags a block, a `mousePressEvent` is issued. The event is passed down from the FlowgraphView (QGraphicsView subclass), which is the widget that displays the Flowgraph (which is a QGraphicsScene subclass). The event passes through the Flowgraph, which records the position of the click:
Once the user clicks and drags a block, a ''mousePressEvent'' is issued. The event is passed down from the FlowgraphView (QGraphicsView subclass), which is the widget that displays the Flowgraph (which is a QGraphicsScene subclass). The event passes through the Flowgraph, which records the position of the click:


[https://github.com/gnuradio/gnuradio/blob/ad92582a7709e8b12ca65900b1bffe27b97293bd/grc/gui_qt/components/flowgraph.py#L270-L277 grc/gui_qt/components/flowgraph.py#L270-L277]
[https://github.com/gnuradio/gnuradio/blob/18dd2e04edb58f6e1ebeb14d3bdfef70499d261b/grc/gui_qt/components/flowgraph.py#L270-L277 grc/gui_qt/components/flowgraph.py#L270-L277]
<syntaxhighlight lang="python" line="line">
<syntaxhighlight lang="python" line="line">
     def mousePressEvent(self, event):
     def mousePressEvent(self, event):
Line 65: Line 65:
The click ends up in the block:
The click ends up in the block:


[https://github.com/gnuradio/gnuradio/blob/ad92582a7709e8b12ca65900b1bffe27b97293bd/grc/gui_qt/components/canvas/block.py#L359-L369 grc/gui_qt/components/canvas/block.py#L359-L369]
[https://github.com/gnuradio/gnuradio/blob/18dd2e04edb58f6e1ebeb14d3bdfef70499d261b/grc/gui_qt/components/canvas/block.py#L359-L369 grc/gui_qt/components/canvas/block.py#L359-L369]
<syntaxhighlight lang="python" line="line">
<syntaxhighlight lang="python" line="line">
     def mousePressEvent(self, e):
     def mousePressEvent(self, e):
         super(self.__class__, self).mousePressEvent(e)
         super(self.__class__, self).mousePressEvent(e)
         log.debug(f"{self} clicked")
         log.debug(f"{self} clicked")
         try:
         url_prefix = str(self.parent.app.platform.config.wiki_block_docs_url_prefix)
            prefix = str(self.parent.app.platform.Config.wiki_block_docs_url_prefix)
        self.parent.app.WikiTab.setURL(QUrl(url_prefix + self.label.replace(" ", "_")))
            self.parent.app.WikiTab.setURL(QUrl(prefix + self.label.replace(" ", "_")))
        except KeyError:
            pass


         self.moveToTop()
         self.moveToTop()
Line 80: Line 77:




A `mouseMoveEvent` is also issued, but the QGraphicsView framework's default handler does everything we need, so we don't need to modify it. The block (which is a QGraphicsItem) is being redrawn continuouslish as the block is dragged, using `block.paint()` (quite long):
A ''mouseMoveEvent'' is also issued, but the QGraphicsView framework's default handler does everything we need, so we don't need to modify it. The block (which is a QGraphicsItem) is being redrawn continuouslish as the block is dragged, using ''block.paint()'' (quite long):


[https://github.com/gnuradio/gnuradio/blob/ad92582a7709e8b12ca65900b1bffe27b97293bd/grc/gui_qt/components/canvas/block.py#L228 grc/gui_qt/components/canvas/block.py#L228]
[https://github.com/gnuradio/gnuradio/blob/18dd2e04edb58f6e1ebeb14d3bdfef70499d261b/grc/gui_qt/components/canvas/block.py#L228 grc/gui_qt/components/canvas/block.py#L228]
<syntaxhighlight lang="python" line="line">
<syntaxhighlight lang="python" line="line">
     def paint(self, painter, option, widget):
     def paint(self, painter, option, widget):
Line 88: Line 85:
</syntaxhighlight>
</syntaxhighlight>


If the block passes through another block's `boundingRect`, the other block will also be redrawn.
If the block passes through another block's ''boundingRect'', the other block will also be redrawn.


[https://github.com/gnuradio/gnuradio/blob/77fc296e81780fcb970ea8534725ac1858f08c12/grc/gui_qt/components/canvas/block.py#L340-L343 grc/gui_qt/components/canvas/block.py#L340-L343]
[https://github.com/gnuradio/gnuradio/blob/18dd2e04edb58f6e1ebeb14d3bdfef70499d261b/grc/gui_qt/components/canvas/block.py#L340-L343 grc/gui_qt/components/canvas/block.py#L340-L343]
<syntaxhighlight lang="python" line="line">
<syntaxhighlight lang="python" line="line">
     def boundingRect(self):  # required to have
     def boundingRect(self):  # required to have
Line 98: Line 95:
</syntaxhighlight>
</syntaxhighlight>


The movement also causes the block's `itemChange` slot to be triggered, which repositions the block if the *Snap to grid* setting has been enabled:
The movement also causes the block's ''itemChange'' slot to be triggered, which repositions the block if the '''Snap to grid''' setting has been enabled:


[https://github.com/gnuradio/gnuradio/blob/ad92582a7709e8b12ca65900b1bffe27b97293bd/grc/gui_qt/components/canvas/block.py#L384-L391 grc/gui_qt/components/canvas/block.py#L384-L391]
[https://github.com/gnuradio/gnuradio/blob/18dd2e04edb58f6e1ebeb14d3bdfef70499d261b/grc/gui_qt/components/canvas/block.py#L384-L391 grc/gui_qt/components/canvas/block.py#L384-L391]
<syntaxhighlight lang="python" line="line">
<syntaxhighlight lang="python" line="line">
     def itemChange(self, change, value):
     def itemChange(self, change, value):
Line 112: Line 109:
</syntaxhighlight>
</syntaxhighlight>


Some blocks have ports, and they move with their parent block. This means that they will also notice the position change, and their `itemChange` slots make sure that the port's connections move accordingly:
Some blocks have ports, and they move with their parent block. This means that they will also notice the position change, and their ''itemChange'' slots make sure that the port's connections move accordingly:


[https://github.com/gnuradio/gnuradio/blob/ad92582a7709e8b12ca65900b1bffe27b97293bd/grc/gui_qt/components/canvas/port.py#L60-L67 grc/gui_qt/components/canvas/port.py#L60-L67]
[https://github.com/gnuradio/gnuradio/blob/18dd2e04edb58f6e1ebeb14d3bdfef70499d261b/grc/gui_qt/components/canvas/port.py#L60-L67 grc/gui_qt/components/canvas/port.py#L60-L67]
<syntaxhighlight lang="python" line="line">
<syntaxhighlight lang="python" line="line">
     def itemChange(self, change, value):
     def itemChange(self, change, value):
Line 126: Line 123:
</syntaxhighlight>
</syntaxhighlight>


Finally the user releases the mouse and drops the block in its new position. This leads to a `mouseReleaseEvent` in the Flowgraph, which emits an `itemMoved` signal.
Finally the user releases the mouse and drops the block in its new position. This leads to a ''mouseReleaseEvent'' in the Flowgraph, which emits an ''itemMoved'' signal.


[https://github.com/gnuradio/gnuradio/blob/ad92582a7709e8b12ca65900b1bffe27b97293bd/grc/gui_qt/components/flowgraph.py#L343-L345 grc/gui_qt/components/flowgraph.py#L343-L345]
[https://github.com/gnuradio/gnuradio/blob/18dd2e04edb58f6e1ebeb14d3bdfef70499d261b/grc/gui_qt/components/flowgraph.py#L343-L345 grc/gui_qt/components/flowgraph.py#L343-L345]
<syntaxhighlight lang="python" line="line">
<syntaxhighlight lang="python" line="line">
             if self.clickPos != event.scenePos():
             if self.clickPos != event.scenePos():
Line 135: Line 132:
</syntaxhighlight>
</syntaxhighlight>


The `itemMoved` signal is caught by the QMainWindow, where it has been connected to the `registerMove` slot. Note that the block is already in its new position, the `registerMove` just registers the move as a MoveAction and places it in the undo stack.
The ''itemMoved'' signal is caught by the QMainWindow, where it has been connected to the ''registerMove'' slot. Note that the block is already in its new position, the ''registerMove'' just registers the move as a MoveAction and places it in the undo stack.


[https://github.com/gnuradio/gnuradio/blob/ad92582a7709e8b12ca65900b1bffe27b97293bd/grc/gui_qt/components/window.py#L210-L215 grc/gui_qt/components/window.py#L210-L215]
[https://github.com/gnuradio/gnuradio/blob/18dd2e04edb58f6e1ebeb14d3bdfef70499d261b/grc/gui_qt/components/window.py#L210-L215 grc/gui_qt/components/window.py#L210-L215]
<syntaxhighlight lang="python" line="line">
<syntaxhighlight lang="python" line="line">
     @QtCore.Slot(QtCore.QPointF)
     @QtCore.Slot(QtCore.QPointF)
Line 146: Line 143:
         self.updateActions()
         self.updateActions()
</syntaxhighlight>
</syntaxhighlight>


=== What happens when a block's properties is changed? (coming) ===
=== What happens when a block's properties is changed? (coming) ===

Latest revision as of 17:25, 15 January 2024

GRC Qt (November 2023)

GRC Qt is a work-in-progress port of GRC from Gtk to Qt. GRC Qt aims to provide better cross-platform support, both across different OSes, but also across different versions of Qt/PyQt (PyQt and PySide, version 5 and 6). GRC Qt can be tried out at https://github.com/gnuradio/gnuradio/tree/feature-grc-qt.

New features

Example integration

Go to File->Examples to open the example browser. You can also open a block's properties dialog (double-click) and click the Examples tab to see which examples contain the block.

GRC Examples browser (December 2023)

OOT browser

You can find the OOT browser at Tools->OOT Module Browser. It lists the installed OOTs and displays information about it from MANIFEST.md.

GRC OOT browser (December 2023)

Preferences

Go to File->Preferences to open the preferences menu. The settings are saved in .gnuradio/grc.conf. grc/gui_qt/resources/available_preferences.yml specifies which settings are available from GRC Qt.

GRC Qt preferences (December 2023)

Undo stack

GRC Qt uses Qt's Undo framework, which comes with a built-in Undo stack viewer. You can find it at Edit->Undo stack.

Log file

GRC Qt writes logs (levels INFO and higher) to ~/.gnuradio/grc.log by default. Run GRC with --log debug to enable DEBUG level log messages too.

GUI testing

The file grc/tests/test_qtbot.py uses Pytest-qt and PyAutoGUI to test various GUI features. Warning: The script will use your mouse and keyboard for ~30 seconds!

Automatic block wiki loading

When clicking a block (either in the flowgraph canvas or in the block library to the left), the wiki tab to the right will automatically load the page from wiki.gnuradio.org corresponding to the block.

Code structure

WIP

Code tour

The following sections are meant to give the reader an understanding of what happens in GRC under the hood when a user interacts with a flowgraph. This will hopefully make it easier for new contributors to familiarise themselves with the GRC code. Please note that the code snippets below may be outdated.

What happens when a block is moved?

Once the user clicks and drags a block, a mousePressEvent is issued. The event is passed down from the FlowgraphView (QGraphicsView subclass), which is the widget that displays the Flowgraph (which is a QGraphicsScene subclass). The event passes through the Flowgraph, which records the position of the click:

grc/gui_qt/components/flowgraph.py#L270-L277

    def mousePressEvent(self, event):
        item = self.itemAt(event.scenePos(), QtGui.QTransform())
        selected = self.selectedItems()
        self.moving_blocks = False
        if item:
            if item.is_block:
                self.moving_blocks = True
        self.clickPos = event.scenePos()

The click ends up in the block:

grc/gui_qt/components/canvas/block.py#L359-L369

    def mousePressEvent(self, e):
        super(self.__class__, self).mousePressEvent(e)
        log.debug(f"{self} clicked")
        url_prefix = str(self.parent.app.platform.config.wiki_block_docs_url_prefix)
        self.parent.app.WikiTab.setURL(QUrl(url_prefix + self.label.replace(" ", "_")))

        self.moveToTop()


A mouseMoveEvent is also issued, but the QGraphicsView framework's default handler does everything we need, so we don't need to modify it. The block (which is a QGraphicsItem) is being redrawn continuouslish as the block is dragged, using block.paint() (quite long):

grc/gui_qt/components/canvas/block.py#L228

    def paint(self, painter, option, widget):
        (...)

If the block passes through another block's boundingRect, the other block will also be redrawn.

grc/gui_qt/components/canvas/block.py#L340-L343

    def boundingRect(self):  # required to have
        return QtCore.QRectF( # TODO: Calculate comment box size
            -2.5, -2.5, self.width + 5, self.height + (5 if not self.comment else 50)
        )  # margin to avoid artifacts

The movement also causes the block's itemChange slot to be triggered, which repositions the block if the Snap to grid setting has been enabled:

grc/gui_qt/components/canvas/block.py#L384-L391

    def itemChange(self, change, value):
        if change == QtWidgets.QGraphicsItem.ItemPositionChange and self.scene() and self.snap_to_grid:
            grid_size = 10
            value.setX(round(value.x()/grid_size)*grid_size)
            value.setY(round(value.y()/grid_size)*grid_size)
            return value
        else:
            return QtWidgets.QGraphicsItem.itemChange(self, change, value)

Some blocks have ports, and they move with their parent block. This means that they will also notice the position change, and their itemChange slots make sure that the port's connections move accordingly:

grc/gui_qt/components/canvas/port.py#L60-L67

    def itemChange(self, change, value):
        if self._dir == "sink":
            self.connection_point = self.scenePos() + QtCore.QPointF(0.0, self.height / 2.0)
        else:
            self.connection_point = self.scenePos() + QtCore.QPointF(self.width, self.height / 2.0)
        for conn in self.connections():
            conn.updateLine()
        return QtWidgets.QGraphicsLineItem.itemChange(self, change, value)

Finally the user releases the mouse and drops the block in its new position. This leads to a mouseReleaseEvent in the Flowgraph, which emits an itemMoved signal.

grc/gui_qt/components/flowgraph.py#L343-L345

            if self.clickPos != event.scenePos():
                if self.moving_blocks:
                    self.itemMoved.emit(event.scenePos() - self.clickPos)

The itemMoved signal is caught by the QMainWindow, where it has been connected to the registerMove slot. Note that the block is already in its new position, the registerMove just registers the move as a MoveAction and places it in the undo stack.

grc/gui_qt/components/window.py#L210-L215

    @QtCore.Slot(QtCore.QPointF)
    def createMove(self, diff):
        self.currentFlowgraph.set_saved(False)
        action = MoveAction(self.currentFlowgraph, diff)
        self.currentFlowgraph.undoStack.push(action)
        self.updateActions()

What happens when a block's properties is changed? (coming)