class SelectFilter(QWidget): """ Construct and read combo boxes for selecting the filter, consisting of the following hierarchy: 1. Response Type rt (LP, HP, Hilbert, ...) 2. Filter Type ft (IIR, FIR, CIC ...) 3. Filter Class (Butterworth, ...) Every time a combo box is changed manually, the filter tree for the selected response resp. filter type is read and the combo box(es) further down in the hierarchy are populated according to the available combinations. sig_tx({'filt_changed'}) is emitted and propagated to input_filter_specs.py where it triggers the recreation of all subwidgets. """ sig_tx = pyqtSignal(object) # outgoing from pyfda.libs.pyfda_qt_lib import emit def __init__(self, parent=None): super(SelectFilter, self).__init__(parent) self.fc_last = '' # previous filter class self._construct_UI() self._set_response_type() # first time initialization def _construct_UI(self): """ Construct UI with comboboxes for selecting filter: - cmbResponseType for selecting response type rt (LP, HP, ...) - cmbFilterType for selection of filter type (IIR, FIR, ...) - cmbFilterClass for selection of design design class (Chebyshev, ...) and populate them from the "filterTree" dict during the initial run. Later, calling _set_response_type() updates the three combo boxes. See filterbroker.py for structure and content of "filterTree" dict """ # ---------------------------------------------------------------------- # Combo boxes for filter selection # ---------------------------------------------------------------------- self.cmbResponseType = QComboBox(self) self.cmbResponseType.setObjectName("comboResponseType") self.cmbResponseType.setToolTip("Select filter response type.") self.cmbFilterType = QComboBox(self) self.cmbFilterType.setObjectName("comboFilterType") self.cmbFilterType.setToolTip( "<span>Choose filter type, either recursive (Infinite Impulse Response) " "or transversal (Finite Impulse Response).</span>") self.cmbFilterClass = QComboBox(self) self.cmbFilterClass.setObjectName("comboFilterClass") self.cmbFilterClass.setToolTip("Select the filter design class.") # Adapt comboboxes size dynamically to largest element self.cmbResponseType.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.cmbFilterType.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.cmbFilterClass.setSizeAdjustPolicy(QComboBox.AdjustToContents) # ---------------------------------------------------------------------- # Populate combo box with initial settings from fb.fil_tree # ---------------------------------------------------------------------- # Translate short response type ("LP") to displayed names ("Lowpass") # (correspondence is defined in pyfda_rc.py) and populate rt combo box # rt_list = sorted(list(fb.fil_tree.keys())) for rt in rt_list: try: self.cmbResponseType.addItem(rc.rt_names[rt], rt) except KeyError as e: logger.warning( f"KeyError: {e} has no corresponding full name in rc.rt_names." ) idx = self.cmbResponseType.findData('LP') # find index for 'LP' if idx == -1: # Key 'LP' does not exist, use first entry instead idx = 0 self.cmbResponseType.setCurrentIndex(idx) # set initial index rt = qget_cmb_box(self.cmbResponseType) for ft in fb.fil_tree[rt]: self.cmbFilterType.addItem(rc.ft_names[ft], ft) self.cmbFilterType.setCurrentIndex(0) # set initial index ft = qget_cmb_box(self.cmbFilterType) for fc in fb.fil_tree[rt][ft]: self.cmbFilterClass.addItem(fb.filter_classes[fc]['name'], fc) self.cmbFilterClass.setCurrentIndex(0) # set initial index # ---------------------------------------------------------------------- # Layout for Filter Type Subwidgets # ---------------------------------------------------------------------- layHFilWdg = QHBoxLayout() # container for filter subwidgets layHFilWdg.addWidget(self.cmbResponseType) # LP, HP, BP, etc. layHFilWdg.addStretch() layHFilWdg.addWidget(self.cmbFilterType) # FIR, IIR layHFilWdg.addStretch() layHFilWdg.addWidget(self.cmbFilterClass) # bessel, elliptic, etc. # ---------------------------------------------------------------------- # Layout for dynamic filter subwidgets (empty frame) # ---------------------------------------------------------------------- # see Summerfield p. 278 self.layHDynWdg = QHBoxLayout() # for additional dynamic subwidgets # ---------------------------------------------------------------------- # Filter Order Subwidgets # ---------------------------------------------------------------------- self.lblOrder = QLabel("<b>Order:</b>") self.chkMinOrder = QCheckBox("Minimum", self) self.chkMinOrder.setToolTip( "<span>Minimum filter order / # of taps is determined automatically.</span>" ) self.lblOrderN = QLabel("<b><i>N =</i></b>") self.ledOrderN = QLineEdit(str(fb.fil[0]['N']), self) self.ledOrderN.setToolTip("Filter order (# of taps - 1).") # -------------------------------------------------- # Layout for filter order subwidgets # -------------------------------------------------- layHOrdWdg = QHBoxLayout() layHOrdWdg.addWidget(self.lblOrder) layHOrdWdg.addWidget(self.chkMinOrder) layHOrdWdg.addStretch() layHOrdWdg.addWidget(self.lblOrderN) layHOrdWdg.addWidget(self.ledOrderN) # ---------------------------------------------------------------------- # OVERALL LAYOUT (stack standard + dynamic subwidgets vertically) # ---------------------------------------------------------------------- self.layVAllWdg = QVBoxLayout() self.layVAllWdg.addLayout(layHFilWdg) self.layVAllWdg.addLayout(self.layHDynWdg) self.layVAllWdg.addLayout(layHOrdWdg) # ============================================================================== frmMain = QFrame(self) frmMain.setLayout(self.layVAllWdg) layHMain = QHBoxLayout() layHMain.addWidget(frmMain) layHMain.setContentsMargins(*rc.params['wdg_margins']) self.setLayout(layHMain) # ============================================================================== # ------------------------------------------------------------ # SIGNALS & SLOTS # ------------------------------------------------------------ # Connect comboBoxes and setters, propgate change events hierarchically # through all widget methods and emit 'filt_changed' in the end. self.cmbResponseType.currentIndexChanged.connect( lambda: self._set_response_type(enb_signal=True)) # 'LP' self.cmbFilterType.currentIndexChanged.connect( lambda: self._set_filter_type(enb_signal=True)) # 'IIR' self.cmbFilterClass.currentIndexChanged.connect( lambda: self._set_design_method(enb_signal=True)) # 'cheby1' self.chkMinOrder.clicked.connect( lambda: self._set_filter_order(enb_signal=True)) # Min. Order self.ledOrderN.editingFinished.connect( lambda: self._set_filter_order(enb_signal=True)) # Manual Order # ------------------------------------------------------------ # ------------------------------------------------------------------------------ def load_dict(self): """ Reload comboboxes from filter dictionary to update changed settings after loading a filter design from disk. `load_dict` uses the automatism of _set_response_type etc. of checking whether the previously selected filter design method is also available for the new combination. """ # find index for response type: rt_idx = self.cmbResponseType.findData(fb.fil[0]['rt']) self.cmbResponseType.setCurrentIndex(rt_idx) self._set_response_type() # ------------------------------------------------------------------------------ def _set_response_type(self, enb_signal=False): """ Triggered when cmbResponseType (LP, HP, ...) is changed: Copy selection to self.rt and fb.fil[0] and reconstruct filter type combo If previous filter type (FIR, IIR, ...) exists for new rt, set the filter type combo box to the old setting """ # Read current setting of comboBox as string and store it in the filter dict fb.fil[0]['rt'] = self.rt = qget_cmb_box(self.cmbResponseType) # Get list of available filter types for new rt ft_list = list( fb.fil_tree[self.rt].keys()) # explicit list() needed for Py3 # --------------------------------------------------------------- # Rebuild filter type combobox entries for new rt setting self.cmbFilterType.blockSignals( True) # don't fire when changed programmatically self.cmbFilterType.clear() for ft in fb.fil_tree[self.rt]: self.cmbFilterType.addItem(rc.ft_names[ft], ft) # Is current filter type (e.g. IIR) in list for new rt? if fb.fil[0]['ft'] in ft_list: ft_idx = self.cmbFilterType.findText(fb.fil[0]['ft']) self.cmbFilterType.setCurrentIndex( ft_idx) # yes, set same ft as before else: self.cmbFilterType.setCurrentIndex(0) # no, set index 0 self.cmbFilterType.blockSignals(False) # --------------------------------------------------------------- self._set_filter_type(enb_signal) # ------------------------------------------------------------------------------ def _set_filter_type(self, enb_signal=False): """" Triggered when cmbFilterType (IIR, FIR, ...) is changed: - read filter type ft and copy it to fb.fil[0]['ft'] and self.ft - (re)construct design method combo, adding displayed text (e.g. "Chebyshev 1") and hidden data (e.g. "cheby1") """ # Read out current setting of comboBox and convert to string fb.fil[0]['ft'] = self.ft = qget_cmb_box(self.cmbFilterType) # logger.debug("InputFilter.set_filter_type triggered: {0}".format( self.ft)) # --------------------------------------------------------------- # Get all available design methods for new ft from fil_tree and # - Collect them in fc_list # - Rebuild design method combobox entries for new ft setting: # The combobox is populated with the "long name", # the internal name is stored in comboBox.itemData self.cmbFilterClass.blockSignals(True) self.cmbFilterClass.clear() fc_list = [] for fc in sorted(fb.fil_tree[self.rt][self.ft]): self.cmbFilterClass.addItem(fb.filter_classes[fc]['name'], fc) fc_list.append(fc) logger.debug("fc_list: {0}\n{1}".format(fc_list, fb.fil[0]['fc'])) # Does new ft also provide the previous design method (e.g. ellip)? # Has filter been instantiated? if fb.fil[0]['fc'] in fc_list and ff.fil_inst: # yes, set same fc as before fc_idx = self.cmbFilterClass.findText( fb.filter_classes[fb.fil[0]['fc']]['name']) logger.debug("fc_idx : %s", fc_idx) self.cmbFilterClass.setCurrentIndex(fc_idx) else: self.cmbFilterClass.setCurrentIndex(0) # no, set index 0 self.cmbFilterClass.blockSignals(False) self._set_design_method(enb_signal) # ------------------------------------------------------------------------------ def _set_design_method(self, enb_signal=False): """ Triggered when cmbFilterClass (cheby1, ...) is changed: - read design method fc and copy it to fb.fil[0] - create / update global filter instance fb.fil_inst of fc class - update dynamic widgets (if fc has changed and if there are any) - call load filter order """ fb.fil[0]['fc'] = fc = qget_cmb_box(self.cmbFilterClass) if fc != self.fc_last: # fc has changed: # when filter has been changed, try to destroy dynamic widgets of last fc: if self.fc_last: self._destruct_dyn_widgets() # ================================================================== """ Create new instance of the selected filter class, accessible via its handle fb.fil_inst """ err = ff.fil_factory.create_fil_inst(fc) logger.debug(f"InputFilter.set_design_method triggered: {fc}\n" f"Returned error code {err}") # ================================================================== # Check whether new design method also provides the old filter order # method. If yes, don't change it, else set first available # filter order method if fb.fil[0]['fo'] not in fb.fil_tree[self.rt][self.ft][fc].keys(): fb.fil[0].update({'fo': {}}) # explicit list(dict.keys()) needed for Python 3 fb.fil[0]['fo'] = list( fb.fil_tree[self.rt][self.ft][fc].keys())[0] # ============================================================================= # logger.debug("selFilter = %s" # "filterTree[fc] = %s" # "filterTree[fc].keys() = %s" # %(fb.fil[0], fb.fil_tree[self.rt][self.ft][fc],\ # fb.fil_tree[self.rt][self.ft][fc].keys() # )) # # ============================================================================= # construct dyn. subwidgets if available if hasattr(ff.fil_inst, 'construct_UI'): self._construct_dyn_widgets() self.fc_last = fb.fil[0]['fc'] self.load_filter_order(enb_signal) # ------------------------------------------------------------------------------ def load_filter_order(self, enb_signal=False): """ Called by set_design_method or from InputSpecs (with enb_signal = False), load filter order setting from fb.fil[0] and update widgets """ # collect dict_keys of available filter order [fo] methods for selected # design method [fc] from fil_tree (explicit list() needed for Python 3) fo_dict = fb.fil_tree[fb.fil[0]['rt']][fb.fil[0]['ft']][fb.fil[0] ['fc']] fo_list = list(fo_dict.keys()) # is currently selected fo setting available for (new) fc ? if fb.fil[0]['fo'] in fo_list: self.fo = fb.fil[0]['fo'] # keep current setting else: self.fo = fo_list[0] # use first list entry from filterTree fb.fil[0]['fo'] = self.fo # and update fo method # check whether fo widget is active, disabled or invisible if 'fo' in fo_dict[self.fo] and len(fo_dict[self.fo]['fo']) > 1: status = fo_dict[self.fo]['fo'][0] else: status = 'i' # Determine which subwidgets are __visible__ self.chkMinOrder.setVisible('min' in fo_list) self.ledOrderN.setVisible(status in {'a', 'd'}) self.lblOrderN.setVisible(status in {'a', 'd'}) # Determine which subwidgets are __enabled__ self.chkMinOrder.setChecked(fb.fil[0]['fo'] == 'min') self.ledOrderN.setText(str(fb.fil[0]['N'])) self.ledOrderN.setEnabled(not self.chkMinOrder.isChecked() and status == 'a') self.lblOrderN.setEnabled(not self.chkMinOrder.isChecked() and status == 'a') if enb_signal: logger.debug("Emit 'filt_changed'") self.emit({'filt_changed': 'filter_type'}) # ------------------------------------------------------------------------------ def _set_filter_order(self, enb_signal=False): """ Triggered when either ledOrderN or chkMinOrder are edited: - copy settings to fb.fil[0] - emit 'filt_changed' if enb_signal is True """ # Determine which subwidgets are _enabled_ if self.chkMinOrder.isVisible(): self.ledOrderN.setEnabled(not self.chkMinOrder.isChecked()) self.lblOrderN.setEnabled(not self.chkMinOrder.isChecked()) if self.chkMinOrder.isChecked() is True: # update in case N has been changed outside this class self.ledOrderN.setText(str(fb.fil[0]['N'])) fb.fil[0].update({'fo': 'min'}) else: fb.fil[0].update({'fo': 'man'}) else: self.lblOrderN.setEnabled(self.fo == 'man') self.ledOrderN.setEnabled(self.fo == 'man') # read manual filter order, convert to positive integer and store it # in filter dictionary. ordn = safe_eval(self.ledOrderN.text(), fb.fil[0]['N'], return_type='int', sign='pos') ordn = ordn if ordn > 0 else 1 self.ledOrderN.setText(str(ordn)) fb.fil[0].update({'N': ordn}) if enb_signal: logger.debug("Emit 'filt_changed'") self.emit({'filt_changed': 'filter_order_widget'}) # ------------------------------------------------------------------------------ def _destruct_dyn_widgets(self): """ Delete the dynamically created filter design subwidget (if there is one) see http://stackoverflow.com/questions/13827798/proper-way-to-cleanup- widgets-in-pyqt This does NOT work when the subwidgets to be deleted and created are identical, as the deletion is only performed when the current scope has been left (?)! Hence, it is necessary to skip this method when the new design method is the same as the old one. """ if hasattr(ff.fil_inst, 'wdg_fil'): # not needed, connection is destroyed automatically # ff.fil_inst.sig_tx.disconnect() try: # remove widget from layout self.layHDynWdg.removeWidget(self.dyn_wdg_fil) # delete UI widget when scope has been left self.dyn_wdg_fil.deleteLater() except AttributeError as e: logger.error("Could not destruct_UI!\n{0}".format(e)) ff.fil_inst.deleteLater( ) # delete QWidget when scope has been left # ------------------------------------------------------------------------------ def _construct_dyn_widgets(self): """ Create filter widget UI dynamically (if the filter routine has one) and connect its sig_tx signal to sig_tx in this scope. """ ff.fil_inst.construct_UI() if hasattr(ff.fil_inst, 'wdg_fil'): try: self.dyn_wdg_fil = getattr(ff.fil_inst, 'wdg_fil') self.layHDynWdg.addWidget(self.dyn_wdg_fil, stretch=1) except AttributeError as e: logger.warning(e) if hasattr(ff.fil_inst, 'sig_tx'): ff.fil_inst.sig_tx.connect(self.sig_tx)
class Input_Coeffs_UI(QWidget): """ Create the UI for the FilterCoeffs class """ sig_rx = pyqtSignal(dict) # incoming sig_tx = pyqtSignal(dict) # outgoing from pyfda.libs.pyfda_qt_lib import emit def __init__(self, parent=None): super(Input_Coeffs_UI, self).__init__(parent) self.eps = 1.e-6 # initialize tolerance value self._construct_UI() # ------------------------------------------------------------------------------ def process_sig_rx(self, dict_sig=None): """ Process signals coming from the CSV pop-up window """ # logger.debug("PROCESS_SIG_RX:\n{0}".format(pprint_log(dict_sig))) if 'closeEvent' in dict_sig: self._close_csv_win() self.emit({'ui_changed': 'csv'}) return elif 'ui_changed' in dict_sig: self._set_load_save_icons() # update icons file <-> clipboard # inform e.g. the p/z input widget about changes in CSV options self.emit({'ui_changed': 'csv'}) # ------------------------------------------------------------------------------ def _construct_UI(self): """ Intitialize the widget, consisting of: - top chkbox row - coefficient table - two bottom rows with action buttons """ self.bfont = QFont() self.bfont.setBold(True) self.bifont = QFont() self.bifont.setBold(True) self.bifont.setItalic(True) # q_icon_size = QSize(20, 20) # optional, size is derived from butEnable ####################################################################### # frmMain # # This frame contains all the buttons ####################################################################### # --------------------------------------------- # layHDisplay # # UI Elements for controlling the display # --------------------------------------------- self.butEnable = PushButton(self, icon=QIcon(':/circle-check.svg'), checked=True) q_icon_size = self.butEnable.iconSize() # <- uncomment this for manual sizing self.butEnable.setToolTip( "<span>Show / hide filter coefficients in an editable table." " For high order systems, table display might be slow.</span>") fix_formats = ['Dec', 'Hex', 'Bin', 'CSD'] self.cmbFormat = QComboBox(self) model = self.cmbFormat.model() item = QtGui.QStandardItem('Float') item.setData('child', Qt.AccessibleDescriptionRole) model.appendRow(item) item = QtGui.QStandardItem('Fixp.:') item.setData('parent', Qt.AccessibleDescriptionRole) item.setData(0, QtGui.QFont.Bold) item.setFlags(item.flags() & ~Qt.ItemIsEnabled) # | Qt.ItemIsSelectable)) model.appendRow(item) for idx in range(len(fix_formats)): item = QtGui.QStandardItem(fix_formats[idx]) # item.setForeground(QtGui.QColor('red')) model.appendRow(item) self.cmbFormat.insertSeparator(1) qset_cmb_box(self.cmbFormat, 'float') self.cmbFormat.setToolTip('Set the display format.') self.cmbFormat.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.spnDigits = QSpinBox(self) self.spnDigits.setRange(0, 16) self.spnDigits.setValue(params['FMT_ba']) self.spnDigits.setToolTip("Number of digits to display.") self.lblDigits = QLabel("Digits", self) self.lblDigits.setFont(self.bifont) self.cmbQFrmt = QComboBox(self) q_formats = [('Norm. Frac.', 'qnfrac'), ('Integer', 'qint'), ('Fractional', 'qfrac')] for q in q_formats: self.cmbQFrmt.addItem(*q) self.lbl_W = QLabel("W = ", self) self.lbl_W.setFont(self.bifont) self.ledW = QLineEdit(self) self.ledW.setToolTip("Specify total wordlength.") self.ledW.setText("16") self.ledW.setMaxLength(2) # maximum of 2 digits self.ledW.setFixedWidth(30) # width of lineedit in points(?) layHDisplay = QHBoxLayout() layHDisplay.setAlignment(Qt.AlignLeft) layHDisplay.addWidget(self.butEnable) layHDisplay.addWidget(self.cmbFormat) layHDisplay.addWidget(self.spnDigits) layHDisplay.addWidget(self.lblDigits) layHDisplay.addWidget(self.cmbQFrmt) layHDisplay.addWidget(self.lbl_W) layHDisplay.addWidget(self.ledW) layHDisplay.addStretch() ####################################################################### # frmButtonsCoeffs # # This frame contains all buttons for manipulating coefficients ####################################################################### # ----------------------------------------------------------------- # layHButtonsCoeffs1 # # UI Elements for loading / storing / manipulating cells and rows # ----------------------------------------------------------------- self.cmbFilterType = QComboBox(self) self.cmbFilterType.setObjectName("comboFilterType") self.cmbFilterType.setToolTip( "<span>Select between IIR and FIR filter for manual entry." "Changing the type reloads the filter from the filter dict.</span>") self.cmbFilterType.addItems(["FIR", "IIR"]) self.cmbFilterType.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.butAddCells = QPushButton(self) self.butAddCells.setIcon(QIcon(':/row_insert_above.svg')) self.butAddCells.setIconSize(q_icon_size) self.butAddCells.setToolTip( "<span>Select cells to insert a new cell above each selected cell. " "Use <SHIFT> or <CTRL> to select multiple cells. " "When nothing is selected, add a row at the end.</span>") self.butDelCells = QPushButton(self) self.butDelCells.setIcon(QIcon(':/row_delete.svg')) self.butDelCells.setIconSize(q_icon_size) self.butDelCells.setToolTip( "<span>Delete selected cell(s) from the table. " "Use <SHIFT> or <CTRL> to select multiple cells. " "When nothing is selected, delete the last row.</span>") self.butSave = QPushButton(self) self.butSave.setIcon(QIcon(':/upload.svg')) self.butSave.setIconSize(q_icon_size) self.butSave.setToolTip( "<span>Copy coefficient table to filter dict and update all plots" "and widgets.</span>") self.butLoad = QPushButton(self) self.butLoad.setIcon(QIcon(':/download.svg')) self.butLoad.setIconSize(q_icon_size) self.butLoad.setToolTip("Reload coefficient table from filter dict.") self.butClear = QPushButton(self) self.butClear.setIcon(QIcon(':/trash.svg')) self.butClear.setIconSize(q_icon_size) self.butClear.setToolTip("Clear all table entries.") self.butFromTable = QPushButton(self) self.butFromTable.setIconSize(q_icon_size) self.butToTable = QPushButton(self) self.butToTable.setIconSize(q_icon_size) self.but_csv_options = PushButton(self, icon=QIcon(':/settings.svg'), checked=False) self.but_csv_options.setIconSize(q_icon_size) self.but_csv_options.setToolTip( "<span>Select CSV format and whether " "to copy to/from clipboard or file.</span>") self._set_load_save_icons() # initialize icon / button settings layHButtonsCoeffs1 = QHBoxLayout() layHButtonsCoeffs1.addWidget(self.cmbFilterType) layHButtonsCoeffs1.addWidget(self.butAddCells) layHButtonsCoeffs1.addWidget(self.butDelCells) layHButtonsCoeffs1.addWidget(self.butClear) layHButtonsCoeffs1.addWidget(self.butSave) layHButtonsCoeffs1.addWidget(self.butLoad) layHButtonsCoeffs1.addWidget(self.butFromTable) layHButtonsCoeffs1.addWidget(self.butToTable) layHButtonsCoeffs1.addWidget(self.but_csv_options) layHButtonsCoeffs1.addStretch() # ---------------------------------------------------------------------- # layHButtonsCoeffs2 # # Eps / set zero settings # --------------------------------------------------------------------- self.butSetZero = QPushButton("= 0", self) self.butSetZero.setToolTip( "<span>Set selected coefficients = 0 with a magnitude < ε. " "When nothing is selected, test the whole table.</span>") self.butSetZero.setIconSize(q_icon_size) lblEps = QLabel(self) lblEps.setText("<b><i>for b, a</i> <</b>") self.ledEps = QLineEdit(self) self.ledEps.setToolTip("Specify tolerance value.") layHButtonsCoeffs2 = QHBoxLayout() layHButtonsCoeffs2.addWidget(self.butSetZero) layHButtonsCoeffs2.addWidget(lblEps) layHButtonsCoeffs2.addWidget(self.ledEps) layHButtonsCoeffs2.addStretch() # ------------------------------------------------------------------- # Now put the ButtonsCoeffs HBoxes into frmButtonsCoeffs # --------------------------------------------------------------------- layVButtonsCoeffs = QVBoxLayout() layVButtonsCoeffs.addLayout(layHButtonsCoeffs1) layVButtonsCoeffs.addLayout(layHButtonsCoeffs2) layVButtonsCoeffs.setContentsMargins(0, 5, 0, 0) # This frame encompasses all Quantization Settings self.frmButtonsCoeffs = QFrame(self) self.frmButtonsCoeffs.setLayout(layVButtonsCoeffs) # ###################################################################### # frmQSettings # # This frame contains all quantization settings # ###################################################################### # ------------------------------------------------------------------- # layHW_Scale # # QFormat and scale settings # --------------------------------------------------------------------- lbl_Q = QLabel("Q =", self) lbl_Q.setFont(self.bifont) self.ledWI = QLineEdit(self) self.ledWI.setToolTip("Specify number of integer bits.") self.ledWI.setText("0") self.ledWI.setMaxLength(2) # maximum of 2 digits self.ledWI.setFixedWidth(30) # width of lineedit in points(?) self.lblDot = QLabel(".", self) # class attribute, visibility is toggled self.lblDot.setFont(self.bfont) self.ledWF = QLineEdit(self) self.ledWF.setToolTip("Specify number of fractional bits.") self.ledWF.setText("15") self.ledWF.setMaxLength(2) # maximum of 2 digits # self.ledWF.setFixedWidth(30) # width of lineedit in points(?) self.ledWF.setMaximumWidth(30) self.lblScale = QLabel("<b><i>Scale</i> =</b>", self) self.ledScale = QLineEdit(self) self.ledScale.setToolTip( "Set the scale for converting float to fixpoint representation.") self.ledScale.setText(str(1)) self.ledScale.setEnabled(False) layHWI_WF = QHBoxLayout() layHWI_WF.addWidget(lbl_Q) layHWI_WF.addWidget(self.ledWI) layHWI_WF.addWidget(self.lblDot) layHWI_WF.addWidget(self.ledWF) layHWI_WF.addStretch() layHScale = QHBoxLayout() layHScale.addWidget(self.lblScale) layHScale.addWidget(self.ledScale) layHScale.addStretch() layHW_Scale = QHBoxLayout() layHW_Scale.addLayout(layHWI_WF) layHW_Scale.addLayout(layHScale) # ------------------------------------------------------------------- # layGQOpt # # Quantization / Overflow / MSB / LSB settings # --------------------------------------------------------------------- lblQOvfl = QLabel("Ovfl.:", self) lblQOvfl.setFont(self.bifont) lblQuant = QLabel("Quant.:", self) lblQuant.setFont(self.bifont) self.cmbQOvfl = QComboBox(self) qOvfl = ['wrap', 'sat'] self.cmbQOvfl.addItems(qOvfl) qset_cmb_box(self.cmbQOvfl, 'sat') self.cmbQOvfl.setToolTip("Select overflow behaviour.") # ComboBox size is adjusted automatically to fit the longest element self.cmbQOvfl.setSizeAdjustPolicy(QComboBox.AdjustToContents) layHQOvflOpt = QHBoxLayout() layHQOvflOpt.addWidget(lblQOvfl) layHQOvflOpt.addWidget(self.cmbQOvfl) layHQOvflOpt.addStretch() self.cmbQuant = QComboBox(self) qQuant = ['none', 'round', 'fix', 'floor'] self.cmbQuant.addItems(qQuant) qset_cmb_box(self.cmbQuant, 'round') self.cmbQuant.setToolTip("Select the kind of quantization.") self.cmbQuant.setSizeAdjustPolicy(QComboBox.AdjustToContents) layHQuantOpt = QHBoxLayout() layHQuantOpt.addWidget(lblQuant) layHQuantOpt.addWidget(self.cmbQuant) layHQuantOpt.addStretch() self.butQuant = QPushButton(self) self.butQuant.setToolTip( "<span>Quantize selected coefficients / " "whole table with specified settings.</span>") self.butQuant.setIcon(QIcon(':/quantize.svg')) self.butQuant.setIconSize(q_icon_size) self.butQuant.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) lblMSBtxt = QLabel(self) lblMSBtxt.setText("<b><i>MSB</i><sub>10</sub> =</b>") self.lblMSB = QLabel(self) layHMSB = QHBoxLayout() layHMSB.addWidget(lblMSBtxt) layHMSB.addWidget(self.lblMSB) layHMSB.addStretch() lblLSBtxt = QLabel(self) lblLSBtxt.setText("<b><i>LSB</i><sub>10</sub> =</b>") self.lblLSB = QLabel(self) layHLSB = QHBoxLayout() layHLSB.addWidget(lblLSBtxt) layHLSB.addWidget(self.lblLSB) layHLSB.addStretch() layGQOpt = QGridLayout() layGQOpt.addLayout(layHQOvflOpt, 0, 0) layGQOpt.addLayout(layHQuantOpt, 0, 1) layGQOpt.addWidget(self.butQuant, 0, 2, Qt.AlignCenter) layGQOpt.addLayout(layHMSB, 1, 0) layGQOpt.addLayout(layHLSB, 1, 1) # ------------------------------------------------------------------- # Display MAX # --------------------------------------------------------------------- lblMAXtxt = QLabel(self) lblMAXtxt.setText("<b><i>Max =</i></b>") self.lblMAX = QLabel(self) layHCoeffs_MAX = QHBoxLayout() layHCoeffs_MAX.addWidget(lblMAXtxt) layHCoeffs_MAX.addWidget(self.lblMAX) layHCoeffs_MAX.addStretch() ####################################################################### # Now put all the coefficient HBoxes into frmQSettings # --------------------------------------------------------------------- layVButtonsQ = QVBoxLayout() layVButtonsQ.addLayout(layHW_Scale) layVButtonsQ.addLayout(layGQOpt) layVButtonsQ.addLayout(layHCoeffs_MAX) layVButtonsQ.setContentsMargins(0, 0, 0, 0) # This frame encompasses all Quantization Settings self.frmQSettings = QFrame(self) self.frmQSettings.setLayout(layVButtonsQ) ####################################################################### # ######################## Main UI Layout ############################ ####################################################################### # layout for frame (UI widget) layVMainF = QVBoxLayout() layVMainF.addLayout(layHDisplay) layVMainF.addWidget(self.frmQSettings) layVMainF.addWidget(QHLine()) layVMainF.addWidget(self.frmButtonsCoeffs) # This frame encompasses all UI elements frmMain = QFrame(self) frmMain.setLayout(layVMainF) layVMain = QVBoxLayout() # the following affects only the first widget (intended here) layVMain.setAlignment(Qt.AlignTop) layVMain.addWidget(frmMain) layVMain.setContentsMargins(*params['wdg_margins']) self.setLayout(layVMain) ####################################################################### # --- set initial values from dict ------------ self.spnDigits.setValue(params['FMT_ba']) self.ledEps.setText(str(self.eps)) # ---------------------------------------------------------------------- # LOCAL SIGNALS & SLOTs # ---------------------------------------------------------------------- self.but_csv_options.clicked.connect(self._open_csv_win) # -------------------------------------------------------------------------- def _open_csv_win(self): """ Pop-up window for CSV options """ if self.but_csv_options.isChecked(): qstyle_widget(self.but_csv_options, "changed") else: qstyle_widget(self.but_csv_options, "normal") if dirs.csv_options_handle is None: # no handle to the window? Create a new instance if self.but_csv_options.isChecked(): # Important: Handle to window must be class attribute, otherwise it # (and the attached window) is deleted immediately when it goes # out of scope dirs.csv_options_handle = CSV_option_box(self) dirs.csv_options_handle.sig_tx.connect(self.process_sig_rx) dirs.csv_options_handle.show() # modeless i.e. non-blocking popup window else: if not self.but_csv_options.isChecked(): # this should not happen if dirs.csv_options_handle is None: logger.warning("CSV options window is already closed!") else: dirs.csv_options_handle.close() self.emit({'ui_changed': 'csv'}) # ------------------------------------------------------------------------------ def _close_csv_win(self): dirs.csv_options_handle = None self.but_csv_options.setChecked(False) qstyle_widget(self.but_csv_options, "normal") # ------------------------------------------------------------------------------ def _set_load_save_icons(self): """ Set icons / tooltipps for loading and saving data to / from file or clipboard depending on selected options. """ if params['CSV']['clipboard']: self.butFromTable.setIcon(QIcon(':/to_clipboard.svg')) self.butFromTable.setToolTip( "<span>Copy table to clipboard, SELECTED items are copied as " "displayed. When nothing is selected, the whole table " "is copied with full precision in decimal format.</span>") self.butToTable.setIcon(QIcon(':/from_clipboard.svg')) self.butToTable.setToolTip("<span>Copy clipboard to table.</span>") else: self.butFromTable.setIcon(QIcon(':/save.svg')) self.butFromTable.setToolTip( "<span>" "Save table to file, SELECTED items are copied as " "displayed. When nothing is selected, the whole table " "is copied with full precision in decimal format.</span>") self.butToTable.setIcon(QIcon(':/file.svg')) self.butToTable.setToolTip("<span>Load table from file.</span>") if dirs.csv_options_handle is None: qstyle_widget(self.but_csv_options, "normal") self.but_csv_options.setChecked(False) else: qstyle_widget(self.but_csv_options, "changed") self.but_csv_options.setChecked(True)
class Plot_FFT_win(QDialog): """ Create a pop-up widget for displaying time and frequency view of an FFT window. Data is passed via the dictionary `win_dict` that is passed during construction. """ # incoming sig_rx = pyqtSignal(object) # outgoing sig_tx = pyqtSignal(object) def __init__(self, parent, win_dict=fb.fil[0]['win_fft'], sym=True, title='pyFDA Window Viewer'): super(Plot_FFT_win, self).__init__(parent) self.needs_calc = True self.needs_draw = True self.needs_redraw = True self.bottom_f = -80 # min. value for dB display self.bottom_t = -60 self.N = 32 # initial number of data points self.N_auto = win_dict['win_len'] self.pad = 16 # amount of zero padding self.win_dict = win_dict self.sym = sym self.tbl_rows = 2 self.tbl_cols = 6 # initial settings for checkboxes self.tbl_sel = [True, True, False, False] self.setAttribute(Qt.WA_DeleteOnClose) self.setWindowTitle(title) self._construct_UI() qwindow_stay_on_top(self, True) #------------------------------------------------------------------------------ def closeEvent(self, event): """ Catch closeEvent (user has tried to close the window) and send a signal to parent where window closing is registered before actually closing the window. """ self.sig_tx.emit({'sender': __name__, 'closeEvent': ''}) event.accept() #------------------------------------------------------------------------------ def process_sig_rx(self, dict_sig=None): """ Process signals coming from the navigation toolbar and from sig_rx """ logger.debug("PROCESS_SIG_RX - vis: {0}\n{1}"\ .format(self.isVisible(), pprint_log(dict_sig))) if ('view_changed' in dict_sig and dict_sig['view_changed'] == 'win')\ or ('filt_changed' in dict_sig and dict_sig['filt_changed'] == 'firwin')\ or self.needs_calc: # logger.warning("Auto: {0} - WinLen: {1}".format(self.N_auto, self.win_dict['win_len'])) self.N_auto = self.win_dict['win_len'] self.calc_N() if self.isVisible(): self.draw() self.needs_calc = False else: self.needs_calc = True elif 'home' in dict_sig: self.update_view() else: logger.error("Unknown content of dict_sig: {0}".format(dict_sig)) #------------------------------------------------------------------------------ def _construct_UI(self): """ Intitialize the widget, consisting of: - Matplotlib widget with NavigationToolbar - Frame with control elements """ self.bfont = QFont() self.bfont.setBold(True) self.chk_auto_N = QCheckBox(self) self.chk_auto_N.setChecked(False) self.chk_auto_N.setToolTip( "Use number of points from calling routine.") self.lbl_auto_N = QLabel("Auto " + to_html("N", frmt='i')) self.led_N = QLineEdit(self) self.led_N.setText(str(self.N)) self.led_N.setMaximumWidth(70) self.led_N.setToolTip("<span>Number of window data points.</span>") self.chk_log_t = QCheckBox("Log", self) self.chk_log_t.setChecked(False) self.chk_log_t.setToolTip("Display in dB") self.led_log_bottom_t = QLineEdit(self) self.led_log_bottom_t.setText(str(self.bottom_t)) self.led_log_bottom_t.setMaximumWidth(50) self.led_log_bottom_t.setEnabled(self.chk_log_t.isChecked()) self.led_log_bottom_t.setToolTip( "<span>Minimum display value for log. scale.</span>") self.lbl_log_bottom_t = QLabel("dB", self) self.lbl_log_bottom_t.setEnabled(self.chk_log_t.isChecked()) self.chk_norm_f = QCheckBox("Norm", self) self.chk_norm_f.setChecked(True) self.chk_norm_f.setToolTip( "Normalize window spectrum for a maximum of 1.") self.chk_half_f = QCheckBox("Half", self) self.chk_half_f.setChecked(True) self.chk_half_f.setToolTip( "Display window spectrum in the range 0 ... 0.5 f_S.") self.chk_log_f = QCheckBox("Log", self) self.chk_log_f.setChecked(True) self.chk_log_f.setToolTip("Display in dB") self.led_log_bottom_f = QLineEdit(self) self.led_log_bottom_f.setText(str(self.bottom_f)) self.led_log_bottom_f.setMaximumWidth(50) self.led_log_bottom_f.setEnabled(self.chk_log_f.isChecked()) self.led_log_bottom_f.setToolTip( "<span>Minimum display value for log. scale.</span>") self.lbl_log_bottom_f = QLabel("dB", self) self.lbl_log_bottom_f.setEnabled(self.chk_log_f.isChecked()) layHControls = QHBoxLayout() layHControls.addWidget(self.chk_auto_N) layHControls.addWidget(self.lbl_auto_N) layHControls.addWidget(self.led_N) layHControls.addStretch(1) layHControls.addWidget(self.chk_log_t) layHControls.addWidget(self.led_log_bottom_t) layHControls.addWidget(self.lbl_log_bottom_t) layHControls.addStretch(10) layHControls.addWidget(self.chk_norm_f) layHControls.addStretch(1) layHControls.addWidget(self.chk_half_f) layHControls.addStretch(1) layHControls.addWidget(self.chk_log_f) layHControls.addWidget(self.led_log_bottom_f) layHControls.addWidget(self.lbl_log_bottom_f) self.tblWinProperties = QTableWidget(self.tbl_rows, self.tbl_cols, self) self.tblWinProperties.setAlternatingRowColors(True) self.tblWinProperties.verticalHeader().setVisible(False) self.tblWinProperties.horizontalHeader().setVisible(False) self._init_table(self.tbl_rows, self.tbl_cols, " ") self.txtInfoBox = QTextBrowser(self) #---------------------------------------------------------------------- # ### frmControls ### # # This widget encompasses all control subwidgets #---------------------------------------------------------------------- self.frmControls = QFrame(self) self.frmControls.setObjectName("frmControls") self.frmControls.setLayout(layHControls) #---------------------------------------------------------------------- # ### mplwidget ### # # main widget: Layout layVMainMpl (VBox) is defined with MplWidget, # additional widgets can be added (like self.frmControls) # The widget encompasses all other widgets. #---------------------------------------------------------------------- self.mplwidget = MplWidget(self) self.mplwidget.layVMainMpl.addWidget(self.frmControls) self.mplwidget.layVMainMpl.setContentsMargins(*params['wdg_margins']) #---------------------------------------------------------------------- # ### frmInfo ### # # This widget encompasses the text info box and the table with window # parameters. #---------------------------------------------------------------------- layVInfo = QVBoxLayout(self) layVInfo.addWidget(self.tblWinProperties) layVInfo.addWidget(self.txtInfoBox) self.frmInfo = QFrame(self) self.frmInfo.setObjectName("frmInfo") self.frmInfo.setLayout(layVInfo) #---------------------------------------------------------------------- # ### splitter ### # # This widget encompasses all control subwidgets #---------------------------------------------------------------------- splitter = QSplitter(self) splitter.setOrientation(Qt.Vertical) splitter.addWidget(self.mplwidget) splitter.addWidget(self.frmInfo) # setSizes uses absolute pixel values, but can be "misused" by specifying values # that are way too large: in this case, the space is distributed according # to the _ratio_ of the values: splitter.setSizes([3000, 1000]) layVMain = QVBoxLayout() layVMain.addWidget(splitter) self.setLayout(layVMain) #---------------------------------------------------------------------- # Set subplots # self.ax = self.mplwidget.fig.subplots(nrows=1, ncols=2) self.ax_t = self.ax[0] self.ax_f = self.ax[1] self.draw() # initial drawing #---------------------------------------------------------------------- # GLOBAL SIGNALS & SLOTs #---------------------------------------------------------------------- self.sig_rx.connect(self.process_sig_rx) #---------------------------------------------------------------------- # LOCAL SIGNALS & SLOTs #---------------------------------------------------------------------- self.chk_log_f.clicked.connect(self.update_view) self.chk_log_t.clicked.connect(self.update_view) self.led_log_bottom_t.editingFinished.connect(self.update_bottom) self.led_log_bottom_f.editingFinished.connect(self.update_bottom) self.chk_auto_N.clicked.connect(self.calc_N) self.led_N.editingFinished.connect(self.draw) self.chk_norm_f.clicked.connect(self.draw) self.chk_half_f.clicked.connect(self.update_view) self.mplwidget.mplToolbar.sig_tx.connect(self.process_sig_rx) self.tblWinProperties.itemClicked.connect(self._handle_item_clicked) #------------------------------------------------------------------------------ def _init_table(self, rows, cols, val): for r in range(rows): for c in range(cols): item = QTableWidgetItem(val) if c % 3 == 0: item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled) if self.tbl_sel[r * 2 + c % 3]: item.setCheckState(Qt.Checked) else: item.setCheckState(Qt.Unchecked) self.tblWinProperties.setItem(r, c, item) # https://stackoverflow.com/questions/12366521/pyqt-checkbox-in-qtablewidget #------------------------------------------------------------------------------ def _set_table_item(self, row, col, val, font=None, sel=None): """ Set the table item with the index `row, col` and the value val """ item = self.tblWinProperties.item(row, col) item.setText(str(val)) if font: self.tblWinProperties.item(row, col).setFont(font) if sel == True: item.setCheckState(Qt.Checked) if sel == False: item.setCheckState(Qt.Unchecked) # when sel is not specified, don't change anything #------------------------------------------------------------------------------ def _handle_item_clicked(self, item): if item.column() % 3 == 0: # clicked on checkbox num = item.row() * 2 + item.column() // 3 if item.checkState() == Qt.Checked: self.tbl_sel[num] = True logger.debug('"{0}:{1}" Checked'.format(item.text(), num)) else: self.tbl_sel[num] = False logger.debug('"{0}:{1}" Unchecked'.format(item.text(), num)) elif item.column() % 3 == 1: # clicked on value field logger.info("{0:s} copied to clipboard.".format(item.text())) fb.clipboard.setText(item.text()) self.update_view() #------------------------------------------------------------------------------ def update_bottom(self): """ Update log bottom settings """ self.bottom_t = safe_eval(self.led_log_bottom_t.text(), self.bottom_t, sign='neg', return_type='float') self.led_log_bottom_t.setText(str(self.bottom_t)) self.bottom_f = safe_eval(self.led_log_bottom_f.text(), self.bottom_f, sign='neg', return_type='float') self.led_log_bottom_f.setText(str(self.bottom_f)) self.update_view() #------------------------------------------------------------------------------ def calc_N(self): """ (Re-)Calculate the number of data points when Auto N chkbox has been clicked or when the number of data points has been updated outside this class """ if self.chk_auto_N.isChecked(): self.N = self.N_auto self.draw() #------------------------------------------------------------------------------ def calc_win(self): """ (Re-)Calculate the window and its FFT """ self.led_N.setEnabled(not self.chk_auto_N.isChecked()) if not self.chk_auto_N.isChecked(): self.N = safe_eval(self.led_N.text(), self.N, sign='pos', return_type='int') # else: #self.N = self.win_dict['win_len'] self.led_N.setText(str(self.N)) self.n = np.arange(self.N) self.win = calc_window_function(self.win_dict, self.win_dict['name'], self.N, sym=self.sym) self.nenbw = self.N * np.sum(np.square(self.win)) / (np.square( np.sum(self.win))) self.cgain = np.sum(self.win) / self.N self.F = fftfreq(self.N * self.pad, d=1. / fb.fil[0]['f_S']) # use zero padding self.Win = np.abs(fft(self.win, self.N * self.pad)) if self.chk_norm_f.isChecked(): self.Win /= (self.N * self.cgain ) # correct gain for periodic signals (coherent gain) first_zero = argrelextrema(self.Win[:(self.N * self.pad) // 2], np.less) if np.shape(first_zero)[1] > 0: first_zero = first_zero[0][0] self.first_zero_f = self.F[first_zero] self.sidelobe_level = np.max( self.Win[first_zero:(self.N * self.pad) // 2]) else: self.first_zero_f = np.nan self.sidelobe_level = 0 #------------------------------------------------------------------------------ def draw(self): """ Main entry point: Re-calculate \|H(f)\| and draw the figure """ self.calc_win() self.update_view() #------------------------------------------------------------------------------ def update_view(self): """ Draw the figure with new limits, scale etc without recalculating the window. """ # suppress "divide by zero in log10" warnings old_settings_seterr = np.seterr() np.seterr(divide='ignore') self.ax_t.cla() self.ax_f.cla() self.ax_t.set_xlabel(fb.fil[0]['plt_tLabel']) self.ax_t.set_ylabel(r'$w[n] \; \rightarrow$') self.ax_f.set_xlabel(fb.fil[0]['plt_fLabel']) self.ax_f.set_ylabel(r'$W(f) \; \rightarrow$') if self.chk_log_t.isChecked(): self.ax_t.plot( self.n, np.maximum(20 * np.log10(np.abs(self.win)), self.bottom_t)) else: self.ax_t.plot(self.n, self.win) if self.chk_half_f.isChecked(): F = self.F[:len(self.F * self.pad) // 2] Win = self.Win[:len(self.F * self.pad) // 2] else: F = fftshift(self.F) Win = fftshift(self.Win) if self.chk_log_f.isChecked(): self.ax_f.plot( F, np.maximum(20 * np.log10(np.abs(Win)), self.bottom_f)) self.nenbw_disp = 10 * np.log10(self.nenbw) self.cgain_disp = 20 * np.log10(self.cgain) self.sidelobe_level_disp = 20 * np.log10(self.sidelobe_level) self.unit_nenbw = "dB" self.unit_scale = "dB" else: self.ax_f.plot(F, Win) self.nenbw_disp = self.nenbw self.cgain_disp = self.cgain self.sidelobe_level_disp = self.sidelobe_level self.unit_nenbw = "bins" self.unit_scale = "" self.led_log_bottom_t.setEnabled(self.chk_log_t.isChecked()) self.lbl_log_bottom_t.setEnabled(self.chk_log_t.isChecked()) self.led_log_bottom_f.setEnabled(self.chk_log_f.isChecked()) self.lbl_log_bottom_f.setEnabled(self.chk_log_f.isChecked()) window_name = self.win_dict['name'] param_txt = "" if self.win_dict['n_par'] > 0: param_txt = " (" + self.win_dict['par'][0][ 'name_tex'] + " = {0:.3g})".format( self.win_dict['par'][0]['val']) if self.win_dict['n_par'] > 1: param_txt = param_txt[:-1]\ + ", {0:s} = {1:.3g})".format(self.win_dict['par'][1]['name_tex'], self.win_dict['par'][1]['val']) self.mplwidget.fig.suptitle(r'{0} Window'.format(window_name) + param_txt) # plot a line at the max. sidelobe level if self.tbl_sel[3]: self.ax_f.axhline(self.sidelobe_level_disp, ls='dotted', c='b') patch = mpl_patches.Rectangle((0, 0), 1, 1, fc="white", ec="white", lw=0, alpha=0) # Info legend for time domain window labels_t = [] labels_t.append("$N$ = {0:d}".format(self.N)) self.ax_t.legend([patch], labels_t, loc='best', fontsize='small', fancybox=True, framealpha=0.7, handlelength=0, handletextpad=0) # Info legend for frequency domain window labels_f = [] N_patches = 0 if self.tbl_sel[0]: labels_f.append("$NENBW$ = {0:.4g} {1}".format( self.nenbw_disp, self.unit_nenbw)) N_patches += 1 if self.tbl_sel[1]: labels_f.append("$CGAIN$ = {0:.4g} {1}".format( self.cgain_disp, self.unit_scale)) N_patches += 1 if self.tbl_sel[2]: labels_f.append("1st Zero = {0:.4g}".format(self.first_zero_f)) N_patches += 1 if N_patches > 0: self.ax_f.legend([patch] * N_patches, labels_f, loc='best', fontsize='small', fancybox=True, framealpha=0.7, handlelength=0, handletextpad=0) np.seterr(**old_settings_seterr) self.update_info() self.redraw() #------------------------------------------------------------------------------ def update_info(self): """ Update the text info box for the window """ if 'info' in self.win_dict: self.txtInfoBox.setText(self.win_dict['info']) self._set_table_item(0, 0, "ENBW", font=self.bfont) #, sel=True) self._set_table_item(0, 1, "{0:.5g}".format(self.nenbw_disp)) self._set_table_item(0, 2, self.unit_nenbw) self._set_table_item(0, 3, "Scale", font=self.bfont) #, sel=True) self._set_table_item(0, 4, "{0:.5g}".format(self.cgain_disp)) self._set_table_item(0, 5, self.unit_scale) self._set_table_item(1, 0, "1st Zero", font=self.bfont) #, sel=True) self._set_table_item(1, 1, "{0:.5g}".format(self.first_zero_f)) self._set_table_item(1, 2, "f_S") self._set_table_item(1, 3, "Sidelobes", font=self.bfont) #, sel=True) self._set_table_item(1, 4, "{0:.5g}".format(self.sidelobe_level_disp)) self._set_table_item(1, 5, self.unit_scale) self.tblWinProperties.resizeColumnsToContents() self.tblWinProperties.resizeRowsToContents() #------------------------------------------------------------------------------ def redraw(self): """ Redraw the canvas when e.g. the canvas size has changed """ self.mplwidget.redraw() self.needs_redraw = False