# 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 abc import logging import weakref # Third-party modules from qtpy import QtWidgets # Logging log = logging.getLogger(f"grc.application.{__name__}") class Component(object): ''' Abstract base class for all grc components. ''' __metaclass__ = abc.ABCMeta def __init__(self): ''' Initializes the component's base class. Sets up the references to the QApplication and the GNU Radio platform. Calls createActions() to initialize the component's actions. ''' log.debug("Initializing {}".format(self.__class__.__name__)) # Application reference - Use weak references to avoid issues with circular references # Platform and settings properties are accessed through this reference self._app = weakref.ref(QtWidgets.QApplication.instance()) # 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) ''' log.debug("Connecting signals") self.connectSlots() # Properties @property def app(self): return self._app() @property def settings(self): return self._app().settings @property def platform(self): return self._app().platform # Required methods @abc.abstractmethod def createActions(self, actions): ''' Add actions to the component. ''' raise NotImplementedError() @abc.abstractmethod def createMenus(self, actions, menus): ''' Add menus to the component. ''' raise NotImplementedError() @abc.abstractmethod def createToolbars(self, actions, toolbars): ''' Add toolbars to the component. ''' raise NotImplementedError() # Base methods def connectSlots(self, useToggled=True, toggledHandler='_toggled', triggeredHandler="_triggered"): ''' Handles connecting signals from given actions to handlers self - Calling class actions - Dictionary of a QAction and unique key Dynamically build the connections for the signals by finding the correct function to handle an action. Default behavior is to connect checkable actions to the 'toggled' signal and normal actions to the 'triggered' signal. If 'toggled' is not avaliable or useToggled is set to False then try to connect it to triggered. Both toggled and triggered are called for checkable items, so there is no need for both to be connected. void QAction::toggled ( bool checked ) [signal] void QAction::triggered ( bool checked = false ) [signal] - Checked is set for checkable actions Essentially the same as QMetaObject::connectSlotsByName, except the actions and slots can be separated into a view and controller class ''' actions = self.actions for key in actions: if useToggled and actions[key].isCheckable(): # Try to use toggled rather than triggered try: handler = key + toggledHandler actions[key].toggled.connect(getattr(self, handler)) log.debug("<{0}.toggled> connected to handler <{1}>".format(key, handler)) # Successful connection. Jump to the next action. continue except: # Default to the triggered handler log.warning("Could not connect <{0}.toggled> to handler <{1}>".format(key, handler)) # Try and bind the 'triggered' signal to a handler. try: handler = key + triggeredHandler actions[key].triggered.connect(getattr(self, handler)) log.debug("<{0}.triggered> connected to handler <{1}>".format(key, handler)) except: try: log.warning("Handler not implemented for <{0}.triggered> in {1}".format( key, type(self).__name__)) actions[key].triggered.connect(getattr(self, 'notImplemented')) except: # This should never happen log.error("Class cannot handle <{0}.triggered>".format(key)) def notImplemented(self): log.warning('Not implemented')