mirror of
https://github.com/gnuradio/gnuradio-companion.git
synced 2025-12-10 00:42:30 -06:00
272 lines
9.2 KiB
Python
272 lines
9.2 KiB
Python
# 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 = '''
|
|
<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\"
|
|
\"http://www.w3.org/TR/REC-html40/strict.dtd\">
|
|
<html>
|
|
<head>
|
|
<meta name=\"qrichtext\" content=\"1\" />
|
|
<style type=\"text/css\">
|
|
p, li {
|
|
white-space: pre-wrap;
|
|
}
|
|
|
|
/* Not currently used because entries are wrapped with <pre> */
|
|
body {
|
|
/* font-family: \'Ubuntu\'; */
|
|
font-family: \'MS Shell Dlg 2\';
|
|
font-size: 10pt;
|
|
font-weight: 400;
|
|
font-style: normal;
|
|
font-color: black;
|
|
margin: 0px;
|
|
text-indent: 0px;
|
|
-qt-block-indent: 0;
|
|
}
|
|
|
|
pre {
|
|
display: block;
|
|
font-family: monospace;
|
|
white-space: pre;
|
|
font-color: black;
|
|
margin: 1em 0;
|
|
}
|
|
|
|
</style>
|
|
</head>
|
|
</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("<font color=\"Green\"><b>", level, "</b></font>")
|
|
elif levelname == "WARNING":
|
|
return output.format("<font color=\"Orange\"><b>", level, "</b></font>")
|
|
elif levelname == "ERROR":
|
|
return output.format("<font color=\"Red\"><b>", level, "</b></font>")
|
|
elif levelname == "CRITICAL":
|
|
return output.format("<font color=\"Red\"><b>", level, "</b></font>")
|
|
else:
|
|
return output.format("<font color=\"Blue\"><b>", level, "</b></font>")
|
|
|
|
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 "<tr><td width=\"25\">{0}</td><td><pre>{1}</pre></td></tr>"
|
|
return "<tr><td width=\"75\">{0}</td><td><pre>{1}</pre></td></tr>"
|
|
return "<tr><td><pre>{0}</pre></td></tr>"
|