# Copyright 2014-2020 Free Software Foundation, Inc. # This file is part of GNU Radio # # GNU Radio Companion is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # GNU Radio Companion is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA from __future__ import absolute_import, print_function # Standard modules import logging import os import sys import subprocess import cProfile import pstats from typing import Union from qtpy import QtCore, QtGui, QtWidgets from qtpy.QtCore import Qt # Custom modules from .flowgraph_view import FlowgraphView from .canvas.flowgraph import FlowgraphScene from .example_browser import ExampleBrowser, ExampleBrowserDialog, Worker from .executor import ExecFlowGraphThread from .. import base, Constants, Utils from .variable_editor import VariableEditorAction from .undoable_actions import ( RotateAction, EnableAction, DisableAction, BypassAction, MoveAction, NewElementAction, DeleteElementAction, BlockPropsChangeAction, BussifyAction ) from .preferences import PreferencesDialog from .oot_browser import OOTBrowser from .dialogs import ErrorsDialog from ...core.base import Element # Logging log = logging.getLogger(f"grc.application.{__name__}") # Shortcuts Action = QtWidgets.QAction Menu = QtWidgets.QMenu Toolbar = QtWidgets.QToolBar Icons = QtGui.QIcon.fromTheme Keys = QtGui.QKeySequence QStyle = QtWidgets.QStyle class MainWindow(QtWidgets.QMainWindow, base.Component): def __init__(self, file_path=[]): QtWidgets.QMainWindow.__init__(self) # base.Component.__init__(self) log.debug("Setting the main window") self.setObjectName("main") self.setWindowTitle(_("window-title")) self.setDockOptions( QtWidgets.QMainWindow.AllowNestedDocks | QtWidgets.QMainWindow.AllowTabbedDocks | QtWidgets.QMainWindow.AnimatedDocks ) self.progress_bar = QtWidgets.QProgressBar() self.statusBar().addPermanentWidget(self.progress_bar) self.progress_bar.hide() # Setup the window icon icon = QtGui.QIcon(self.settings.path.ICON) log.debug("Setting window icon - ({0})".format(self.settings.path.ICON)) self.setWindowIcon(icon) monitor = self.screen().availableGeometry() log.debug( "Setting window size - ({}, {})".format(monitor.width(), monitor.height()) ) self.resize(int(monitor.width() * 0.50), monitor.height()) self.setCorner(Qt.BottomLeftCorner, Qt.LeftDockWidgetArea) # Get max length of recently opened files list to be displayed in the menu self.max_recent_files = self.app.qsettings.value('appearance/max_recent_files', 10, type=int) recent_files = list(self.app.qsettings.value('window/files_recent', [])) for file in recent_files: if not os.path.exists(file): recent_files.remove(file) self.recent_files = recent_files[:self.max_recent_files] self.menuBar().setNativeMenuBar(self.settings.window.NATIVE_MENUBAR) # TODO: Not sure about document mode # self.setDocumentMode(True) # Generate the rest of the window self.createStatusBar() self.profiler = cProfile.Profile() # actions['Quit.triggered.connect(self.close) # actions['Report.triggered.connect(self.reportDock.show) # QtCore.QMetaObject.connectSlotsByName(self) # Translation support # self.setWindowTitle(_translate("blockLibraryDock", "Library", None)) # library.headerItem().setText(0, _translate("blockLibraryDock", "Blocks", None)) # QtCore.QMetaObject.connectSlotsByName(blockLibraryDock) # TODO: Move to the base controller and set actions as class attributes # Automatically create the actions, menus and toolbars. # Child controllers need to call the register functions to integrate into the mainwindow self.actions = {} self.menus = {} self.toolbars = {} self.createActions(self.actions) self.createMenus(self.actions, self.menus) self.createToolbars(self.actions, self.toolbars) self.connectSlots() # Rest of the GUI widgets # Map some of the view's functions to the controller class self.registerDockWidget = self.addDockWidget self.registerMenu = self.addMenu self.registerToolBar = self.addToolBar # Do other initialization stuff. View should already be allocated and # actions dynamically connected to class functions. Also, the log # functionality should be also allocated log.debug("__init__") # Add the menus from the view menus = self.menus self.registerMenu(menus["file"]) self.registerMenu(menus["edit"]) self.registerMenu(menus["view"]) self.registerMenu(menus["build"]) self.registerMenu(menus["tools"]) self.registerMenu(menus["help"]) toolbars = self.toolbars self.registerToolBar(toolbars["file"]) self.registerToolBar(toolbars["edit"]) self.registerToolBar(toolbars["run"]) self.registerToolBar(toolbars["misc"]) self.tabWidget = QtWidgets.QTabWidget() self.tabWidget.setTabsClosable(True) self.tabWidget.tabCloseRequested.connect( lambda index: self.close_triggered(index) ) self.tabWidget.currentChanged.connect( lambda index: self.tab_triggered(index) ) self.setCentralWidget(self.tabWidget) self.clipboard = None self.undoView = None files_open = list(self.app.qsettings.value('window/files_open', [])) + file_path grc_file_found = False if files_open: for file in files_open: if os.path.isfile(file): self.open_triggered(file) grc_file_found = True if not grc_file_found: self.new_triggered() try: self.restoreGeometry(self.app.qsettings.value("window/geometry")) self.restoreState(self.app.qsettings.value("window/windowState")) except TypeError: log.warning("Could not restore window geometry and state.") self.examples_found = False self.ExampleBrowser = ExampleBrowser() self.ExampleBrowser.file_to_open.connect(self.open_example) self.OOTBrowser = OOTBrowser() self.threadpool = QtCore.QThreadPool() self.threadpool.setMaxThreadCount(1) ExampleFinder = Worker(self.ExampleBrowser.find_examples) ExampleFinder.signals.result.connect(self.populate_libraries_w_examples) ExampleFinder.signals.progress.connect(self.update_progress_bar) self.threadpool.start(ExampleFinder) """def show(self): log.debug("Showing main window") self.show() """ @QtCore.Slot(VariableEditorAction) def handle_editor_action(self, key): # Calculate the position to insert a new block # Perhaps we should add a random component, as we may add several blocks pos = (self.currentFlowgraphScene.sceneRect().width() / 2, self.currentFlowgraphScene.sceneRect().height() / 2) if key == VariableEditorAction.DELETE_BLOCK: self.delete_triggered() elif key == VariableEditorAction.DISABLE_BLOCK: self.disable_triggered() elif key == VariableEditorAction.ENABLE_BLOCK: self.enable_triggered() elif key == VariableEditorAction.ADD_VARIABLE: self.currentFlowgraphScene.add_block('variable', pos) elif key == VariableEditorAction.ADD_IMPORT: self.currentFlowgraphScene.add_block('import', pos) elif key == VariableEditorAction.OPEN_PROPERTIES: self.currentFlowgraphScene.selected_blocks()[0].open_properties() else: log.debug(f"{key} not implemented yet") self.currentFlowgraphScene.clearSelection() @property def currentView(self): return self.tabWidget.currentWidget() @property def currentFlowgraphScene(self): return self.tabWidget.currentWidget().scene() @property def currentFlowgraph(self): return self.tabWidget.currentWidget().scene().core @QtCore.Slot(QtCore.QPointF) def registerMove(self, diff): self.currentFlowgraphScene.set_saved(False) self.tabWidget.tabBar().setTabTextColor(self.tabWidget.currentIndex(), Qt.red) action = MoveAction(self.currentFlowgraphScene, diff) self.currentFlowgraphScene.undoStack.push(action) self.updateActions() @QtCore.Slot(Element) def registerNewElement(self, elem): self.currentFlowgraphScene.set_saved(False) self.tabWidget.tabBar().setTabTextColor(self.tabWidget.currentIndex(), Qt.red) action = NewElementAction(self.currentFlowgraphScene, elem) self.currentFlowgraphScene.undoStack.push(action) self.updateActions() self.currentFlowgraphScene.update() @QtCore.Slot(Element) def registerDeleteElement(self, elem): self.currentFlowgraphScene.set_saved(False) self.tabWidget.tabBar().setTabTextColor(self.tabWidget.currentIndex(), Qt.red) action = DeleteElementAction(self.currentFlowgraphScene, elem) self.currentFlowgraphScene.undoStack.push(action) self.updateActions() self.currentFlowgraphScene.update() @QtCore.Slot(Element) def registerBlockPropsChange(self, elem): self.currentFlowgraphScene.set_saved(False) self.tabWidget.tabBar().setTabTextColor(self.tabWidget.currentIndex(), Qt.red) action = BlockPropsChangeAction(self.currentFlowgraphScene, elem) self.currentFlowgraphScene.undoStack.push(action) self.updateActions() self.currentFlowgraphScene.update() def createActions(self, actions): """ Defines all actions for this view. Controller manages connecting signals to slots implemented in the controller """ log.debug("Creating actions") # File Actions actions["new"] = Action( Icons("document-new"), _("new"), self, shortcut=Keys.New, statusTip=_("new-tooltip"), ) actions["open"] = Action( Icons("document-open"), _("open"), self, shortcut=Keys.Open, statusTip=_("open-tooltip"), ) actions["open_recent"] = Action( Icons("document-open"), _("open_recent"), self, statusTip=_("open-recent-file"), ) actions["open_recent"].setText("Open recent file") actions["example_browser"] = Action( _("example_browser"), self, statusTip=_("example_browser-tooltip"), ) actions["close"] = Action( Icons("window-close"), _("close"), self, shortcut=Keys.Close, statusTip=_("close-tooltip"), ) actions["close_all"] = Action( Icons("window-close"), _("close_all"), self, statusTip=_("close_all-tooltip"), ) actions["save"] = Action( Icons("document-save"), _("save"), self, shortcut=Keys.Save, statusTip=_("save-tooltip"), ) actions["save_as"] = Action( Icons("document-save-as"), _("save_as"), self, shortcut=Keys.SaveAs, statusTip=_("save_as-tooltip"), ) actions["save_copy"] = Action(_("save_copy"), self) actions["screen_capture"] = Action( Icons("camera-photo"), _("screen_capture"), self, shortcut=Keys.Print, statusTip=_("screen_capture-tooltip"), ) actions["exit"] = Action( Icons("application-exit"), _("exit"), self, shortcut=Keys.Quit, statusTip=_("exit-tooltip"), ) # Edit Actions actions["undo"] = Action( Icons("edit-undo"), _("undo"), self, shortcut=Keys.Undo, statusTip=_("undo-tooltip"), ) actions["redo"] = Action( Icons("edit-redo"), _("redo"), self, shortcut=Keys.Redo, statusTip=_("redo-tooltip"), ) actions["view_undo_stack"] = Action("View undo stack", self) actions["cut"] = Action( Icons("edit-cut"), _("cut"), self, shortcut=Keys.Cut, statusTip=_("cut-tooltip"), ) actions["copy"] = Action( Icons("edit-copy"), _("copy"), self, shortcut=Keys.Copy, statusTip=_("copy-tooltip"), ) actions["paste"] = Action( Icons("edit-paste"), _("paste"), self, shortcut=Keys.Paste, statusTip=_("paste-tooltip"), ) actions["delete"] = Action( Icons("edit-delete"), _("delete"), self, shortcut=Keys.Delete, statusTip=_("delete-tooltip"), ) actions["undo"].setEnabled(False) actions["redo"].setEnabled(False) actions["cut"].setEnabled(False) actions["copy"].setEnabled(False) actions["paste"].setEnabled(False) actions["delete"].setEnabled(False) actions["select_all"] = Action( Icons("edit-select_all"), _("select_all"), self, shortcut=Keys.SelectAll, statusTip=_("select_all-tooltip"), ) actions["select_none"] = Action( _("Select None"), self, statusTip=_("select_none-tooltip") ) actions["rotate_ccw"] = Action( Icons("object-rotate-left"), _("rotate_ccw"), self, shortcut=Keys.MoveToPreviousChar, statusTip=_("rotate_ccw-tooltip"), ) actions["rotate_cw"] = Action( Icons("object-rotate-right"), _("rotate_cw"), self, shortcut=Keys.MoveToNextChar, statusTip=_("rotate_cw-tooltip"), ) actions["rotate_cw"].setEnabled(False) actions["rotate_ccw"].setEnabled(False) actions["enable"] = Action(_("enable"), self, shortcut="E") actions["disable"] = Action(_("disable"), self, shortcut="D") actions["bypass"] = Action(_("bypass"), self, shortcut="B") # TODO actions["block_inc_type"] = Action(_("block_inc_type"), self) actions["block_dec_type"] = Action(_("block_dec_type"), self) actions["enable"].setEnabled(False) actions["disable"].setEnabled(False) actions["bypass"].setEnabled(False) # TODO actions["vertical_align_top"] = Action(_("vertical_align_top"), self) actions["vertical_align_middle"] = Action(_("vertical_align_middle"), self) actions["vertical_align_bottom"] = Action(_("vertical_align_bottom"), self) actions["vertical_align_top"].setEnabled(False) actions["vertical_align_middle"].setEnabled(False) actions["vertical_align_bottom"].setEnabled(False) actions["horizontal_align_left"] = Action(_("horizontal_align_left"), self) actions["horizontal_align_center"] = Action(_("horizontal_align_center"), self) actions["horizontal_align_right"] = Action(_("horizontal_align_right"), self) actions["horizontal_align_left"].setEnabled(False) actions["horizontal_align_center"].setEnabled(False) actions["horizontal_align_right"].setEnabled(False) actions["create_hier"] = Action(_("create_hier_block"), self) actions["open_hier"] = Action(_("open_hier_block"), self) actions["toggle_source_bus"] = Action(_("toggle_source_bus"), self) actions["toggle_sink_bus"] = Action(_("toggle_sink_bus"), self) actions["create_hier"].setEnabled(True) actions["open_hier"].setEnabled(True) actions["toggle_source_bus"].setEnabled(False) actions["toggle_sink_bus"].setEnabled(False) actions["properties"] = Action( Icons("document-properties"), _("flowgraph-properties"), self, statusTip=_("flowgraph-properties-tooltip"), ) actions["properties"].setEnabled(False) # View Actions actions["zoom_in"] = Action( Icons("zoom-in"), _("Zoom &in"), self ) actions["zoom_in"].setShortcuts([Keys.ZoomIn, "Ctrl+="]) actions["zoom_out"] = Action( Icons("zoom-out"), _("Zoom &out"), self, shortcut=Keys.ZoomOut, ) actions["zoom_original"] = Action( Icons("zoom-original"), _("O&riginal size"), self, shortcut="Ctrl+0", ) actions["toggle_grid"] = Action( _("toggle_grid"), self, shortcut="G", statusTip=_("toggle_grid-tooltip") ) actions["errors"] = Action( Icons("dialog-error"), _("errors"), self, statusTip=_("errors-tooltip") ) actions["find"] = Action( Icons("edit-find"), _("find"), self, shortcut=Keys.Find, statusTip=_("find-tooltip"), ) # Help Actions actions["about"] = Action( Icons("help-about"), _("about"), self, statusTip=_("about-tooltip") ) actions["about_qt"] = Action( self.style().standardIcon(QStyle.SP_TitleBarMenuButton), _("about-qt"), self, statusTip=_("about-tooltip"), ) actions["generate"] = Action( Icons("system-run"), _("process-generate"), self, shortcut="F5", statusTip=_("process-generate-tooltip"), ) actions["execute"] = Action( Icons("media-playback-start"), _("process-execute"), self, shortcut="F6", statusTip=_("process-execute-tooltip"), ) actions["kill"] = Action( Icons("process-stop"), _("process-kill"), self, shortcut="F7", statusTip=_("process-kill-tooltip"), ) actions["help"] = Action( Icons("help-browser"), _("help"), self, shortcut=Keys.HelpContents, statusTip=_("help-tooltip"), ) # Tools Actions actions["filter_design_tool"] = Action(_("&Filter Design Tool"), self) actions["module_browser"] = Action(_("&OOT Module Browser"), self) actions["start_profiler"] = Action(_("Start profiler"), self) actions["stop_profiler"] = Action(_("Stop profiler"), self) # Help Actions actions["types"] = Action(_("&Types"), self) actions["keys"] = Action(_("&Keys"), self) actions["get_involved"] = Action(_("&Get Involved"), self) actions["preferences"] = Action( Icons("preferences-system"), _("preferences"), self, statusTip=_("preferences-tooltip"), ) actions["reload"] = Action( Icons("view-refresh"), _("reload"), self, statusTip=_("reload-tooltip") ) # Disable some actions, by default actions["save"].setEnabled(True) actions["errors"].setEnabled(False) def updateDocTab(self): pass """ doc_txt = self._app().DocumentationTab._text blocks = self.currentFlowgraphScene.selected_blocks() if len(blocks) == 1: #print(blocks[0].documentation) doc_string = blocks[0].documentation[''] doc_txt.setText(doc_string) """ def updateActions(self): """Update the available actions based on what is selected""" blocks = self.currentFlowgraphScene.selected_blocks() conns = self.currentFlowgraphScene.selected_connections() undoStack = self.currentFlowgraphScene.undoStack canUndo = undoStack.canUndo() canRedo = undoStack.canRedo() self.currentFlowgraph.validate() valid_fg = self.currentFlowgraph.is_valid() saved_fg = self.currentFlowgraphScene.saved self.actions["save"].setEnabled(not saved_fg) self.actions["undo"].setEnabled(canUndo) self.actions["redo"].setEnabled(canRedo) self.actions["generate"].setEnabled(valid_fg) self.actions["execute"].setEnabled(valid_fg) self.actions["errors"].setEnabled(not valid_fg) self.actions["kill"].setEnabled(self.currentView.process_is_done()) self.actions["cut"].setEnabled(False) self.actions["copy"].setEnabled(False) self.actions["paste"].setEnabled(False) self.actions["delete"].setEnabled(False) self.actions["rotate_cw"].setEnabled(False) self.actions["rotate_ccw"].setEnabled(False) self.actions["enable"].setEnabled(False) self.actions["disable"].setEnabled(False) self.actions["bypass"].setEnabled(False) self.actions["properties"].setEnabled(False) self.actions["toggle_source_bus"].setEnabled(False) self.actions["toggle_sink_bus"].setEnabled(False) if self.clipboard: self.actions["paste"].setEnabled(True) if len(conns) > 0: self.actions["delete"].setEnabled(True) if len(blocks) > 0: self.actions["cut"].setEnabled(True) self.actions["copy"].setEnabled(True) self.actions["delete"].setEnabled(True) self.actions["rotate_cw"].setEnabled(True) self.actions["rotate_ccw"].setEnabled(True) self.actions["enable"].setEnabled(True) self.actions["disable"].setEnabled(True) self.actions["bypass"].setEnabled(True) self.actions["toggle_source_bus"].setEnabled(True) self.actions["toggle_sink_bus"].setEnabled(True) self.actions["vertical_align_top"].setEnabled(False) self.actions["vertical_align_middle"].setEnabled(False) self.actions["vertical_align_bottom"].setEnabled(False) self.actions["horizontal_align_left"].setEnabled(False) self.actions["horizontal_align_center"].setEnabled(False) self.actions["horizontal_align_right"].setEnabled(False) if len(blocks) == 1: self.actions["properties"].setEnabled(True) if len(blocks) > 1: self.actions["vertical_align_top"].setEnabled(True) self.actions["vertical_align_middle"].setEnabled(True) self.actions["vertical_align_bottom"].setEnabled(True) self.actions["horizontal_align_left"].setEnabled(True) self.actions["horizontal_align_center"].setEnabled(True) self.actions["horizontal_align_right"].setEnabled(True) for block in blocks: if not block.core.can_bypass(): self.actions["bypass"].setEnabled(False) break def createMenus(self, actions, menus): """Setup the main menubar for the application""" log.debug("Creating menus") # Global menu options self.menuBar().setNativeMenuBar(True) open_recent = Menu("Open recent file") menus["open_recent"] = open_recent act_recent_len = len(self.recent_files) if act_recent_len > self.max_recent_files: act_recent_len = self.max_recent_files # Populate recent file list if act_recent_len == 0: for i in range(self.max_recent_files): # Setup invisible dummy entries, that can be filled later, if recently opened files are available action = open_recent.addAction("Dummy", self.open_recent_triggered) action.setVisible(False) else: for i in range(act_recent_len): open_recent.addAction(self.recent_files[i], self.open_recent_triggered) if act_recent_len < self.max_recent_files: for i in range(self.max_recent_files - act_recent_len): action = open_recent.addAction("Dummy", self.open_recent_triggered) action.setVisible(False) # Setup the file menu file = Menu("&File") file.addAction(actions["new"]) file.addAction(actions["open"]) file.addMenu(open_recent) file.addAction(actions["example_browser"]) file.addAction(actions["close"]) file.addAction(actions["close_all"]) file.addSeparator() file.addAction(actions["save"]) file.addAction(actions["save_as"]) file.addAction(actions["save_copy"]) file.addSeparator() file.addAction(actions["screen_capture"]) file.addSeparator() file.addAction(actions["preferences"]) file.addSeparator() file.addAction(actions["exit"]) menus["file"] = file # Setup the edit menu edit = Menu("&Edit") edit.addAction(actions["undo"]) edit.addAction(actions["redo"]) edit.addAction(actions["view_undo_stack"]) edit.addSeparator() edit.addAction(actions["cut"]) edit.addAction(actions["copy"]) edit.addAction(actions["paste"]) edit.addAction(actions["delete"]) edit.addAction(actions["select_all"]) edit.addAction(actions["select_none"]) edit.addSeparator() edit.addAction(actions["rotate_ccw"]) edit.addAction(actions["rotate_cw"]) align = Menu("&Align") menus["align"] = align align.addAction(actions["vertical_align_top"]) align.addAction(actions["vertical_align_middle"]) align.addAction(actions["vertical_align_bottom"]) align.addSeparator() align.addAction(actions["horizontal_align_left"]) align.addAction(actions["horizontal_align_center"]) align.addAction(actions["horizontal_align_right"]) edit.addMenu(align) edit.addSeparator() edit.addAction(actions["enable"]) edit.addAction(actions["disable"]) edit.addAction(actions["bypass"]) edit.addSeparator() more = Menu("&More") menus["more"] = more more.addAction(actions["create_hier"]) more.addAction(actions["open_hier"]) more.addAction(actions["toggle_source_bus"]) more.addAction(actions["toggle_sink_bus"]) edit.addMenu(more) edit.addAction(actions["properties"]) menus["edit"] = edit # Setup submenu panels = Menu("&Panels") menus["panels"] = panels panels.setEnabled(False) toolbars = Menu("&Toolbars") menus["toolbars"] = toolbars toolbars.setEnabled(False) # Setup the view menu view = Menu("&View") view.addMenu(panels) view.addMenu(toolbars) view.addSeparator() view.addAction(actions["zoom_in"]) view.addAction(actions["zoom_out"]) view.addAction(actions["zoom_original"]) view.addSeparator() view.addAction(actions["toggle_grid"]) view.addAction(actions["find"]) menus["view"] = view # Setup the build menu build = Menu("&Build") build.addAction(actions["errors"]) build.addAction(actions["generate"]) build.addAction(actions["execute"]) build.addAction(actions["kill"]) menus["build"] = build # Setup the tools menu tools = Menu("&Tools") tools.addAction(actions["filter_design_tool"]) tools.addAction(actions["module_browser"]) tools.addSeparator() tools.addAction(actions["start_profiler"]) tools.addAction(actions["stop_profiler"]) menus["tools"] = tools # Setup the help menu help = Menu("&Help") help.addAction(actions["help"]) help.addAction(actions["types"]) help.addAction(actions["keys"]) help.addSeparator() help.addAction(actions["get_involved"]) help.addAction(actions["about"]) help.addAction(actions["about_qt"]) menus["help"] = help def createToolbars(self, actions, toolbars): log.debug("Creating toolbars") # Main toolbar file = Toolbar("File") file.setObjectName("_FileTb") file.addAction(actions["new"]) file.addAction(actions["open"]) file.addAction(actions["save"]) file.addAction(actions["close"]) toolbars["file"] = file # Edit toolbar edit = Toolbar("Edit") edit.setObjectName("_EditTb") edit.addAction(actions["undo"]) edit.addAction(actions["redo"]) edit.addSeparator() edit.addAction(actions["cut"]) edit.addAction(actions["copy"]) edit.addAction(actions["paste"]) edit.addAction(actions["delete"]) toolbars["edit"] = edit # Run Toolbar run = Toolbar("Run") run.setObjectName("_RunTb") run.addAction(actions["errors"]) run.addAction(actions["generate"]) run.addAction(actions["execute"]) run.addAction(actions["kill"]) toolbars["run"] = run # Misc Toolbar misc = Toolbar("Misc") misc.setObjectName("_MiscTb") misc.addAction(actions["reload"]) toolbars["misc"] = misc def createStatusBar(self): log.debug("Creating status bar") self.statusBar().showMessage(_("ready-message")) def open(self): if self.currentFlowgraphScene.filename: dirname = os.path.dirname(self.currentFlowgraphScene.filename) else: dirname = os.getcwd() Open = QtWidgets.QFileDialog.getOpenFileName filename, filtr = Open( self, self.actions["open"].statusTip(), dir=dirname, filter="Flow Graph Files (*.grc);;All files (*.*)", ) return filename def save(self): Save = QtWidgets.QFileDialog.getSaveFileName filename, filtr = Save( self, self.actions["save"].statusTip(), filter="Flow Graph Files (*.grc);;All files (*.*)", ) return filename # Overridden methods def addDockWidget(self, location, widget): """Adds a dock widget to the view.""" # This overrides QT's addDockWidget so that a 'show' menu auto can automatically be # generated for this action. super().addDockWidget(location, widget) # This is the only instance where a controller holds a reference to a view it does not # actually control. name = widget.__class__.__name__ log.debug("Generating show action item for widget: {0}".format(name)) # Create the new action and wire it to the show/hide for the widget self.menus["panels"].addAction(widget.toggleViewAction()) self.menus["panels"].setEnabled(True) def addToolBar(self, toolbar): """Adds a toolbar to the main window""" # This is also overridden so a show menu item can automatically be added super().addToolBar(toolbar) name = toolbar.windowTitle() log.debug("Generating show action item for toolbar: {0}".format(name)) # Create the new action and wire it to the show/hide for the widget self.menus["toolbars"].addAction(toolbar.toggleViewAction()) self.menus["toolbars"].setEnabled(True) def addMenu(self, menu): """Adds a menu to the main window""" help = self.menus["help"].menuAction() self.menuBar().insertMenu(help, menu) def populate_libraries_w_examples(self, example_tuple): examples, examples_w_block, designated_examples_w_block = example_tuple self.ExampleBrowser.populate(examples) self.app.BlockLibrary.populate_w_examples(examples_w_block, designated_examples_w_block) self.progress_bar.reset() self.progress_bar.hide() self.examples_found = True @QtCore.Slot(tuple) def update_progress_bar(self, progress_tuple): progress, msg = progress_tuple self.progress_bar.show() self.progress_bar.setValue(progress) self.progress_bar.setTextVisible(True) self.progress_bar.setFormat(msg) def connect_fg_signals(self, scene: FlowgraphScene): scene.selectionChanged.connect(self.updateActions) scene.selectionChanged.connect(self.updateDocTab) scene.itemMoved.connect(self.registerMove) scene.newElement.connect(self.registerNewElement) scene.deleteElement.connect(self.registerDeleteElement) scene.blockPropsChange.connect(self.registerBlockPropsChange) # Action Handlers def new_triggered(self): log.debug("New") fg_view = FlowgraphView(self, self.platform) fg_view.centerOn(0, 0) initial_state = self.platform.parse_flow_graph("") fg_view.scene().import_data(initial_state) fg_view.scene().saved = False self.connect_fg_signals(fg_view.scene()) log.debug("Adding flowgraph view") self.tabWidget.addTab(fg_view, "Untitled") self.tabWidget.setCurrentIndex(self.tabWidget.count() - 1) self.tabWidget.tabBar().setTabTextColor(self.tabWidget.currentIndex(), Qt.red) def open_triggered(self, filename=None, save_allowed=True): log.debug("open") if not filename: filename = self.open() if filename: open_fgs = self.get_open_flowgraphs() if filename not in open_fgs: self.add_recent_file(filename) log.info("Opening flowgraph ({0})".format(filename)) new_flowgraph = FlowgraphView(self, self.platform) initial_state = self.platform.parse_flow_graph(filename) self.tabWidget.addTab(new_flowgraph, os.path.basename(filename)) self.tabWidget.setCurrentIndex(self.tabWidget.count() - 1) self.currentFlowgraphScene.import_data(initial_state) self.currentFlowgraphScene.filename = filename self.connect_fg_signals(self.currentFlowgraphScene) self.currentFlowgraphScene.saved = True self.currentFlowgraphScene.save_allowed = save_allowed self.currentFlowgraphScene.core.rewrite() self.currentFlowgraphScene.core.validate() for block in self.currentFlowgraphScene.core.blocks: block.gui.create_shapes_and_labels() self.currentFlowgraphScene.update_elements_to_draw() if hasattr(self.app, 'VariableEditor'): self.app.VariableEditor.set_scene(self.currentFlowgraphScene) self.updateActions() else: self.tabWidget.setCurrentIndex(open_fgs.index(filename)) def open_example(self, example_path): log.debug("open example") if example_path: self.open_triggered(example_path, False) def save_triggered(self): if not self.currentFlowgraphScene.save_allowed: self.save_as_triggered() return log.debug("save") filename = self.currentFlowgraphScene.filename if filename: try: self.platform.save_flow_graph(filename, self.currentFlowgraph) self.currentFlowgraph.grc_file_path = filename self.add_recent_file(filename) except IOError: log.error("Save failed") return log.info(f"Saved {filename}") self.tabWidget.tabBar().setTabTextColor(self.tabWidget.currentIndex(), self.palette().color(self.palette().WindowText)) self.currentFlowgraphScene.set_saved(True) else: log.debug("Flowgraph does not have a filename") self.save_as_triggered() self.updateActions() def save_as_triggered(self): log.debug("Save As") file_dialog = QtWidgets.QFileDialog() file_dialog.setWindowTitle(self.actions["save"].statusTip()) file_dialog.setNameFilter('Flow Graph Files (*.grc)') file_dialog.setDefaultSuffix('grc') file_dialog.setAcceptMode(QtWidgets.QFileDialog.AcceptSave) if self.currentFlowgraphScene.filename: dirname = os.path.dirname(self.currentFlowgraphScene.filename) else: dirname = os.getcwd() file_dialog.setDirectory(dirname) filename = None if file_dialog.exec_() == QtWidgets.QFileDialog.Accepted: filename = file_dialog.selectedFiles()[0] if filename: self.currentFlowgraphScene.filename = filename try: self.platform.save_flow_graph(filename, self.currentFlowgraph) except IOError: log.error("Save (as) failed") return log.info(f"Saved (as) {filename}") self.tabWidget.tabBar().setTabTextColor(self.tabWidget.currentIndex(), self.palette().color(self.palette().WindowText)) self.currentFlowgraphScene.set_saved(True) self.tabWidget.setTabText(self.tabWidget.currentIndex(), os.path.basename(filename)) self.add_recent_file(filename) else: log.debug("Cancelled Save As action") self.updateActions() def save_copy_triggered(self): log.debug("Save Copy") file_dialog = QtWidgets.QFileDialog() file_dialog.setWindowTitle(self.actions["save"].statusTip()) file_dialog.setNameFilter('Flow Graph Files (*.grc)') file_dialog.setDefaultSuffix('grc') file_dialog.setAcceptMode(QtWidgets.QFileDialog.AcceptSave) filename = None if file_dialog.exec_() == QtWidgets.QFileDialog.Accepted: filename = file_dialog.selectedFiles()[0] if filename: try: self.platform.save_flow_graph(filename, self.currentFlowgraph) except IOError: log.error("Save (copy) failed") log.info(f"Saved (copy) {filename}") else: log.debug("Cancelled Save Copy action") def tab_triggered(self, tab_index=None): """ Switches to a tab. Parameters: tab_index: switches to tab(tab_index) """ log.debug(f"Switching to tab (index {tab_index})") if tab_index < 0: return self.tabWidget.setCurrentIndex(tab_index) if hasattr(self.app, 'VariableEditor'): self.app.VariableEditor.set_scene(self.currentFlowgraphScene) self.updateActions() def close_triggered(self, tab_index=None) -> Union[str, bool]: """ Closes a tab. Parameters: tab_index: specifies which tab to close. If none, close the open tab Returns: the file path OR True if a tab was closed (False otherwise) """ log.debug(f"Closing a tab (index {tab_index})") file_path = self.currentFlowgraphScene.filename if tab_index is None: tab_index = self.tabWidget.currentIndex() if self.currentFlowgraphScene.saved: self.tabWidget.removeTab(tab_index) else: message = None if file_path: message = f"Save changes to {os.path.basename(file_path)} before closing? Your changes will be lost otherwise." else: message = "This flowgraph has not been saved" # TODO: Revise text response = QtWidgets.QMessageBox.question( None, "Save flowgraph?", message, QtWidgets.QMessageBox.Discard | QtWidgets.QMessageBox.Cancel | QtWidgets.QMessageBox.Save, ) if response == QtWidgets.QMessageBox.Discard: file_path = self.currentFlowgraphScene.filename self.tabWidget.removeTab(tab_index) elif response == QtWidgets.QMessageBox.Save: self.save_triggered() if self.currentFlowgraphScene.saved: file_path = self.currentFlowgraphScene.filename self.tabWidget.removeTab(tab_index) else: return False else: # Cancel return False if self.tabWidget.count() == 0: # No tabs left self.new_triggered() return True else: return file_path def close_all_triggered(self): log.debug("close") while self.tabWidget.count() > 1: self.close_triggered() # Close the final tab self.close_triggered() def print_triggered(self): log.debug("print") def screen_capture_triggered(self): log.debug("screen capture") # TODO: Should be user-set somehow background_transparent = True if self.currentFlowgraphScene.filename: filename = self.currentFlowgraphScene.filename.split('.')[0] + '.pdf' else: filename = 'Untitled.pdf' Save = QtWidgets.QFileDialog.getSaveFileName file_path, filtr = Save( self, self.actions["screen_capture"].statusTip(), filename, filter="PDF files (*.pdf);;PNG files (*.png);;SVG files (*.svg)", ) if file_path is not None: try: Utils.make_screenshot( self.currentView, file_path, background_transparent ) except ValueError: log.error("Failed to generate screenshot") def undo_triggered(self): log.debug("undo") self.currentFlowgraphScene.undoStack.undo() self.currentFlowgraphScene.update() self.updateActions() def redo_triggered(self): log.debug("redo") self.currentFlowgraphScene.undoStack.redo() self.updateActions() def view_undo_stack_triggered(self): log.debug("view_undo_stack") self.undoView = QtWidgets.QUndoView(self.currentFlowgraphScene.undoStack) self.undoView.setWindowTitle("Undo stack") self.undoView.show() def cut_triggered(self): log.debug("cut") self.copy_triggered() self.delete_triggered() self.updateActions() def copy_triggered(self): log.debug("copy") self.clipboard = self.currentFlowgraphScene.copy_to_clipboard() self.updateActions() def paste_triggered(self): log.debug("paste") if self.clipboard: self.currentFlowgraphScene.paste_from_clipboard(self.clipboard) self.currentFlowgraphScene.update() else: log.debug("clipboard is empty") def delete_triggered(self): log.debug("delete") self.currentFlowgraphScene.set_saved(False) self.tabWidget.tabBar().setTabTextColor(self.tabWidget.currentIndex(), Qt.red) action = DeleteElementAction(self.currentFlowgraphScene) self.currentFlowgraphScene.undoStack.push(action) self.updateActions() self.currentFlowgraphScene.update() def select_all_triggered(self): log.debug("select_all") self.currentFlowgraphScene.select_all() self.updateActions() def select_none_triggered(self): log.debug("select_none") self.currentFlowgraphScene.clearSelection() self.updateActions() def rotate_ccw_triggered(self): # Pass to Undo/Redo log.debug("rotate_ccw") self.currentFlowgraphScene.set_saved(False) self.tabWidget.tabBar().setTabTextColor(self.tabWidget.currentIndex(), Qt.red) rotateCommand = RotateAction(self.currentFlowgraphScene, -90) self.currentFlowgraphScene.undoStack.push(rotateCommand) self.updateActions() self.currentFlowgraphScene.update() def rotate_cw_triggered(self): # Pass to Undo/Redo log.debug("rotate_cw") self.currentFlowgraphScene.set_saved(False) self.tabWidget.tabBar().setTabTextColor(self.tabWidget.currentIndex(), Qt.red) rotateCommand = RotateAction(self.currentFlowgraphScene, 90) self.currentFlowgraphScene.undoStack.push(rotateCommand) self.updateActions() self.currentFlowgraphScene.update() def toggle_source_bus_triggered(self): log.debug("toggle_source_bus") self.currentFlowgraphScene.set_saved(False) self.tabWidget.tabBar().setTabTextColor(self.tabWidget.currentIndex(), Qt.red) bussifyCommand = BussifyAction(self.currentFlowgraphScene, 'source') self.currentFlowgraphScene.undoStack.push(bussifyCommand) self.updateActions() self.currentFlowgraphScene.update() def toggle_sink_bus_triggered(self): log.debug("toggle_sink_bus") self.currentFlowgraphScene.set_saved(False) self.tabWidget.tabBar().setTabTextColor(self.tabWidget.currentIndex(), Qt.red) bussifyCommand = BussifyAction(self.currentFlowgraphScene, 'sink') self.currentFlowgraphScene.undoStack.push(bussifyCommand) self.updateActions() self.currentFlowgraphScene.update() def errors_triggered(self): log.debug("errors") err = ErrorsDialog(self.currentFlowgraph) err.exec() def module_browser_triggered(self): log.debug("oot browser") self.OOTBrowser.show() def zoom_in_triggered(self): log.debug("zoom in") self.currentView.zoom(1.1) def zoom_out_triggered(self): log.debug("zoom out") self.currentView.zoom(1.0 / 1.1) def zoom_original_triggered(self): log.debug("zoom to original size") self.currentView.zoomOriginal() def find_triggered(self): log.debug("find block") self._app().BlockLibrary._search_bar.clear() self._app().BlockLibrary._search_bar.setFocus() self._app().BlockLibrary.reset() def get_involved_triggered(self): log.debug("get involved") ad = QtWidgets.QMessageBox() ad.setWindowTitle("Get Involved Instructions") ad.setText( """\ Welcome to the GNU Radio Community!

