# 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 html import logging import textwrap # Third-party modules from qtpy import QtCore, QtGui, QtWidgets # Custom modules from .. import base # Shortcuts Action = QtWidgets.QAction Menu = QtWidgets.QMenu Toolbar = QtWidgets.QToolBar Icons = QtGui.QIcon.fromTheme Keys = QtGui.QKeySequence # Logging log = logging.getLogger(f"grc.application.{__name__}") HTML = ''' ''' class Console(QtWidgets.QDockWidget, base.Component): def __init__(self, level): super(Console, self).__init__() self.setObjectName('console') self.setWindowTitle('Console') self.level = level ### GUI Widgets # Create the layout widget container = QtWidgets.QWidget(self) container.setObjectName('console::container') self._container = container layout = QtWidgets.QHBoxLayout(container) layout.setObjectName('console::layout') layout.setSpacing(0) layout.setContentsMargins(5, 0, 5, 5) self._layout = layout # Console output widget text = QtWidgets.QTextEdit(container) text.setObjectName('console::text') text.setUndoRedoEnabled(False) text.setReadOnly(True) text.setCursorWidth(0) text.setTextInteractionFlags(QtCore.Qt.TextSelectableByKeyboard | QtCore.Qt.TextSelectableByMouse) text.setHtml(textwrap.dedent(HTML)) self._text = text # Add widgets to the component layout.addWidget(text) container.setLayout(layout) self.setWidget(container) ### Translation support #self.setWindowTitle(_translate("", "Library", None)) #library.headerItem().setText(0, _translate("", "Blocks", None)) #QtCore.QMetaObject.connectSlotsByName(blockLibraryDock) ### Setup actions # 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() # Register the dock widget through the AppController. # The AppController then tries to find a saved dock location from the preferences # before calling the MainWindow Controller to add the widget. self.app.registerDockWidget(self, location=self.settings.window.CONSOLE_DOCK_LOCATION) # Register the menus self.app.registerMenu(self.menus["console"]) # Register a new handler for the root logger that outputs messages of # INFO and HIGHER to the reports view handler = ReportsHandler(self.add_line) handler.setLevel(self.level) # Need to add this handler to the parent of the controller's logger log.parent.addHandler(handler) self.handler = handler self.actions['show_level'].setChecked = True self.handler.show_level = True self.enabled = False def enable(self): self.enabled = True ### Actions def createActions(self, actions): ''' Defines all actions for this view. ''' log.debug("Creating actions") # File Actions actions['save'] = Action(Icons("document-save"), _("save"), self, statusTip=_("save-tooltip")) actions['clear'] = Action(Icons("document-close"), _("clear"), self, statusTip=_("clear-tooltip")) actions['show_level'] = Action(_("show-level"), self, statusTip=_("show-level"), checkable=True, checked=True) actions['auto_scroll'] = Action(_("auto-scroll"), self, statusTip=_("auto-scroll"), checkable=True, checked=True) def createMenus(self, actions, menus): ''' Setup the view's menus ''' log.debug("Creating menus") console_menu = QtWidgets.QMenu("&Console") console_menu.setObjectName("console::menu") # Not needed, we have FileHandler logging in main.py #console_menu.addAction(actions["save"]) console_menu.addAction(actions["clear"]) console_menu.addAction(actions["show_level"]) console_menu.addAction(actions["auto_scroll"]) menus["console"] = console_menu def createToolbars(self, actions, toolbars): log.debug("Creating toolbars") def add_line(self, line): # TODO: Support multiple columns for the HTML. DO better with the spacing # and margins in the output if self.enabled: self._text.append(line) if self.actions["auto_scroll"].isChecked(): self._text.verticalScrollBar().setValue( self._text.verticalScrollBar().maximum()) # Handlers for the view actions def clear_triggered(self): self._text.clear() def save_triggered(self): log.warning("Save reports not implemented") def show_level_toggled(self, checked): self.handler.show_level = checked class ReportsHandler(logging.Handler): # Inherit from logging.Handler ''' Writes out logs to the reporst window ''' def __init__(self, add_line, show_level=True, short_level=True): # run the regular Handler __init__ logging.Handler.__init__(self) self.add_line = add_line # Function for adding a line to the view self.show_level = show_level # Dynamically show levels self.short_level = short_level # Default to true, changed by properties self.formatLevelLength = self.formatLevelShort if not short_level: self.formatLevelLength = self.formatLevelLong def emit(self, record): # Just handle all formatting here if self.show_level: level = self.formatLevel(record.levelname) message = html.escape(record.msg) output = self.formatOutput() self.add_line(output.format(level, message)) else: message = html.escape(record.msg) output = self.formatOutput() self.add_line(output.format(message)) def formatLevel(self, levelname): output = "{0}{1}{2}" level = self.formatLevelLength(levelname) if levelname == "INFO": return output.format("", level, "") elif levelname == "WARNING": return output.format("", level, "") elif levelname == "ERROR": return output.format("", level, "") elif levelname == "CRITICAL": return output.format("", level, "") else: return output.format("", level, "") def formatLevelShort(self, levelname): return f'[{levelname[0:1]}]' def formatLevelLong(self, levelname): output = "{0:<10}" if levelname in ["DEBUG", "INFO", "WARNING"]: return output.format(f'[{levelname.capitalize()}]') else: return output.format(f'[{levelname.upper()}]') def formatOutput(self): ''' Returns the correct output format based on internal settings ''' if self.show_level: if self.short_level: return "{0}
{1}
" return "{0}
{1}
" return "
{0}
"