# -*- coding: utf-8 -*-
#
# This file is part of the pyFDA project hosted at https://github.com/chipmuenk/pyfda
#
# Copyright © pyFDA Project Contributors
# Licensed under the terms of the MIT License
# (see file LICENSE in root directory for details)
"""
Mainwindow for the pyFDA app
"""
import sys, os
import logging
import logging.config
logger = logging.getLogger(__name__)
import pyfda.libs.pyfda_dirs as dirs # initial import constructs file paths
from pyfda.libs.pyfda_lib import to_html
from pyfda import pyfda_rc as rc
import pyfda.filterbroker as fb
from pyfda import qrc_resources # contains all icons
# edit pyfda.qrc, then
# create with pyrcc4 pyfda.qrc -o qrc_resources.py -py3
# or pyrcc5 pyfda.qrc -o qrc_resources.py
# and manually replace "from from PyQt4/5 import QtCore"
# by "from pyfda.libs.compat import QtCore" in qrc_resources.py
from pyfda.input_widgets import input_tab_widgets
from pyfda.plot_widgets import plot_tab_widgets
from pyfda.libs.compat import (Qt, QtCore, QMainWindow, QApplication, QSplitter,
QMessageBox, QPlainTextEdit, QMenu, pyqtSignal)
#========================= Setup the loggers ==================================
[docs]
class DynFileHandler(logging.FileHandler):
"""
subclass FileHandler with a customized handler for dynamic definition of
the logging filepath and -name
"""
def __init__(self, *args):
filename, mode, encoding = args
if filename == '':
filename = dirs.LOG_FILE # use name including data and time
if not os.path.isabs(filename): # path to logging file given in config_file?
dirs.LOG_DIR_FILE = os.path.join(dirs.LOG_DIR, filename) # no, use default dir
logging.FileHandler.__init__(self, dirs.LOG_DIR_FILE, mode, encoding)
# =============================================================================
# logging.addLevelName(logging.WARNING, ACol.YELLOW2 +
# logging.getLevelName(logging.WARNING) + ACol.CEND)
# logging.addLevelName(logging.ERROR, ACol.RED2 +
# logging.getLevelName(logging.ERROR) + ACol.CEND)
# logging.addLevelName(logging.CRITICAL, ACol.RED2 + ";" + ACol.CSELECTED +
# logging.getLevelName(logging.CRITICAL) + ACol.CEND)
# https://stackoverflow.com/questions/24469662/how-to-redirect-logger-output-into-pyqt-text-widget
# coloured logger: https://stackoverflow.com/questions/384076/how-can-i-color-python-logging-output
# =============================================================================
[docs]
class XStream(QtCore.QObject):
"""
subclass for log messages on logger window
Overrides stdout to print messages to textWidget
"""
_stdout = None
messageWritten = pyqtSignal(str) # pass str to slot
[docs]
def flush( self ):
pass
[docs]
def fileno( self ):
return -1
[docs]
def write(self, msg):
if not self.signalsBlocked():
msg = to_html(msg,frmt='log')
self.messageWritten.emit(msg)
[docs]
@staticmethod
def stdout():
if not XStream._stdout:
XStream._stdout = XStream()
sys.stdout = XStream._stdout
return XStream._stdout
[docs]
class QEditHandler(logging.Handler):
"""
subclass Handler to also log messages to textWidget on main display
Overrides stdout to print messages to textWidget (XStream)
"""
def __init__(self):
logging.Handler.__init__(self)
[docs]
def emit(self, record):
msg = self.format(record)
if msg:
XStream.stdout().write('%s'%msg)
# "register" custom class DynFileHandler as an attribute for the logging module
# to use it inside the logging config file and pass file name / path and mode
# as parameters:
logging.DynFileHandler = DynFileHandler
logging.QEditHandler = QEditHandler
logging.config.fileConfig(dirs.USER_LOG_CONF_DIR_FILE)#, disable_existing_loggers=True)
#==============================================================================
[docs]
class pyFDA(QMainWindow):
"""
Create the main window consisting of a tabbed widget for entering filter
specifications, poles / zeros etc. and another tabbed widget for plotting
various filter characteristics
QMainWindow is used here as it is a class that understands GUI elements like
toolbar, statusbar, central widget, docking areas etc.
"""
sig_rx = pyqtSignal(object) # incoming
# sig_tx = pyqtSignal(object) # outgoing
def __init__(self, parent=None):
super(QMainWindow,self).__init__()
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
# create clipboard instance that can be accessed from other modules
fb.clipboard = QApplication.clipboard()
self._construct_UI()
def _construct_UI(self):
"""
Construct the main GUI, consisting of:
- Tabbed input widgets (left side)
- Tabbed plot widgets (right side)
- Logger window (right side, below plot tab)
"""
# ============== UI Layout with H and V-Splitter =====================
# create tab widgets for input and plot widgets
inputTabWidgets = input_tab_widgets.InputTabWidgets(
self, objectName='input_tab_widgets_inst')
pltTabWidgets = plot_tab_widgets.PlotTabWidgets(
self, objectName='plot_tab_widgets_inst')
self.loggerWin = QPlainTextEdit(self) # logger window
self.loggerWin.setReadOnly(True)
# set custom right-button context menu policy
self.loggerWin.setContextMenuPolicy(Qt.CustomContextMenu)
self.loggerWin.customContextMenuRequested.connect(self.logger_win_context_menu)
# create context menu and define actions and shortcuts
self.popMenu = QMenu(self)
self.popMenu.addAction('Select &All', self.loggerWin.selectAll, "Ctrl+A")
self.popMenu.addAction('&Copy Selected', self.loggerWin.copy)
self.popMenu.addSeparator()
self.popMenu.addAction('Clear &Window', self.loggerWin.clear)
# =============================================================================
# add logger window underneath plot Tab Widgets
spltVPltLogger = QSplitter(QtCore.Qt.Vertical)
spltVPltLogger.addWidget(pltTabWidgets)
spltVPltLogger.addWidget(self.loggerWin)
# create horizontal splitter that contains all subwidget groups
spltHMain = QSplitter(QtCore.Qt.Horizontal)
spltHMain.addWidget(inputTabWidgets)
spltHMain.addWidget(spltVPltLogger)
spltHMain.setStretchFactor(1, 4) # relative initial sizes of subwidgets
spltHMain.setContentsMargins(*rc.params['wdg_margins'])
spltHMain.setFocus()
# make spltHMain occupy the main area of QMainWindow and make QMainWindow its parent !!!
self.setCentralWidget(spltHMain)
spltVPltLoggerH = spltVPltLogger.size().height()
spltVPltLogger.setSizes([int(spltVPltLoggerH*0.95), int(spltVPltLoggerH*0.05 - 8)])
self.setWindowTitle('pyFDA - Python Filter Design and Analysis')
#=============== Menubar =======================================
# aboutAction = QAction('&About', self)
# aboutAction.setShortcut('Ctrl+A')
# aboutAction.setStatusTip('Info about pyFDA')
#
# menubar = self.menuBar()
# fileMenu = menubar.addMenu('&About')
# fileMenu.addAction(aboutAction)
# self.statusMessage("Application is initialized.")
#----------------------------------------------------------------------
# GLOBAL SIGNALS & SLOTs
#----------------------------------------------------------------------
self.sig_rx.connect(self.process_sig_rx)
#----------------------------------------------------------------------
# SIGNALS & SLOTs
#----------------------------------------------------------------------
# Here, signals about spec and design changes from lower hierarchies
# are distributed. At the moment, only changes in the input widgets are
# routed to the plot widgets:
inputTabWidgets.sig_tx.connect(pltTabWidgets.sig_rx)
inputTabWidgets.sig_tx.connect(self.process_sig_rx)
pltTabWidgets.sig_tx.connect(inputTabWidgets.sig_rx)
# open pop-up "about" window
#aboutAction.triggered.connect(self.aboutWindow)
# trigger the close event in response to sigQuit generated in another subwidget:
# inputTabWidgets.input_filter_specs.sigQuit.connect(self.close)
# when a message has been written, pass it via signal-slot mechanism and
# print it to logger window
XStream.stdout().messageWritten.connect(self.loggerWin.appendHtml)
#------------------------------------------------------------------------------
[docs]
def process_sig_rx(self, dict_sig=None):
"""
Process signals coming from sig_rx:
- trigger close event in response to 'close_event' emitted in another subwidget:
"""
logger.debug("Processing {0}: {1}".format(type(dict_sig).__name__, dict_sig))
if 'close_event' in dict_sig:
self.close()
#==============================================================================
# def statusMessage(self, message):
# """
# Display a message in the statusbar.
# """
# self.statusBar().showMessage(message)
#
#
#==============================================================================
# =============================================================================
[docs]
def closeEvent(self, event):
"""
reimplement QMainWindow.closeEvent() to prompt the user
"""
# test for a handle to other pop-up windows and close them, otherwise
# other pop-up windows can block the Messagebox (which has focus) and
# pyfda cannot be terminated and freezes
tran_freq_win_handle_vis = False
fir_win_handle_vis = False
if not dirs.csv_options_handle is None:
dirs.csv_options_handle.close()
if not dirs.tran_freq_win_handle is None and dirs.tran_freq_win_handle.isVisible():
tran_freq_win_handle_vis = True
dirs.tran_freq_win_handle.hide()
if not dirs.firwin_handle is None and dirs.firwin_handle.isVisible():
fir_win_handle_vis = True
dirs.firwin_handle.hide()
reply = QMessageBox.question(self, 'Message',
"Quit pyFDA?", QMessageBox.Yes, QMessageBox.No)
if reply == QMessageBox.Yes:
# Clear clipboard before exit to avoid error message on older Qt versions
# "QClipboard: Unable to receive an event from the clipboard manager
# in a reasonable time
fb.clipboard.clear()
event.accept()
else: # restore hidden pop-up windows
if fir_win_handle_vis:
dirs.firwin_handle.show()
if tran_freq_win_handle_vis:
dirs.tran_freq_win_handle.show()
event.ignore()
#==============================================================================