For more details on contributing to GNU Radio and getting engaged with our great community visit here.

You can also join our Matrix chat server, IRC Channel (#gnuradio) or contact through our mailing list (discuss-gnuradio). """ ) ad.exec() def about_triggered(self): log.debug("about") config = self.platform.config py_version = sys.version.split()[0] QtWidgets.QMessageBox.about( self, "About GNU Radio", f"GNU Radio {config.version} (Python {py_version})" ) def about_qt_triggered(self): log.debug("about_qt") QtWidgets.QApplication.instance().aboutQt() def properties_triggered(self): log.debug("properties") if len(self.currentFlowgraphScene.selected_blocks()) != 1: log.warn("Opening Properties even though selected_blocks() != 1 ") self.currentFlowgraphScene.selected_blocks()[0].open_properties() def enable_triggered(self): log.debug("enable") self.currentFlowgraphScene.set_saved(False) all_enabled = True for block in self.currentFlowgraphScene.selected_blocks(): if not block.core.state == "enabled": all_enabled = False break if not all_enabled: cmd = EnableAction(self.currentFlowgraphScene) self.currentFlowgraphScene.undoStack.push(cmd) self.currentFlowgraphScene.update() self.updateActions() def disable_triggered(self): log.debug("disable") self.currentFlowgraphScene.set_saved(False) all_disabled = True for g_block in self.currentFlowgraphScene.selected_blocks(): if not g_block.core.state == "disabled": all_disabled = False break if not all_disabled: cmd = DisableAction(self.currentFlowgraphScene) self.currentFlowgraphScene.undoStack.push(cmd) self.currentFlowgraphScene.update() self.updateActions() def bypass_triggered(self): log.debug("bypass") all_bypassed = True for g_block in self.currentFlowgraphScene.selected_blocks(): if not g_block.core.state == "bypassed": all_bypassed = False break if not all_bypassed: cmd = BypassAction(self.currentFlowgraphScene) self.currentFlowgraphScene.undoStack.push(cmd) self.currentFlowgraphScene.update() self.updateActions() def block_inc_type_triggered(self): log.debug("block_inc_type") def block_dec_type_triggered(self): log.debug("block_dec_type") def generate_triggered(self): log.debug("generate") if not self.currentFlowgraphScene.saved: self.save_triggered() if not self.currentFlowgraphScene.saved: # The line above was cancelled log.error("Cannot generate a flowgraph without saving first") return filename = self.currentFlowgraphScene.filename self.currentFlowgraph.grc_file_path = filename generator = self.platform.Generator( self.currentFlowgraph, os.path.dirname(filename) ) generator.write() self.currentView.generator = generator log.info(f"Generated {generator.file_path}") def execute_triggered(self): log.debug("execute") if self.currentView.process_is_done(): self.generate_triggered() if self.currentView.generator: xterm = self.app.qsettings.value("grc/xterm_executable", "") '''if self.config.xterm_missing() != xterm: if not os.path.exists(xterm): Dialogs.show_missing_xterm(main, xterm) self.config.xterm_missing(xterm)''' if self.currentFlowgraphScene.saved and self.currentFlowgraphScene.filename: # Save config before execution # self.config.save() ExecFlowGraphThread( view=self.currentView, flowgraph=self.currentFlowgraph, xterm_executable=xterm, callback=self.updateActions ) def kill_triggered(self): log.debug("kill") def reload_triggered(self): log.debug("Reload hier blocks") self.app.BlockLibrary.reload_blocks() range_ = self.tabWidget.count() for idx in range(range_): self.rebuild_tab(idx) self.updateActions() def create_hier_triggered(self): selected_blocks = [] source_fg = self.currentFlowgraphScene for block in source_fg.selected_blocks(): selected_blocks.append(block.core) # Generate new page self.new_triggered() sink_fg = self.currentFlowgraphScene # Set flow graph to heir block type top_block = sink_fg.core.get_block(Constants.DEFAULT_FLOW_GRAPH_ID) # Check if hb or hb_qt is required gen_opts = 'hb' for block in selected_blocks: if block.label.upper().startswith('QT GUI'): gen_opts = 'hb_qt_gui' break top_block.params['generate_options'].set_value(gen_opts) # this needs to be a unique name top_block.params['id'].set_value('new_hier') # Remove default samp_rate remove_me = sink_fg.core.get_block("samp_rate") sink_fg.remove_element(remove_me.gui) self.clipboard = source_fg.copy_to_clipboard() self.paste_triggered() # For each variable generate a possible parameter block # The user has to decide if the hier block should use a variable or a paramter for variable in sink_fg.core.get_variables(): id = sink_fg.add_block( 'parameter', (variable.states['coordinate'][0] + variable.gui.width + 50, variable.states['coordinate'][1] + 50)) param_block = sink_fg.core.get_block(id) param_block.params['id'].set_value(variable.name) for connection in source_fg.core.connections: # Get id of connected blocks source = connection.source_block sink = connection.sink_block if source not in selected_blocks and sink in selected_blocks: # Create pad source and add to canvas pad_source_key = int(connection.sink_port.key) pad_id = sink_fg.add_block( 'pad_source', (source.states['coordinate'][0], source.states['coordinate'][1] + pad_source_key * 50)) pad_block = sink_fg.core.get_block(pad_id) pad_source = pad_block.sources[0] sink_block = sink_fg.core.get_block(sink_fg.core.blocks[selected_blocks.index(sink) + 1].name) sink = sink_block.sinks[pad_source_key] # ensure the port types match if pad_source.dtype != sink.dtype: if pad_source.dtype == 'complex' and sink.dtype == 'fc32': pass else: pad_block.params['type'].value = sink.dtype pad_source.dtype = sink.dtype new_connection = sink_fg.core.connect(pad_source, sink) sink_fg.addItem(new_connection.gui) elif sink not in selected_blocks and source in selected_blocks: # Create Pad Sink and add to canvas pad_sink_key = int(connection.source_port.key) pad_id = sink_fg.add_block( 'pad_sink', (sink.states['coordinate'][0], sink.states['coordinate'][1] + pad_sink_key * 50)) pad_block = sink_fg.core.get_block(pad_id) pad_sink = pad_block.sinks[0] source_block = sink_fg.core.get_block(sink_fg.core.blocks[selected_blocks.index(source) + 1].name) source = source_block.sources[pad_sink_key] # ensure the port types match if pad_sink.dtype != source.dtype: if pad_sink.dtype == 'complex' and source.dtype == 'fc32': pass else: pad_block.params['type'].value = source.dtype pad_sink.dtype = source.dtype new_connection = sink_fg.core.connect(source, pad_sink) sink_fg.addItem(new_connection.gui) sink_fg.clearSelection() sink_fg.update() self.updateActions() def open_hier_triggered(self): log.debug("Open hier block triggered") for block in self.currentFlowgraphScene.selected_blocks(): grc_source = block.core.extra_data.get('grc_source', '') if grc_source: self.open_triggered(grc_source) def rebuild_tab(self, idx): fgscene = self.tabWidget.widget(idx).scene() log.info("Rebuilding flowgraph ({0})".format(fgscene.filename)) data = fgscene.core.export_data() fgblocks = fgscene.core.blocks for block in fgblocks: fgscene.removeItem(block.gui) for conn in fgscene.core.connections: fgscene.removeItem(conn.gui) fgscene.import_data(data) def add_recent_file(self, file_name): # double check file_name if os.path.exists(file_name): recent_files = self.recent_files if file_name in recent_files: recent_files.remove(file_name) # Attempt removal recent_files.insert(0, file_name) # Insert at start self.recent_files = recent_files[:self.max_recent_files] # Now add files to menu entries actions_list = self.menus["open_recent"].actions() for i in range(len(self.recent_files)): actions_list[i].setText(self.recent_files[i]) actions_list[i].setVisible(True) def show_help(parent): """Display basic usage tips.""" message = """\ Usage Tips \n\ Add block: drag and drop or double click a block in the block selection window. Rotate block: Select a block, press left/right on the keyboard. Change type: Select a block, press up/down on the keyboard. Edit parameters: double click on a block in the flow graph. Make connection: click on the source port of one block, then click on the sink port of another block. Remove connection: select the connection and press delete, or drag the connection. \n\ *Press Ctrl+K or see menu for Keyboard - Shortcuts \ """ ad = QtWidgets.QMessageBox() ad.setWindowTitle("Help") ad.setText(message) ad.exec() def types_triggered(self): log.debug("types") colors = [(name, color) for name, key, sizeof, color in Constants.CORE_TYPES] message = """ """ message += "\n".join( '' "".format(color=color, name=name) for name, color in colors ) message += "
{name}
" ad = QtWidgets.QMessageBox() ad.setWindowTitle("Stream Types") ad.setText(message) ad.exec() def keys_triggered(self): log.debug("keys") message = """\ Keyboard Shortcuts

Ctrl+N: Create a new flowgraph.
Ctrl+O: Open an existing flowgraph.
Ctrl+S: Save the current flowgraph or save as for new.
Ctrl+W: Close the current flowgraph.
Ctrl+Z: Undo a change to the flowgraph.
Ctrl+Y: Redo a change to the flowgraph.
Ctrl+A: Selects all blocks and connections.
Ctrl+P: Screen Capture of the Flowgraph.
Ctrl+Shift+P: Save the console output to file.
Ctrl+L: Clear the console.
Ctrl+E: Show variable editor.
Ctrl+F: Search for a block by name.
Ctrl+Q: Quit.
F1 : Help menu.
F5 : Generate the Flowgraph.
F6 : Execute the Flowgraph.
F7 : Kill the Flowgraph.
Ctrl+Shift+S: Save as the current flowgraph.
Ctrl+Shift+D: Create a duplicate of current flow graph.
Ctrl+X/C/V: Edit-cut/copy/paste.
Ctrl+D/B/R: Toggle visibility of disabled blocks or connections/block tree widget/console.
Shift+T/M/B/L/C/R: Vertical Align Top/Middle/Bottom and Horizontal Align Left/Center/Right respectively of the selected block.
Ctrl+0: Reset the zoom level
Ctrl++/-: Zoom in and out
\ """ ad = QtWidgets.QMessageBox() ad.setWindowTitle("Keyboard shortcuts") ad.setText(message) ad.exec() def preferences_triggered(self): log.debug("preferences") prefs_dialog = PreferencesDialog(self.app.qsettings) if prefs_dialog.exec_(): # User pressed Save prefs_dialog.save_all() self.currentFlowgraphScene.update() def open_recent_triggered(self): log.debug("open_recent") self.open_triggered(self.sender().text()) def example_browser_triggered(self, key_filter: Union[str, None] = None): log.debug("example-browser") if self.examples_found: self.ExampleBrowser.reset() ex_dialog = ExampleBrowserDialog(self.ExampleBrowser) if len(ex_dialog.browser.examples_dict) == 0: ad = QtWidgets.QMessageBox() ad.setWindowTitle("GRC: No examples found") ad.setText("GRC did not find any examples. Please ensure that the example path in grc.conf is correct.") ad.exec() return if isinstance(key_filter, str): if not ex_dialog.browser.filter_(key_filter): ad = QtWidgets.QMessageBox() ad.setWindowTitle("GRC: No examples") ad.setText("There are no examples for this block.") ad.exec() return else: ex_dialog.browser.reset() ex_dialog.exec_() else: ad = QtWidgets.QMessageBox() ad.setWindowTitle("GRC still indexing examples") ad.setText("GRC is still indexing examples, please try again shortly.") ad.exec() def exit_triggered(self): log.debug("exit") files_open = [] range_ = reversed(range(self.tabWidget.count())) for idx in range_: # Close the rightmost first. It'll be the first element in files_open tab = self.tabWidget.widget(idx) file_path = self.currentFlowgraphScene.filename if file_path: files_open.append(file_path) closed = self.close_triggered() if closed == False: # We cancelled closing a tab. We don't want to close the application return # Save the panel settings # Console self.app.qsettings.setValue('appearance/display_console', not self.app.Console.isHidden()) # Block Library self.app.qsettings.setValue('appearance/display_blocklibrary', not self.app.BlockLibrary.isHidden()) # Wiki self.app.qsettings.setValue('appearance/display_wiki', not self.app.WikiTab.isHidden()) # Variable Editor self.app.qsettings.setValue('appearance/display_variable_editor', not self.app.VariableEditor.isHidden()) # Write the leftmost tab to file first self.app.qsettings.setValue('window/files_open', reversed(files_open)) self.app.qsettings.setValue('window/windowState', self.saveState()) self.app.qsettings.setValue('window/geometry', self.saveGeometry()) self.app.qsettings.sync() # TODO: Make sure all flowgraphs have been saved self.app.exit() def closeEvent(self, evt): log.debug("Close Event") self.exit_triggered() evt.ignore() def help_triggered(self): log.debug("help") self.show_help() def report_triggered(self): log.debug("report") def library_triggered(self): log.debug("library_triggered") def library_toggled(self): log.debug("library_toggled") def filter_design_tool_triggered(self): log.debug("filter_design_tool") subprocess.Popen( "gr_filter_design", shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, ) def start_profiler_triggered(self): log.info("Starting profiler") self.profiler.enable() def stop_profiler_triggered(self): self.profiler.disable() log.info("Stopping profiler") stats = pstats.Stats(self.profiler) stats.dump_stats('stats.prof') def get_open_flowgraphs(self): index = self.tabWidget.count() if index == 0: return [] result = [] range_ = range(index) for fg_nr in range_: self.tabWidget.setCurrentIndex(fg_nr) result.append(self.currentFlowgraphScene.filename) return result