From 84e4ebe358a26e876dc72b395ea4f325b3d47626 Mon Sep 17 00:00:00 2001 From: tsole0 Date: Fri, 21 Jun 2024 10:04:23 -0400 Subject: [PATCH 01/11] add error message when user does not enter model name --- src/sas/qtgui/Utilities/TabbedModelEditor.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/sas/qtgui/Utilities/TabbedModelEditor.py b/src/sas/qtgui/Utilities/TabbedModelEditor.py index 3d034cfe99..2b498c4e58 100644 --- a/src/sas/qtgui/Utilities/TabbedModelEditor.py +++ b/src/sas/qtgui/Utilities/TabbedModelEditor.py @@ -306,6 +306,10 @@ def updateFromPlugin(self): # get required filename filename = model['filename'] + if filename == "": + QtWidgets.QMessageBox.critical(self, "Plugin Error", "Please specify a filename.") + return + # check if file exists plugin_location = models.find_plugins_dir() full_path = os.path.join(plugin_location, filename) From b4c8f8c8aa74b7f7c559c50543da7411128bad9b Mon Sep 17 00:00:00 2001 From: tsole0 Date: Thu, 20 Jun 2024 12:11:48 -0400 Subject: [PATCH 02/11] Fixes pre-existing bug forcing user to re-enter default text for model file to generate --- src/sas/qtgui/Utilities/PluginDefinition.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sas/qtgui/Utilities/PluginDefinition.py b/src/sas/qtgui/Utilities/PluginDefinition.py index fb52ff3432..294dbf6ee3 100644 --- a/src/sas/qtgui/Utilities/PluginDefinition.py +++ b/src/sas/qtgui/Utilities/PluginDefinition.py @@ -63,6 +63,7 @@ def addWidgets(self): return y """ + self.model['func_text'] = text self.txtFunction.insertPlainText(text) self.txtFunction.setFont(GuiUtils.getMonospaceFont()) From bf3b57a782f9cc453c749826f38593d7705e9370 Mon Sep 17 00:00:00 2001 From: tsole0 Date: Fri, 21 Jun 2024 10:17:25 -0400 Subject: [PATCH 03/11] edit bug fix to use variable consistent with release version --- src/sas/qtgui/Utilities/PluginDefinition.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sas/qtgui/Utilities/PluginDefinition.py b/src/sas/qtgui/Utilities/PluginDefinition.py index 294dbf6ee3..7334b41c4f 100644 --- a/src/sas/qtgui/Utilities/PluginDefinition.py +++ b/src/sas/qtgui/Utilities/PluginDefinition.py @@ -63,7 +63,7 @@ def addWidgets(self): return y """ - self.model['func_text'] = text + self.model['text'] = text self.txtFunction.insertPlainText(text) self.txtFunction.setFont(GuiUtils.getMonospaceFont()) From 01b286cab05a22ac3b43ea86f8fcb2f1bebf67f8 Mon Sep 17 00:00:00 2001 From: tsole0 Date: Fri, 21 Jun 2024 11:23:12 -0400 Subject: [PATCH 04/11] bug fix: adds functionality to auto-delete unused rows --- src/sas/qtgui/Utilities/PluginDefinition.py | 28 +++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/sas/qtgui/Utilities/PluginDefinition.py b/src/sas/qtgui/Utilities/PluginDefinition.py index 7334b41c4f..4803ce9a1c 100644 --- a/src/sas/qtgui/Utilities/PluginDefinition.py +++ b/src/sas/qtgui/Utilities/PluginDefinition.py @@ -133,9 +133,21 @@ def onParamsChanged(self, row, column): self.model['parameters'] = self.parameter_dict # Check if the update was Value for last row. If so, add a new row - if column == 1 and row == self.tblParams.rowCount()-1: + if row == self.tblParams.rowCount() - 1: # Add a row self.tblParams.insertRow(self.tblParams.rowCount()) + + # Check to see if the last two rows are empty. If so, remove the last row + remove_last_row = 0 + for i in range(2): + for j in range(2): + cell_contents = self.tblParams.item(self.tblParams.rowCount() - (2 - i), j) + if cell_contents == None or cell_contents.text() == "": + remove_last_row += 1 + if remove_last_row == 4: + # If all four last cells are empty, remove the last row + self.tblParams.removeRow(self.tblParams.rowCount() - 1) + self.modelModified.emit() def onParamsPDChanged(self, row, column): @@ -153,9 +165,21 @@ def onParamsPDChanged(self, row, column): self.model['pd_parameters'] = self.pd_parameter_dict # Check if the update was Value for last row. If so, add a new row - if column == 1 and row == self.tblParamsPD.rowCount()-1: + if row == self.tblParamsPD.rowCount() - 1: # Add a row self.tblParamsPD.insertRow(self.tblParamsPD.rowCount()) + + # Check to see if the last two rows are empty. If so, remove the last row + remove_last_row = 0 + for i in range(2): + for j in range(2): + cell_contents = self.tblParamsPD.item(self.tblParamsPD.rowCount() - (2 - i), j) + if cell_contents == None or cell_contents.text() == "": + remove_last_row += 1 + if remove_last_row == 4: + # If all four last cells are empty, remove the last row + self.tblParamsPD.removeRow(self.tblParamsPD.rowCount() - 1) + self.modelModified.emit() From 558c7a1768ff27248fd8b78e8b3682f9a925d418 Mon Sep 17 00:00:00 2001 From: tsole0 Date: Mon, 24 Jun 2024 12:55:42 -0400 Subject: [PATCH 05/11] bug fix: ensure that both a syntax check and a model check is run on model file before approving it; condense into checkModel method --- src/sas/qtgui/Utilities/TabbedModelEditor.py | 70 +++++++++++++++----- 1 file changed, 52 insertions(+), 18 deletions(-) diff --git a/src/sas/qtgui/Utilities/TabbedModelEditor.py b/src/sas/qtgui/Utilities/TabbedModelEditor.py index 2b498c4e58..aef3f65ff5 100644 --- a/src/sas/qtgui/Utilities/TabbedModelEditor.py +++ b/src/sas/qtgui/Utilities/TabbedModelEditor.py @@ -195,7 +195,7 @@ def loadFile(self, filename): # Check the validity of loaded model if the model is python if self.is_python: - error_line = self.checkModel(plugin_text) + error_line = self.checkModel(plugin_text, run_unit_test=False) if error_line > 0: # select bad line cursor = QtGui.QTextCursor(self.editor_widget.txtEditor.document().findBlockByLineNumber(error_line-1)) @@ -329,8 +329,9 @@ def updateFromPlugin(self): # disable "Apply" self.buttonBox.button(QtWidgets.QDialogButtonBox.Apply).setEnabled(False) - # Run the model test in sasmodels - if not self.isModelCorrect(full_path): + # Run the model test in sasmodels and check model syntax. Returns error line if checks fail. + error_line = self.checkModel(full_path) + if error_line > 0: return self.editor_widget.setEnabled(True) @@ -352,7 +353,7 @@ def updateFromPlugin(self): self.parent.communicate.statusBarUpdateSignal.emit(msg) logging.info(msg) - def checkModel(self, model_str): + def checkModel(self, full_path, run_unit_test=True): """ Run the ast check and return True if the model is good. @@ -361,9 +362,13 @@ def checkModel(self, model_str): # successfulCheck = True error_line = 0 try: + with open(full_path, 'r', encoding="utf-8") as plugin: + model_str = plugin.read() ast.parse(model_str) + if run_unit_test: + model_check = GuiUtils.checkModel(full_path) - except SyntaxError as ex: + except Exception as ex: msg = "Error building model: " + str(ex) logging.error(msg) # print four last lines of the stack trace @@ -376,21 +381,43 @@ def checkModel(self, model_str): # Set the status bar message # GuiUtils.Communicate.statusBarUpdateSignal.emit("Model check failed") self.parent.communicate.statusBarUpdateSignal.emit("Model check failed") - # Put a thick, red border around the mini-editor - self.tabWidget.currentWidget().txtEditor.setStyleSheet("border: 5px solid red") - # last_lines = traceback.format_exc().split('\n')[-4:] - traceback_to_show = '\n'.join(last_lines) - self.tabWidget.currentWidget().txtEditor.setToolTip(traceback_to_show) + + # Remove the file so it is not being loaded on refresh + os.remove(full_path) + + # Put a thick, red border around the editor. + from sas.qtgui.Utilities.CodeEditor import QCodeEditor + + # Find all QTextBrowser and QCodeEditor children + text_browsers = self.tabWidget.currentWidget().findChildren(QtWidgets.QTextBrowser) + code_editors = self.tabWidget.currentWidget().findChildren(QCodeEditor) + + # Combine the lists and apply the stylesheet + for child in text_browsers + code_editors: + child.setStyleSheet("border: 5px solid red") + # last_lines = traceback.format_exc().split('\n')[-4:] + traceback_to_show = '\n'.join(last_lines) + child.setToolTip(traceback_to_show) + # attempt to find the failing command line number, usually the last line with # `File ... line` syntax - for line in reversed(all_lines): - if 'File' in line and 'line' in line: + reversed_error_text = list(reversed(all_lines)) + for line in reversed_error_text: + if ('File' in line and 'line' in line): + # If model check fails (not syntax) then 'line' and 'File' will be in adjacent lines error_line = re.split('line ', line)[1] try: error_line = int(error_line) break except ValueError: - error_line = 0 + # Sometimes the line number is followed by more text + try: + error_line = error_line.split(',')[0] + error_line = int(error_line) + break + except ValueError: + error_line = 0 + return error_line def isModelCorrect(self, full_path): @@ -437,13 +464,21 @@ def updateFromEditor(self): if not w.is_python: base, _ = os.path.splitext(filename) filename = base + '.c' - # make sure we have the file handle ready - assert(filename != "") + assert filename != "" + # Retrieve model string model_str = self.getModel()['text'] + # Save the file + self.writeFile(filename, model_str) + + # Get model filepath + plugin_location = models.find_plugins_dir() + full_path = os.path.join(plugin_location, filename) + if os.path.splitext(full_path)[1] != ".py": + full_path += ".py" if w.is_python and self.is_python: - error_line = self.checkModel(model_str) + error_line = self.checkModel(full_path) if error_line > 0: # select bad line cursor = QtGui.QTextCursor(w.txtEditor.document().findBlockByLineNumber(error_line-1)) @@ -453,8 +488,7 @@ def updateFromEditor(self): # change the frame colours back w.txtEditor.setStyleSheet("") w.txtEditor.setToolTip("") - # Save the file - self.writeFile(filename, model_str) + # Update the tab title self.setTabEdited(False) From 54a8aaf0677d3d85af631db70c684c7e483b362b Mon Sep 17 00:00:00 2001 From: tsole0 Date: Mon, 24 Jun 2024 13:03:11 -0400 Subject: [PATCH 06/11] remove isModelCorrect method whose functionality was incorporated into checkModel in previous commit --- src/sas/qtgui/Utilities/TabbedModelEditor.py | 35 -------------------- 1 file changed, 35 deletions(-) diff --git a/src/sas/qtgui/Utilities/TabbedModelEditor.py b/src/sas/qtgui/Utilities/TabbedModelEditor.py index aef3f65ff5..56ea211f19 100644 --- a/src/sas/qtgui/Utilities/TabbedModelEditor.py +++ b/src/sas/qtgui/Utilities/TabbedModelEditor.py @@ -420,41 +420,6 @@ def checkModel(self, full_path, run_unit_test=True): return error_line - def isModelCorrect(self, full_path): - """ - Run the sasmodels method for model check - and return True if the model is good. - False otherwise. - """ - successfulCheck = True - try: - model_results = GuiUtils.checkModel(full_path) - logging.info(model_results) - # We can't guarantee the type of the exception coming from - # Sasmodels, so need the overreaching general Exception - except Exception as ex: - msg = "Error building model: "+ str(ex) - logging.error(msg) - #print three last lines of the stack trace - # this will point out the exact line failing - last_lines = traceback.format_exc().split('\n')[-4:] - traceback_to_show = '\n'.join(last_lines) - logging.error(traceback_to_show) - - # Set the status bar message - self.parent.communicate.statusBarUpdateSignal.emit("Model check failed") - - # Remove the file so it is not being loaded on refresh - os.remove(full_path) - # Put a thick, red border around the mini-editor - self.plugin_widget.txtFunction.setStyleSheet("border: 5px solid red") - # Use the last line of the traceback for the tooltip - last_lines = traceback.format_exc().split('\n')[-2:] - traceback_to_show = '\n'.join(last_lines) - self.plugin_widget.txtFunction.setToolTip(traceback_to_show) - successfulCheck = False - return successfulCheck - def updateFromEditor(self): """ Save the current state of the Model Editor From 2b710229f4428f55c264e6f0a11629ef7fc87129 Mon Sep 17 00:00:00 2001 From: tsole0 Date: Mon, 24 Jun 2024 13:12:19 -0400 Subject: [PATCH 07/11] Update comments and remove unnecessary flag from method --- src/sas/qtgui/Utilities/TabbedModelEditor.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/sas/qtgui/Utilities/TabbedModelEditor.py b/src/sas/qtgui/Utilities/TabbedModelEditor.py index 56ea211f19..ad453091dd 100644 --- a/src/sas/qtgui/Utilities/TabbedModelEditor.py +++ b/src/sas/qtgui/Utilities/TabbedModelEditor.py @@ -195,7 +195,7 @@ def loadFile(self, filename): # Check the validity of loaded model if the model is python if self.is_python: - error_line = self.checkModel(plugin_text, run_unit_test=False) + error_line = self.checkModel(plugin_text) if error_line > 0: # select bad line cursor = QtGui.QTextCursor(self.editor_widget.txtEditor.document().findBlockByLineNumber(error_line-1)) @@ -353,11 +353,11 @@ def updateFromPlugin(self): self.parent.communicate.statusBarUpdateSignal.emit(msg) logging.info(msg) - def checkModel(self, full_path, run_unit_test=True): + def checkModel(self, full_path): """ - Run the ast check - and return True if the model is good. - False otherwise. + Run ast and model checks + Attempt to return the line number of the error if any + :param full_path: full path to the model file """ # successfulCheck = True error_line = 0 @@ -365,8 +365,7 @@ def checkModel(self, full_path, run_unit_test=True): with open(full_path, 'r', encoding="utf-8") as plugin: model_str = plugin.read() ast.parse(model_str) - if run_unit_test: - model_check = GuiUtils.checkModel(full_path) + GuiUtils.checkModel(full_path) except Exception as ex: msg = "Error building model: " + str(ex) From eb3dee07b6f80b311044349c25e3c1faa457d21e Mon Sep 17 00:00:00 2001 From: tsole0 Date: Wed, 26 Jun 2024 11:27:48 -0400 Subject: [PATCH 08/11] when saving C model, run model check and highlight C window if error with C code. Clear highlights from both python and C windows if checks pass. Ensure that checkModel() is always passed a path argument instead of raw text. --- src/sas/qtgui/Utilities/TabbedModelEditor.py | 24 ++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/sas/qtgui/Utilities/TabbedModelEditor.py b/src/sas/qtgui/Utilities/TabbedModelEditor.py index ad453091dd..f38208e452 100644 --- a/src/sas/qtgui/Utilities/TabbedModelEditor.py +++ b/src/sas/qtgui/Utilities/TabbedModelEditor.py @@ -195,7 +195,7 @@ def loadFile(self, filename): # Check the validity of loaded model if the model is python if self.is_python: - error_line = self.checkModel(plugin_text) + error_line = self.checkModel(self.filename) if error_line > 0: # select bad line cursor = QtGui.QTextCursor(self.editor_widget.txtEditor.document().findBlockByLineNumber(error_line-1)) @@ -439,9 +439,20 @@ def updateFromEditor(self): # Get model filepath plugin_location = models.find_plugins_dir() full_path = os.path.join(plugin_location, filename) - if os.path.splitext(full_path)[1] != ".py": + if not w.is_python and self.is_python: + pass + elif os.path.splitext(full_path)[1] != ".py": full_path += ".py" + + # Check model as long as there is a .py file in one of the tabs if w.is_python and self.is_python: + check_model = True + elif not w.is_python and self.is_python: + # Set full_path to the .py file so that we can run a model check on it (the .py model should link to the .c model) + full_path = self.filename.with_suffix(".py") + check_model = True + + if check_model: error_line = self.checkModel(full_path) if error_line > 0: # select bad line @@ -450,8 +461,13 @@ def updateFromEditor(self): return # change the frame colours back - w.txtEditor.setStyleSheet("") - w.txtEditor.setToolTip("") + try: + self.c_editor_widget.txtEditor.setStyleSheet("") + self.c_editor_widget.txtEditor.setToolTip("") + except AttributeError: + pass + self.editor_widget.txtEditor.setStyleSheet("") + self.editor_widget.txtEditor.setToolTip("") # Update the tab title self.setTabEdited(False) From 14d1e0af54aa20ec80ece1e79c4d19b3486f7ad5 Mon Sep 17 00:00:00 2001 From: tsole0 Date: Thu, 27 Jun 2024 12:13:22 -0400 Subject: [PATCH 09/11] fix bug that deletes .py file after a failed model check on a .c file by removing os.remove(). allow user to load .c models into editor even if .c file fails model checks --- src/sas/qtgui/Utilities/TabbedModelEditor.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/sas/qtgui/Utilities/TabbedModelEditor.py b/src/sas/qtgui/Utilities/TabbedModelEditor.py index f38208e452..f633c2c036 100644 --- a/src/sas/qtgui/Utilities/TabbedModelEditor.py +++ b/src/sas/qtgui/Utilities/TabbedModelEditor.py @@ -193,6 +193,10 @@ def loadFile(self, filename): # Name the tab with .py filename self.tabWidget.setTabText(0, display_name) + # In case previous model was incorrect, change the frame colours back + self.editor_widget.txtEditor.setStyleSheet("") + self.editor_widget.txtEditor.setToolTip("") + # Check the validity of loaded model if the model is python if self.is_python: error_line = self.checkModel(self.filename) @@ -200,11 +204,8 @@ def loadFile(self, filename): # select bad line cursor = QtGui.QTextCursor(self.editor_widget.txtEditor.document().findBlockByLineNumber(error_line-1)) self.editor_widget.txtEditor.setTextCursor(cursor) - return - - # In case previous model was incorrect, change the frame colours back - self.editor_widget.txtEditor.setStyleSheet("") - self.editor_widget.txtEditor.setToolTip("") + # Do not return because we still want to load C file if it exists + QtWidgets.QMessageBox.warning(self, "Model check failed", "The loaded model contains errors. Please correct all errors before using model.") # See if there is filename.c present c_path = self.filename.parent / self.filename.name.replace(".py", ".c") @@ -381,9 +382,6 @@ def checkModel(self, full_path): # GuiUtils.Communicate.statusBarUpdateSignal.emit("Model check failed") self.parent.communicate.statusBarUpdateSignal.emit("Model check failed") - # Remove the file so it is not being loaded on refresh - os.remove(full_path) - # Put a thick, red border around the editor. from sas.qtgui.Utilities.CodeEditor import QCodeEditor From 788433b32a39372a1d2b7501de929c2abc40502a Mon Sep 17 00:00:00 2001 From: tsole0 Date: Fri, 12 Jul 2024 14:47:29 -0400 Subject: [PATCH 10/11] fixes bug that causes font to change if all text in text editor deleted --- src/sas/qtgui/Utilities/UI/PluginDefinitionUI.ui | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/sas/qtgui/Utilities/UI/PluginDefinitionUI.ui b/src/sas/qtgui/Utilities/UI/PluginDefinitionUI.ui index 00b365b52b..0fa294b07e 100755 --- a/src/sas/qtgui/Utilities/UI/PluginDefinitionUI.ui +++ b/src/sas/qtgui/Utilities/UI/PluginDefinitionUI.ui @@ -133,10 +133,13 @@ value <!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"> +<html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:6.6pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:7.8pt;"><br /></p></body></html> +hr { height: 1px; border-width: 0; } +li.unchecked::marker { content: "\2610"; } +li.checked::marker { content: "\2612"; } +</style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Consolas'; font-size:11pt;"><br /></p></body></html> From 6ac6c455c548ab1ebe76ca3c18a4e86c4a07b70e Mon Sep 17 00:00:00 2001 From: krzywon Date: Tue, 29 Oct 2024 10:36:49 -0400 Subject: [PATCH 11/11] Abstract the empty row removal tool and apply to the PD and regular params in the model editor window --- src/sas/qtgui/Utilities/PluginDefinition.py | 61 ++++++++------------- 1 file changed, 24 insertions(+), 37 deletions(-) diff --git a/src/sas/qtgui/Utilities/PluginDefinition.py b/src/sas/qtgui/Utilities/PluginDefinition.py index 4803ce9a1c..dada924d69 100644 --- a/src/sas/qtgui/Utilities/PluginDefinition.py +++ b/src/sas/qtgui/Utilities/PluginDefinition.py @@ -5,12 +5,25 @@ from sas.qtgui.Utilities.UI.PluginDefinitionUI import Ui_PluginDefinition from sas.qtgui.Utilities import GuiUtils -# txtName -# txtDescription -# chkOverwrite -# tblParams -# tblParamsPD -# txtFunction + +def remove_empty_table_rows(tbl: QtWidgets.QTableWidget): + """A helper function to remove empty rows in a PySide Table, if there are more than two empty rows at the end. + This function ensures there is always an empty row in the table. + + :param tbl: A QTableWidget on i rows and j columns. + """ + for i in range(0, tbl.rowCount()): + for j in range(0, tbl.columnCount()): + cell_contents = tbl.item(i, j) + if cell_contents is None or cell_contents.text() == "": + # cell_contents may be None: Ensure this check remains, otherwise the .text() call may throw an error + pass + else: + break + else: + tbl.removeRow(i) + tbl.insertRow(tbl.rowCount()) + class PluginDefinition(QtWidgets.QDialog, Ui_PluginDefinition): """ @@ -132,27 +145,14 @@ def onParamsChanged(self, row, column): self.parameter_dict[row] = (param, value) self.model['parameters'] = self.parameter_dict - # Check if the update was Value for last row. If so, add a new row - if row == self.tblParams.rowCount() - 1: - # Add a row - self.tblParams.insertRow(self.tblParams.rowCount()) - - # Check to see if the last two rows are empty. If so, remove the last row - remove_last_row = 0 - for i in range(2): - for j in range(2): - cell_contents = self.tblParams.item(self.tblParams.rowCount() - (2 - i), j) - if cell_contents == None or cell_contents.text() == "": - remove_last_row += 1 - if remove_last_row == 4: - # If all four last cells are empty, remove the last row - self.tblParams.removeRow(self.tblParams.rowCount() - 1) + # Check if there are empty rows. + remove_empty_table_rows(self.tblParams) self.modelModified.emit() def onParamsPDChanged(self, row, column): """ - Respond to changes in non-polydisperse parameter table + Respond to changes in polydisperse parameter table """ param = value = None if self.tblParamsPD.item(row, 0): @@ -164,21 +164,8 @@ def onParamsPDChanged(self, row, column): self.pd_parameter_dict[row] = (param, value) self.model['pd_parameters'] = self.pd_parameter_dict - # Check if the update was Value for last row. If so, add a new row - if row == self.tblParamsPD.rowCount() - 1: - # Add a row - self.tblParamsPD.insertRow(self.tblParamsPD.rowCount()) - - # Check to see if the last two rows are empty. If so, remove the last row - remove_last_row = 0 - for i in range(2): - for j in range(2): - cell_contents = self.tblParamsPD.item(self.tblParamsPD.rowCount() - (2 - i), j) - if cell_contents == None or cell_contents.text() == "": - remove_last_row += 1 - if remove_last_row == 4: - # If all four last cells are empty, remove the last row - self.tblParamsPD.removeRow(self.tblParamsPD.rowCount() - 1) + # Check if there are empty rows. + remove_empty_table_rows(self.tblParamsPD) self.modelModified.emit()