Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • software_public/passoft/sohstationviewer
1 result
Show changes
Commits on Source (10)
Showing
with 354 additions and 405 deletions
......@@ -17,7 +17,7 @@ requirements:
- python >=3.9
- numpy>=1.23.0
- obspy >=1.3.0
- PySide2
- PySide6>=6.5.2
- matplotlib>=3.5.0
test:
......
......@@ -34,7 +34,7 @@ setup(
install_requires=[
'numpy>=1.23.0',
'obspy>=1.3.0',
'PySide2',
'PySide6>=6.5.2',
'matplotlib>=3.5.0',
],
setup_requires=[],
......
import configparser
from pathlib import Path
from PySide2 import QtCore
from PySide6 import QtCore
from sohstationviewer.conf import constants
from sohstationviewer.conf.constants import CONFIG_PATH
......
......@@ -9,9 +9,9 @@ import re
from pathlib import Path
from typing import List, Optional, Dict, Tuple, Union, BinaryIO
from PySide2.QtCore import QEventLoop, Qt
from PySide2.QtGui import QCursor
from PySide2.QtWidgets import QTextBrowser, QApplication
from PySide6.QtCore import QEventLoop, Qt
from PySide6.QtGui import QCursor
from PySide6.QtWidgets import QTextBrowser, QApplication
from obspy.io import reftek
from obspy import UTCDateTime
......
......@@ -9,8 +9,8 @@ from datetime import datetime
from pathlib import Path
from typing import Tuple, List, Union, Dict
from PySide2 import QtCore
from PySide2.QtWidgets import QTextBrowser
from PySide6 import QtCore
from PySide6.QtWidgets import QTextBrowser
from obspy import UTCDateTime
......
......@@ -2,20 +2,22 @@
basic executing database functions
"""
import sqlite3
from typing import Sequence, Union
from sohstationviewer.conf.dbSettings import dbConf
def execute_db(sql):
def execute_db(sql: str, parameters: Union[dict, Sequence] = ()):
"""
Execute or fetch data from DB
:param sql: str - request string to execute or fetch data from database
:param parameters: the parameters used for executing parameterized queries
:return rows: [(str/int,] - result of querying DB
"""
conn = sqlite3.connect(dbConf['dbpath'])
cur = conn.cursor()
try:
cur.execute(sql)
cur.execute(sql, parameters)
except sqlite3.OperationalError as e:
print("sqlite3.OperationalError:%s\n\tSQL: %s" % (str(e), sql))
rows = cur.fetchall()
......
#!/usr/bin/env python3
import platform
import os
import sys
import traceback
from pathlib import Path
from PySide2 import QtWidgets
from PySide2.QtGui import QGuiApplication
from PySide2.QtWidgets import QMessageBox
from PySide6 import QtWidgets
from PySide6.QtWidgets import QMessageBox
from sohstationviewer.view.main_window import MainWindow
from sohstationviewer.conf.config_processor import (
......@@ -15,16 +13,6 @@ from sohstationviewer.conf.config_processor import (
BadConfigError,
)
# Enable Layer-backing for MacOs version >= 11
# Only needed if using the pyside2 library with version>=5.15.
# Layer-backing is always enabled in pyside6.
os_name, version, *_ = platform.platform().split('-')
# if os_name == 'macOS' and version >= '11':
# mac OSX 11.6 appear to be 10.16 when read with python and still required this
# environment variable
if os_name == 'macOS':
os.environ['QT_MAC_WANTS_LAYER'] = '1'
def fix_relative_paths() -> None:
"""
......@@ -74,12 +62,12 @@ def check_if_user_want_to_reset_config() -> bool:
bad_config_dialog.setDetailedText(traceback.format_exc())
bad_config_dialog.setInformativeText('Do you want to reset the config '
'file?')
bad_config_dialog.setStandardButtons(QMessageBox.Ok |
QMessageBox.Close)
bad_config_dialog.setDefaultButton(QMessageBox.Ok)
bad_config_dialog.setIcon(QMessageBox.Critical)
reset_choice = bad_config_dialog.exec_()
return reset_choice == QMessageBox.Ok
bad_config_dialog.setStandardButtons(QMessageBox.StandardButton.Ok |
QMessageBox.StandardButton.Close)
bad_config_dialog.setDefaultButton(QMessageBox.StandardButton.Ok)
bad_config_dialog.setIcon(QMessageBox.Icon.Critical)
reset_choice = bad_config_dialog.exec()
return reset_choice == QMessageBox.StandardButton.Ok
def main():
......@@ -104,7 +92,7 @@ def main():
QMessageBox.critical(None, 'Cannot reset config',
'Config file cannot be reset. Please ensure '
'that it is not opened in another program.',
QMessageBox.Close)
QMessageBox.StandardButton.Close)
sys.exit(1)
elif do_reset is not None:
sys.exit(1)
......@@ -112,7 +100,7 @@ def main():
resize_windows(wnd)
wnd.show()
sys.exit(app.exec_())
sys.exit(app.exec())
if __name__ == '__main__':
......
......@@ -5,7 +5,8 @@ import traceback
from pathlib import Path
from typing import Union, List, Optional
from PySide2 import QtCore, QtWidgets
from PySide6.QtCore import Qt
from PySide6 import QtCore, QtWidgets
from sohstationviewer.conf import constants
from sohstationviewer.controller.util import display_tracking_info
......@@ -84,7 +85,7 @@ class DataLoaderWorker(QtCore.QObject):
# its unpause slot to the loader's unpause signal
data_object = object_type.get_empty_instance()
self.button_chosen.connect(data_object.receive_pause_response,
type=QtCore.Qt.DirectConnection)
type=Qt.ConnectionType.DirectConnection)
data_object.__init__(
self.data_type, self.tracking_box,
self.is_multiplex, self.list_of_dir,
......
......@@ -6,8 +6,8 @@ import traceback
from obspy import UTCDateTime
from PySide2 import QtCore
from PySide2 import QtWidgets
from PySide6 import QtCore
from PySide6 import QtWidgets
from sohstationviewer.controller.util import \
display_tracking_info, get_valid_file_count, validate_file, validate_dir
......
from PySide2 import QtWidgets
from PySide6 import QtWidgets
from sohstationviewer.view.ui.calendar_ui_qtdesigner import Ui_CalendarDialog
......
from PySide2 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
class CalendarWidget(QtWidgets.QCalendarWidget):
......@@ -43,7 +43,8 @@ class CalendarWidget(QtWidgets.QCalendarWidget):
self.toggle_day_of_year.setCheckable(True)
palette = self.toggle_day_of_year.palette()
palette.setColor(QtGui.QPalette.WindowText, QtGui.QColor('white'))
palette.setColor(QtGui.QPalette.ColorRole.WindowText,
QtGui.QColor('white'))
self.toggle_day_of_year.setPalette(palette)
self.toggle_day_of_year.show()
......
from typing import Dict, List, Union
from pathlib import Path
from PySide2 import QtWidgets, QtCore
from PySide2.QtWidgets import QDialogButtonBox, QDialog, QPlainTextEdit, \
from PySide6 import QtWidgets, QtCore
from PySide6.QtWidgets import QDialogButtonBox, QDialog, QPlainTextEdit, \
QMainWindow
from sohstationviewer.database.process_db import (
......@@ -35,7 +35,10 @@ class InputDialog(QDialog):
self.text_box.setPlainText(text)
button_box = QDialogButtonBox(
QDialogButtonBox.Ok | QDialogButtonBox.Cancel, self)
QDialogButtonBox.StandardButton.Ok |
QDialogButtonBox.StandardButton.Cancel,
self
)
layout = QtWidgets.QFormLayout(self)
layout.addRow(self.text_box)
......@@ -230,8 +233,12 @@ class ChannelPreferDialog(OneWindowAtATimeDialog):
'Edit', 'Clear']
self.soh_list_table_widget.setHorizontalHeaderLabels(col_headers)
header = self.soh_list_table_widget.horizontalHeader()
header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
header.setSectionResizeMode(3, QtWidgets.QHeaderView.Stretch)
header.setSectionResizeMode(
QtWidgets.QHeaderView.ResizeMode.ResizeToContents
)
header.setSectionResizeMode(
3, QtWidgets.QHeaderView.ResizeMode.Stretch
)
self.soh_list_table_widget.setRowCount(TOTAL_ROW)
self.avail_data_types = self.get_data_types()
......@@ -380,8 +387,9 @@ class ChannelPreferDialog(OneWindowAtATimeDialog):
f"#{row_idx + 1}?")
result = QtWidgets.QMessageBox.question(
self, "Confirmation", msg,
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
if result == QtWidgets.QMessageBox.No:
QtWidgets.QMessageBox.StandardButton.Yes |
QtWidgets.QMessageBox.StandardButton.No)
if result == QtWidgets.QMessageBox.StandardButton.No:
return
self.changed = True
if soh_list_name_item.text() != '':
......@@ -454,8 +462,10 @@ class ChannelPreferDialog(OneWindowAtATimeDialog):
"Do you want to add channels from DB?")
result = QtWidgets.QMessageBox.question(
self, "Add Channels from DB for RT130", msg,
QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel)
if result == QtWidgets.QMessageBox.Ok:
QtWidgets.QMessageBox.StandardButton.Ok |
QtWidgets.QMessageBox.StandardButton.Cancel
)
if result == QtWidgets.QMessageBox.StandardButton.Ok:
self.add_db_channels()
else:
self.scan_chan_btn.setEnabled(True)
......@@ -546,8 +556,9 @@ class ChannelPreferDialog(OneWindowAtATimeDialog):
"database.")
result = QtWidgets.QMessageBox.question(
self, "Confirmation", msg,
QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel)
if result == QtWidgets.QMessageBox.Cancel:
QtWidgets.QMessageBox.StandardButton.Ok |
QtWidgets.QMessageBox.StandardButton.Cancel)
if result == QtWidgets.QMessageBox.StandardButton.Cancel:
return False
sql_list = []
for row_idx in range(TOTAL_ROW):
......
from typing import List
from PySide2 import QtWidgets
from PySide6 import QtWidgets
def create_multi_buttons_dialog(
......@@ -28,12 +28,15 @@ def create_multi_buttons_dialog(
# reasons.
label = str(label).replace("'", '').replace('"', '')
buttons.append(
msg_box.addButton(label, QtWidgets.QMessageBox.ActionRole)
msg_box.addButton(label,
QtWidgets.QMessageBox.ButtonRole.ActionRole)
)
if has_abort:
abort_button = msg_box.addButton(QtWidgets.QMessageBox.Abort)
abort_button = msg_box.addButton(
QtWidgets.QMessageBox.StandardButton.Abort
)
msg_box.exec_()
msg_box.exec()
try:
if msg_box.clickedButton() == abort_button:
return -1
......
from PySide2.QtCore import Qt, QDate
from PySide2.QtGui import (
QKeyEvent, QWheelEvent, QContextMenuEvent
from PySide6.QtCore import Qt, QDate
from PySide6.QtGui import (
QKeyEvent, QWheelEvent, QContextMenuEvent, QAction,
)
from PySide2.QtWidgets import (
QDateEdit, QLineEdit, QMenu, QAction,
from PySide6.QtWidgets import (
QDateEdit, QLineEdit, QMenu
)
......
......@@ -3,8 +3,8 @@ import platform
import os
from typing import Optional, Dict
from PySide2 import QtWidgets, QtGui
from PySide2.QtWidgets import QWidget, QDialog
from PySide6 import QtWidgets, QtGui
from PySide6.QtWidgets import QWidget, QDialog
from sohstationviewer.database.process_db import execute_db
from sohstationviewer.database.extract_data import (
......@@ -23,7 +23,8 @@ def add_separation_line(layout):
:param layout: QLayout - the layout that contains the line
"""
label = QtWidgets.QLabel()
label.setFrameStyle(QtWidgets.QFrame.HLine | QtWidgets.QFrame.Sunken)
label.setFrameStyle(QtWidgets.QFrame.Shape.HLine |
QtWidgets.QFrame.Shadow.Sunken)
label.setLineWidth(1)
layout.addWidget(label)
......@@ -73,7 +74,7 @@ class AddEditSingleChannelDialog(QDialog):
"to convert from count to actual value"
)
validator = QtGui.QDoubleValidator(0.0, 5.0, 6)
validator.setNotation(QtGui.QDoubleValidator.StandardNotation)
validator.setNotation(QtGui.QDoubleValidator.Notation.StandardNotation)
self.conversion_lnedit.setValidator(validator)
self.conversion_lnedit.setText('1')
......@@ -216,8 +217,10 @@ class AddEditSingleChannelDialog(QDialog):
f"'{self.chan_id}'?")
result = QtWidgets.QMessageBox.question(
self, "Confirmation", msg,
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
if result == QtWidgets.QMessageBox.No:
QtWidgets.QMessageBox.StandardButton.Yes |
QtWidgets.QMessageBox.StandardButton.No
)
if result == QtWidgets.QMessageBox.StandardButton.No:
self.param_changed_by_signal = True
self.param_cbobox.setCurrentText(self.param)
return
......@@ -274,11 +277,12 @@ class AddEditSingleChannelDialog(QDialog):
"Are you sure you want to continue?")
result = QtWidgets.QMessageBox.question(
self, "Confirmation", msg,
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
if result == QtWidgets.QMessageBox.No:
QtWidgets.QMessageBox.StandardButton.Yes |
QtWidgets.QMessageBox.StandardButton.No)
if result == QtWidgets.QMessageBox.StandardButton.No:
return
win = EditSingleParamDialog(self, self.param_cbobox.currentText())
win.exec_()
win.exec()
def update_para_info(self, param):
"""
......@@ -347,5 +351,5 @@ if __name__ == '__main__':
# test TriColorLInes. Ex: param. Ex: param:Error/warning
# test = AddEditSingleChannelDialog(None, 'Error/Warning', 'RT130')
test.exec_()
sys.exit(app.exec_())
test.exec()
sys.exit(app.exec())
......@@ -2,7 +2,7 @@
channel_dialog.py
GUI to add/edit/remove channels
"""
from PySide2.QtWidgets import QMessageBox
from PySide6.QtWidgets import QMessageBox
from sohstationviewer.view.db_config.db_config_dialog import UiDBInfoDialog
from sohstationviewer.database.process_db import execute_db
......@@ -22,10 +22,18 @@ class ChannelDialog(UiDBInfoDialog):
parent, ['No.', 'Channel', 'Label', 'Param',
'ConvertFactor', 'Unit', 'FixPoint'],
'channel', 'channels', resize_content_columns=[0, 4, 5, 6],
need_data_type_choice=True, required_columns={3: 'Param'},
need_data_type_choice=True, required_columns={2: 'Param'},
check_fk=False)
self.delete_sql_supplement = f" AND dataType='{self.data_type}'"
self.setWindowTitle("Edit/Add/Delete Channels")
self.insert_sql_template = (f"INSERT INTO Channels VALUES"
f"(?, ?, ?, '', ?, ?, ?, "
f"'{self.data_type}')")
self.update_sql_template = (f"UPDATE Channels SET channel=?, "
f"label=?, param=?, convertFactor=?, "
f"unit=?, fixPoint=? "
f"WHERE channel='%s' "
f"AND dataType='{self.data_type}'")
def update_data_table_widget_items(self):
"""
......@@ -57,16 +65,35 @@ class ChannelDialog(UiDBInfoDialog):
row to be deleted
"""
self.add_widget(None, row_idx, 0) # No.
self.add_widget(self.data_list, row_idx, 1, foreign_key=fk) # chanID
self.add_widget(self.data_list, row_idx, 2) # label
self.add_widget(self.data_list, row_idx, 3, choices=self.param_choices)
self.add_widget(self.data_list, row_idx, 4,
self.add_widget(self.database_rows, row_idx, 1,
foreign_key=fk) # chanID
self.add_widget(self.database_rows, row_idx, 2) # label
self.add_widget(self.database_rows, row_idx, 3,
choices=self.param_choices)
self.add_widget(self.database_rows, row_idx, 4,
field_name='convertFactor')
self.add_widget(self.data_list, row_idx, 5) # unit
self.add_widget(self.data_list, row_idx, 6,
self.add_widget(self.database_rows, row_idx, 5) # unit
self.add_widget(self.database_rows, row_idx, 6,
range_values=[0, 5]) # fixPoint
self.add_delete_button_to_row(row_idx, fk)
def get_data_type_from_selector(self):
"""
Update the dialog with the new data type. Also update some other
attributes of the dialog affected by the data type.
:return:
"""
old_data_type = self.data_type
self.data_type = self.data_type_combo_box.currentText()
self.update_data_table_widget_items()
self.delete_sql_supplement = f" AND dataType='{self.data_type}'"
self.insert_sql_template = self.insert_sql_template.replace(
old_data_type, self.data_type
)
self.update_sql_template = self.update_sql_template.replace(
old_data_type, self.data_type
)
def data_type_changed(self):
"""
Method called when the data type of the data table is changed.
......@@ -85,9 +112,7 @@ class ChannelDialog(UiDBInfoDialog):
return
if not self.has_changes():
self.data_type = self.data_type_combo_box.currentText()
self.update_data_table_widget_items()
self.delete_sql_supplement = f" AND dataType='{self.data_type}'"
self.get_data_type_from_selector()
return
unsaved_changes_prompt = (
......@@ -95,17 +120,18 @@ class ChannelDialog(UiDBInfoDialog):
'Are you sure you want to change the data type? '
'Any changes you made will not be saved?'
)
options = QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel
options = (QMessageBox.StandardButton.Save |
QMessageBox.StandardButton.Discard |
QMessageBox.StandardButton.Cancel)
user_choice = QMessageBox.warning(self, 'Unsaved Changes',
unsaved_changes_prompt, options)
if user_choice != QMessageBox.Cancel:
if user_choice == QMessageBox.Save:
if user_choice != QMessageBox.StandardButton.Cancel:
if user_choice == QMessageBox.StandardButton.Save:
self.save_changes(need_confirmation=False)
elif user_choice == QMessageBox.Discard:
self.undo_all_deletes()
self.data_type = self.data_type_combo_box.currentText()
self.update_data_table_widget_items()
self.delete_sql_supplement = f" AND dataType='{self.data_type}'"
elif user_choice == QMessageBox.StandardButton.Discard:
self.untrack_changes()
pass
self.get_data_type_from_selector()
else:
# Cover both the case where the cancel button is pressed and the
# case where the exit button is pressed.
......@@ -153,26 +179,3 @@ class ChannelDialog(UiDBInfoDialog):
for i in range(remove_row_idx, self.data_table_widget.rowCount()):
cell_widget = self.data_table_widget.cellWidget(i, 0)
cell_widget.setText(str(i))
def update_data(self, row, widget_idx, list_idx):
"""
Prepare insert, update queries and additional condition for common
delete query then update data of a row from
self.data_table_widgets' content.
:param row: list - data of a row
:param widget_idx: index of row in self.data_table_widgets
:param list_idx: index of row in self.data_list
"""
insert_sql = (f"INSERT INTO Channels VALUES"
f"('{row[0]}', '{row[1]}', '{row[2]}',"
f" {row[3]}, '{row[4]}', {row[5]}, '{self.data_type}')")
update_sql = (f"UPDATE Channels SET channel='{row[0]}', "
f"label='{row[1]}', param='{row[2]}', "
f"convertFactor={row[3]}, unit='{row[4]}', "
f"fixPoint={row[5]} "
f"WHERE channel='%s'"
f" AND dataType='{self.data_type}'")
del_sql_add = f" AND dataType='{self.data_type}'"
return super().update_data(
row, widget_idx, list_idx, insert_sql, update_sql, del_sql_add)
......@@ -13,6 +13,9 @@ class DataTypeDialog(UiDBInfoDialog):
super().__init__(parent, ['No.', 'DataType'], 'dataType', 'dataTypes',
resize_content_columns=[0])
self.setWindowTitle("Edit/Add/Delete DataTypes")
self.insert_sql_template = "INSERT INTO DataTypes VALUES(?)"
self.update_sql_template = ("UPDATE DataTypes SET dataType=? "
"WHERE dataType='%s'")
def set_row_widgets(self, row_idx, fk=False):
"""
......@@ -23,7 +26,7 @@ class DataTypeDialog(UiDBInfoDialog):
row to be deleted
"""
self.add_widget(None, row_idx, 0) # No.
self.add_widget(self.data_list, row_idx, 1, foreign_key=fk)
self.add_widget(self.database_rows, row_idx, 1, foreign_key=fk)
self.add_delete_button_to_row(row_idx, fk)
def get_data_list(self):
......@@ -41,19 +44,3 @@ class DataTypeDialog(UiDBInfoDialog):
:param row_idx: index of row
"""
return [self.data_table_widget.cellWidget(row_idx, 1).text().strip()]
def update_data(self, row, widget_idx, list_idx):
"""
Prepare insert, update queries then update data of a row from
self.data_table_widgets' content.
:param row: list - data of a row
:param widget_idx: index of row in self.data_table_widgets
:param list_idx: index of row in self.data_list
"""
insert_sql = f"INSERT INTO DataTypes VALUES('{row[0]}')"
update_sql = (f"UPDATE DataTypes SET dataType='{row[0]}' "
f"WHERE dataType='%s'")
return super().update_data(
row, widget_idx, list_idx, insert_sql, update_sql)
from __future__ import annotations
from typing import Set, Dict, Optional
from PySide2 import QtWidgets, QtGui, QtCore
from PySide2.QtGui import QCloseEvent
from PySide2.QtWidgets import QMessageBox
from typing import Dict, Optional, List
from PySide6 import QtWidgets, QtGui, QtCore
from PySide6.QtGui import QCloseEvent
from PySide6.QtWidgets import QMessageBox, QWidget
from sohstationviewer.database.process_db import execute_db
from sohstationviewer.view.util.one_instance_at_a_time import \
......@@ -38,29 +39,36 @@ def set_widget_color(widget, changed=False, read_only=False):
if read_only:
# grey text
palette.setColor(QtGui.QPalette.Text, QtGui.QColor(100, 100, 100))
palette.setColor(QtGui.QPalette.ColorRole.Text,
QtGui.QColor(100, 100, 100))
# light blue background
palette.setColor(QtGui.QPalette.Base, QtGui.QColor(210, 240, 255))
palette.setColor(QtGui.QPalette.ColorRole.Base,
QtGui.QColor(210, 240, 255))
widget.setReadOnly(True)
widget.setPalette(palette)
return
else:
# white background
palette.setColor(QtGui.QPalette.Base, QtGui.QColor(255, 255, 255))
palette.setColor(QtGui.QPalette.ColorRole.Base,
QtGui.QColor(255, 255, 255))
if changed:
# red text
palette.setColor(QtGui.QPalette.Text, QtGui.QColor(255, 0, 0))
palette.setColor(QtGui.QPalette.ColorRole.Text,
QtGui.QColor(255, 0, 0))
else:
try:
if widget.isReadOnly():
# grey text
palette.setColor(
QtGui.QPalette.Text, QtGui.QColor(100, 100, 100))
QtGui.QPalette.ColorRole.Text,
QtGui.QColor(100, 100, 100))
else:
# black text
palette.setColor(QtGui.QPalette.Text, QtGui.QColor(0, 0, 0))
palette.setColor(QtGui.QPalette.ColorRole.Text,
QtGui.QColor(0, 0, 0))
except AttributeError:
palette.setColor(QtGui.QPalette.Text, QtGui.QColor(0, 0, 0))
palette.setColor(QtGui.QPalette.ColorRole.Text,
QtGui.QColor(0, 0, 0))
widget.setPalette(palette)
......@@ -110,21 +118,28 @@ class UiDBInfoDialog(OneWindowAtATimeDialog):
# ========================== internal data ===========================
"""
data_list: list of entries in the database table.
database_data: list of entries in the database.
"""
self.data_list = []
self.database_rows = []
"""
changes_by_rowid: dict of changes by row ids
is_row_changed_array: a bit array that store whether a row is changed.
"""
self.changes_by_rowid: Dict[int, Set[int]] = {}
self.is_row_changed_array: List[bool] = []
"""
queued_row_delete_sqls: a dictionary of delete SQL statements that are
executed when changes are saved to the database. Map a row id in the
data table to each SQL statement to make syncing deletes in the data
table and in the database easier.
table and deletes in the database easier.
"""
self.queued_row_delete_sqls: Dict[int, str] = {}
"""
insert_sql_template, update_sql_template: the parameterized SQL
statements used to insert/update rows in the database. Should be
defined in any child class that supports editing the database.
"""
self.insert_sql_template = ''
self.update_sql_template = ''
"""
delete_sql_supplement: the extension added to the sql statements used
to delete rows from the database. Should be defined in a child
class when needed.
......@@ -214,7 +229,9 @@ class UiDBInfoDialog(OneWindowAtATimeDialog):
if field_name == 'convertFactor':
# precision=6
validator = QtGui.QDoubleValidator(0.0, 5.0, 6)
validator.setNotation(QtGui.QDoubleValidator.StandardNotation)
validator.setNotation(
QtGui.QDoubleValidator.Notation.StandardNotation
)
widget.setValidator(validator)
if text == '':
widget.setText('1')
......@@ -241,18 +258,17 @@ class UiDBInfoDialog(OneWindowAtATimeDialog):
else:
new = False if row_idx < len(data_list) else True
set_widget_color(widget, read_only=False, changed=new)
change_signal = None
if choices is None and range_values is None:
widget.textChanged.connect(
lambda changed_text:
self.cell_input_change(changed_text, row_idx, col_idx))
change_signal = widget.textChanged
elif choices:
widget.currentTextChanged.connect(
lambda changed_text:
self.cell_input_change(changed_text, row_idx, col_idx))
change_signal = widget.currentTextChanged
elif range_values:
widget.valueChanged.connect(
lambda changed_text:
self.cell_input_change(changed_text, row_idx, col_idx))
change_signal = widget.valueChanged
change_signal.connect(
lambda changed_text:
self.on_cell_input_change(changed_text, widget)
)
self.data_table_widget.setCellWidget(row_idx, col_idx, widget)
if field_name == 'description':
self.data_table_widget.resizeRowToContents(row_idx)
......@@ -294,7 +310,10 @@ class UiDBInfoDialog(OneWindowAtATimeDialog):
self.save_changes_btn = QtWidgets.QPushButton(
self, text='SAVE CHANGES')
self.save_changes_btn.setFixedWidth(300)
self.save_changes_btn.clicked.connect(self.save_changes)
# The clicked signal of buttons emit a single argument with default
# value of False. This cause self.save_changes to be called with
# an argument, which is not what we want in this situation.
self.save_changes_btn.clicked.connect(lambda: self.save_changes())
h_layout.addWidget(self.save_changes_btn)
self.close_btn = QtWidgets.QPushButton(self, text='CLOSE')
......@@ -313,16 +332,16 @@ class UiDBInfoDialog(OneWindowAtATimeDialog):
# deleted a row in the data table will be selectable, which doesn't
# look too good.
self.data_table_widget.setSelectionMode(
QtWidgets.QAbstractItemView.NoSelection
QtWidgets.QAbstractItemView.SelectionMode.NoSelection
)
self.data_table_widget.verticalHeader().hide()
self.data_table_widget.setColumnCount(self.total_col)
self.data_table_widget.setHorizontalHeaderLabels(self.column_headers)
header = self.data_table_widget.horizontalHeader()
header.setSectionResizeMode(QtWidgets.QHeaderView.Stretch)
header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeMode.Stretch)
for i in self.resize_content_columns:
header.setSectionResizeMode(
i, QtWidgets.QHeaderView.ResizeToContents)
i, QtWidgets.QHeaderView.ResizeMode.ResizeToContents)
if self.need_data_type_choice:
self.data_type = self.data_type_combo_box.currentText()
......@@ -346,14 +365,21 @@ class UiDBInfoDialog(OneWindowAtATimeDialog):
If self.data_list is empty, call clear_first_row to create a row with
all necessary widget cells but empty.
"""
self.data_list = self.get_data_list()
row_count = len(self.data_list)
# When it comes to deleted rows, we can either reset them manually or
# remove them altogether. We chose to remove them using this line
# because it is the cleanest way to do it, and the performance does not
# matter because we don't really expect to show more than 100 channels
# at a time.
self.data_table_widget.setRowCount(0)
self.database_rows = self.get_data_list()
self.is_row_changed_array = [False] * len(self.database_rows)
row_count = len(self.database_rows)
row_count = 1 if row_count == 0 else row_count
self.data_table_widget.setRowCount(row_count)
for i in range(len(self.data_list)):
fk = self.check_data_foreign_key(self.data_list[i][0])
for i in range(len(self.database_rows)):
fk = self.check_data_foreign_key(self.database_rows[i][0])
self.set_row_widgets(i, fk)
if len(self.data_list) == 0:
if len(self.database_rows) == 0:
"""
No Row, should leave 1 empty row
"""
......@@ -373,6 +399,7 @@ class UiDBInfoDialog(OneWindowAtATimeDialog):
self.data_table_widget.scrollToBottom()
self.data_table_widget.repaint() # to show row's header
self.data_table_widget.cellWidget(row_position, 1).setFocus()
self.is_row_changed_array.append(True)
def remove_row(self, remove_row_idx):
"""
......@@ -386,32 +413,27 @@ class UiDBInfoDialog(OneWindowAtATimeDialog):
cell_widget.setText(str(i))
@QtCore.Slot()
def cell_input_change(self, changed_text, row_idx, col_idx):
def on_cell_input_change(self, changed_text: str,
changed_cell_widget: QWidget):
"""
If cell's value is changed, text's color will be red,
otherwise, text's color will be black
:param changed_text: new value of the cell widget
:param row_idx: row index of the cell widget
:param col_idx: column index of the cell widget
:param changed_cell_widget: the widget whose content was changed
"""
widget_x, widget_y = changed_cell_widget.pos().toTuple()
col_idx = self.data_table_widget.columnAt(widget_x)
row_idx = self.data_table_widget.rowAt(widget_y)
changed = False
if row_idx < len(self.data_list):
if changed_text != self.data_list[row_idx][col_idx - 1]:
if row_idx < len(self.database_rows):
if changed_text != self.database_rows[row_idx][col_idx - 1]:
changed = True
cell_widget = self.data_table_widget.cellWidget(row_idx, col_idx)
set_widget_color(cell_widget, changed=changed)
else:
changed = True
if changed:
if row_idx not in self.changes_by_rowid:
self.changes_by_rowid[row_idx] = set()
self.changes_by_rowid[row_idx].add(col_idx - 1) # skip order col
else:
try:
self.changes_by_rowid[row_idx].remove(col_idx - 1)
except KeyError:
pass
self.is_row_changed_array[row_idx] = changed
def check_data_foreign_key(self, val):
"""
......@@ -431,44 +453,6 @@ class UiDBInfoDialog(OneWindowAtATimeDialog):
else:
return False
def reset_row_inputs(self, reset, widget_idx, list_idx):
"""
Reset the value based on reset's value and the status of the
cell_widget
If there is a fk constrain, the read_only flag is True. The cell cannot
be changed. Value should remain the same, color should remain
grey/blue).
If read_only flag is False the color of the widget will be set to
black/white
If reset=1, value at the cell widget will be reset to the original
value in the self.data_list (database)
:param reset: reset flag to determine how the cell widget to be reset
:param widget_idx: index of the row in self.data_table_widget
:param list_idx: index of the row in self.data_list
"""
for col_idx in range(1, self.total_col):
cell_widget = self.data_table_widget.cellWidget(
widget_idx,
col_idx
)
read_only = False
if hasattr(cell_widget, 'isReadOnly'):
read_only = cell_widget.isReadOnly()
if not read_only:
if reset == 1:
org_val = self.data_list[list_idx][col_idx - 1]
if isinstance(cell_widget, QtWidgets.QLineEdit):
cell_widget.setText(str(org_val))
elif isinstance(cell_widget, QtWidgets.QComboBox):
cell_widget.setCurrentText(str(org_val))
elif isinstance(cell_widget, QtWidgets.QSpinBox):
try:
cell_widget.setValue(int(org_val))
except TypeError:
cell_widget.setValue(0)
set_widget_color(cell_widget)
def set_row_widgets(self, row_idx, fk=False):
"""
Set the widgets in a row in self.data_table_widgets. Because each table
......@@ -477,6 +461,15 @@ class UiDBInfoDialog(OneWindowAtATimeDialog):
"""
pass
def get_row_inputs(self, row_idx: int) -> list:
"""
Get content of a row in the data table. Need to be implemented in all
children that have editable rows.
:param row_idx: index of row
"""
pass
def add_delete_button_to_row(self, row_idx, fk: bool = False):
"""
Add a delete button to a row of the data table. The button will be on
......@@ -516,8 +509,8 @@ class UiDBInfoDialog(OneWindowAtATimeDialog):
f'has been deleted.'
)
row_deleted_label = QtWidgets.QLabel(row_deleted_notifications)
row_deleted_label.setTextFormat(QtCore.Qt.RichText)
row_deleted_label.setAlignment(QtCore.Qt.AlignCenter)
row_deleted_label.setTextFormat(QtCore.Qt.TextFormat.RichText)
row_deleted_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
self.data_table_widget.setCellWidget(row_idx, 1,
row_deleted_label)
......@@ -545,15 +538,25 @@ class UiDBInfoDialog(OneWindowAtATimeDialog):
delete_button_y = row_delete_button.y()
row_idx = self.data_table_widget.rowAt(delete_button_y)
if row_idx >= len(self.data_list):
# Because self.changed_rows only track changes to row content and not
# deletions, we want to remove the deleted row's ID from it.
if row_idx < len(self.database_rows):
self.is_row_changed_array[row_idx] = False
else:
# Because rows not in the database are removed from the table when
# deleted, we delete the value that tracks whether they changed
# when they are deleted.
self.is_row_changed_array.pop(row_idx)
if row_idx >= len(self.database_rows):
# Deleting a row that is not in the database. Because no rows that
# are in the database are removed, and the user can only add rows
# at the end of the data table, it suffices to check that the index
# of the removed row is greater than the length of the data
# retrieved from the database.
# are in the database are removed until changes are saved, and the
# user can only add rows at the end of the data table, it suffices
# to check that the index of the removed row is greater than the
# number of rows retrieved from the database.
self.remove_row(row_idx)
else:
primary_key = self.data_list[row_idx][0]
primary_key = self.database_rows[row_idx][0]
self.display_row_deleted_notification(row_idx, primary_key)
......@@ -605,68 +608,93 @@ class UiDBInfoDialog(OneWindowAtATimeDialog):
queued for execution)
False otherwise
"""
return len(self.queued_row_delete_sqls) != 0
return (len(self.queued_row_delete_sqls) != 0 or
any(self.is_row_changed_array))
def undo_all_deletes(self):
# self.undo_delete_table_row modifies self.queued_row_delete_sqls when
# it is run, so we have to extract the row indices that have been
# deleted before iterating.
deleted_row_indices = list(self.queued_row_delete_sqls.keys())
for row_idx in deleted_row_indices:
self.undo_delete_table_row(row_idx)
def untrack_changes(self):
"""
Untrack all changes that have been made.
"""
self.is_row_changed_array = []
self.queued_row_delete_sqls = {}
def validate_changes(self):
"""
Look through the changes the user made and attempt to check whether
they are all valid. Invalid changes include:
- Those that create duplicate rows.
- Those that create rows with empty primary key.
- Those that make a cell in a required column empty.
:return: True if all changes are valid, False otherwise
"""
primary_keys = {database_row[0] for database_row in self.database_rows}
changed_row_ids = [idx
for (idx, is_changed)
in enumerate(self.is_row_changed_array)
if is_changed]
for row_id in changed_row_ids:
row_content = self.get_row_inputs(row_id)
is_row_primary_key_same_as_database = (
row_id < len(self.database_rows) and
row_content[0] == self.database_rows[row_id][0]
)
is_duplicate_primary_key = (
row_content[0] in primary_keys and
not is_row_primary_key_same_as_database
)
is_required_column_empty = any(
row_content[i] == '' for i in self.required_columns
)
is_changes_invalid = (is_duplicate_primary_key or
row_content[0].strip() == '' or
is_required_column_empty)
if is_changes_invalid:
return False
return True
@QtCore.Slot()
def save_changes(self, need_confirmation=False):
def save_changes(self, need_confirmation=True) -> bool:
"""
When button 'SAVE' is clicked, check each row in data_table_widget to
remain, update, delete, reset the row in self.data_table_widget,
self.data_list and database.
reset's value is the return from self.update_data. if reset=-1, the row
remain unchanged, else self.reset_row_inputs() will be called to reset
the row.
Save the changes to the database. Ask for user confirmation before
saving. If there is any invalid change, exit with an error message.
:param need_confirmation: whether confirmation is needed before saving
the changes made to the database
:return: True if save is successful, False otherwise
"""
# try:
# self.data_table_widget.focusWidget().clearFocus()
# except AttributeError:
# pass
# self.remove_count = 0
# self.insert_count = 0
# self.skip_count = 0
# row_count = self.data_table_widget.rowCount()
# for i in range(row_count):
# widget_idx = i - (row_count - self.data_table_widget.rowCount())
# if widget_idx != i and i in self.changes_by_rowid:
# # row id may not be consistent with widget_idx due to
# # row row removed or inserted.
# self.changes_by_rowid.remove(i)
# self.changes_by_rowid.add(widget_idx)
# list_idx = (i - self.remove_count
# + self.insert_count - self.skip_count)
# try:
# row_inputs = self.get_row_inputs(widget_idx)
# except Exception as e:
# QtWidgets.QMessageBox.warning(self, "Error", str(e))
# return
# reset = self.update_data(row_inputs, widget_idx, list_idx)
# if reset > -1:
# self.reset_row_inputs(reset, widget_idx, list_idx)
# try:
# if len(self.changes_by_rowid[widget_idx]) == 0:
# del self.changes_by_rowid[widget_idx]
# except KeyError:
# pass
if self.has_changes() and not need_confirmation:
# Disable all graphical updates to the data table so that we can update
# it without the user seeing all the intermediate changes we made.
self.data_table_widget.setUpdatesEnabled(False)
if self.has_changes() and need_confirmation:
save_prompt = ('Do you want to save the changes you made? This '
'action cannot be undone.')
choice = QMessageBox.question(self, 'Saving changes', save_prompt,
QMessageBox.Save | QMessageBox.Cancel
QMessageBox.StandardButton.Save |
QMessageBox.StandardButton.Cancel
)
if choice != QMessageBox.Save:
return
if choice != QMessageBox.StandardButton.Save:
self.data_table_widget.setUpdatesEnabled(True)
return False
if not self.validate_changes():
invalid_changes_text = ('Your changes could not be saved due to '
'some of them being invalid.')
QMessageBox.critical(self, 'Invalid Changes', invalid_changes_text)
self.data_table_widget.setUpdatesEnabled(True)
return False
changed_row_ids = [idx
for (idx, is_changed)
in enumerate(self.is_row_changed_array)
if is_changed]
for row_id in changed_row_ids:
if row_id < len(self.database_rows):
current_primary_key = self.database_rows[row_id][0]
sql_template = self.update_sql_template % current_primary_key
else:
sql_template = self.insert_sql_template
execute_db(sql_template, self.get_row_inputs(row_id))
# We need to delete rows from bottom to top for deletions to all be
# done correctly.
......@@ -674,92 +702,14 @@ class UiDBInfoDialog(OneWindowAtATimeDialog):
reverse=True):
execute_db(sql)
self.remove_row(row_id)
del self.data_list[row_id]
self.queued_row_delete_sqls = {}
del self.database_rows[row_id]
self.untrack_changes()
self.update_data_table_widget_items()
def update_data(self, row, widget_idx, list_idx, insert_sql, update_sql,
del_sql_add=None):
"""
Check values of the given row to decide to add, remove, update the row
in data_table_widget and data_list along with database
according to the action .
:param row: list of values of the given row in data_table_widget
:param widget_idx: index of the rows in data_table_widget
:param list_idx: index of the rows in self.data_list
:param insert_sql: query to insert a row to the table
:param update_sql: query to update a row in the table
:param del_sql_add: additional condition to add to the common del_sql
:return -1 for doing nothing
0 reset color to not show value changed
but no need to reset input values to org row
1 reset color to not show value changed
and reset input values to org row
"""
if list_idx < len(self.data_list):
org_row = self.data_list[list_idx]
if row == org_row:
return -1
if row[0] in [p[0] for p in self.data_list]:
if org_row[0] != row[0]:
msg = (f"Row {widget_idx}: The name {self.col_name} "
f"has been changed to '{row[0]}' "
f"which already is in the database.\n\n"
f"It will be changed back to {org_row[0]}.")
QtWidgets.QMessageBox.information(self, "Error", msg)
# reset the first widget value and call the function again
# to check other fields
cell_widget = self.data_table_widget.cellWidget(widget_idx,
1)
cell_widget.setText(org_row[0])
row[0] = org_row[0]
self.changes_by_rowid[widget_idx].remove(0)
self.update_data(row, widget_idx, list_idx)
else:
msg = (f"Row {widget_idx}: {org_row} has "
f"been changed to {row}.\n\nPlease confirm it.")
result = QtWidgets.QMessageBox.question(
self, "Confirmation", msg,
QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel
)
if result == QtWidgets.QMessageBox.Cancel:
return 1
else:
execute_db(update_sql % org_row[0])
self.data_list[list_idx] = row
del self.changes_by_rowid[widget_idx]
return 0
if row[0] == "":
msg = (f"Row {widget_idx} has blank {self.col_name}.\n\n"
f"It will be removed.")
QtWidgets.QMessageBox.information(self, "Error", msg)
self.remove_row(widget_idx)
del self.changes_by_rowid[widget_idx]
return -1
blank_required_columns = [self.required_columns[i]
for i in self.required_columns.keys()
if row[i - 1] == ""]
if blank_required_columns != []:
msg = (f"Row {widget_idx}: blank "
f"{', '.join(blank_required_columns)} which require some "
f"value(s).")
QtWidgets.QMessageBox.information(self, "Error", msg)
return -1
if row[0] in [p[0] for p in self.data_list]:
msg = (f"Row {widget_idx}: The {self.col_name} '{row[0]}' is "
f"already in the database.\n\n"
f"Row {widget_idx} will be removed.")
QtWidgets.QMessageBox.information(self, "Error", msg)
self.remove_row(widget_idx)
del self.changes_by_rowid[widget_idx]
return -1
execute_db(insert_sql)
self.data_list.append(row)
self.insert_count += 1
del self.changes_by_rowid[widget_idx]
return 0
self.data_table_widget.setUpdatesEnabled(True)
return True
def closeEvent(self, event: QCloseEvent):
"""
......@@ -778,15 +728,22 @@ class UiDBInfoDialog(OneWindowAtATimeDialog):
'Are you sure you want to close this editor? '
'Any changes you made will not be saved?'
)
options = QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel
do_exit = False
options = (QMessageBox.StandardButton.Save |
QMessageBox.StandardButton.Discard |
QMessageBox.StandardButton.Cancel)
user_choice = QMessageBox.warning(self, 'Unsaved Changes',
unsaved_changes_prompt, options)
if user_choice != QMessageBox.Cancel:
if user_choice == QMessageBox.Save:
self.save_changes(need_confirmation=False)
elif user_choice == QMessageBox.Discard:
pass
super().closeEvent(event)
if user_choice != QMessageBox.StandardButton.Cancel:
if user_choice == QMessageBox.StandardButton.Save:
do_exit = self.save_changes(need_confirmation=False)
elif user_choice == QMessageBox.StandardButton.Discard:
do_exit = True
if do_exit:
super().closeEvent(event)
else:
event.ignore()
else:
# The exit button defaults to the cancel button if there is one, so
# we don't need to explicitly check for the exit button.
......
......@@ -3,8 +3,8 @@ import platform
import os
from typing import Optional, Dict
from PySide2 import QtWidgets
from PySide2.QtWidgets import QWidget, QDialog, QLineEdit
from PySide6 import QtWidgets
from PySide6.QtWidgets import QWidget, QDialog, QLineEdit
from sohstationviewer.view.util.plot_func_names import plot_functions
......@@ -165,5 +165,5 @@ if __name__ == '__main__':
# test TriColorLInes. Ex: param. Ex: param:Error/warning
# test = EditSingleParamDialog(None, 'Error/Warning', 'RT130')
test.exec_()
sys.exit(app.exec_())
test.exec()
sys.exit(app.exec())
......@@ -3,11 +3,9 @@ param_dialog.py
GUI to add/dit/remove params
NOTE: Cannot remove or change params that are already used for channels.
"""
from typing import List
from PySide2 import QtWidgets, QtCore
from PySide2.QtCore import Qt
from PySide2.QtWidgets import QComboBox, QWidget
from PySide6 import QtWidgets, QtCore
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QComboBox, QWidget
from sohstationviewer.conf.constants import ColorMode, ALL_COLOR_MODES
from sohstationviewer.view.util.plot_func_names import plot_functions
......@@ -36,6 +34,16 @@ class ParamDialog(UiDBInfoDialog):
['No.', 'Param', 'Plot Type', 'ValueColors', 'Height '],
'param', 'parameters',
resize_content_columns=[0, 3])
value_colors_column = 'valueColors' + self.color_mode
self.insert_sql_template = (f"INSERT INTO Parameters "
f"(param, plotType, {value_colors_column},"
f" height) VALUES (?, ?, ?, ?)")
self.update_sql_template = (f"UPDATE Parameters SET param=?, "
f"plotType=?, {value_colors_column}=?, "
f"height=? "
f"WHERE param='%s'")
self.setWindowTitle("Edit/Add/Delete Parameters")
self.add_color_selector(color_mode)
......@@ -65,10 +73,10 @@ class ParamDialog(UiDBInfoDialog):
:param fk: bool: True if there is a foreign constrain that prevents the
row to be deleted
"""
self.add_widget(None, row_idx, 0) # No.
self.add_widget(self.data_list, row_idx, 1, foreign_key=fk)
self.add_widget(None, row_idx, 0) # No.
self.add_widget(self.database_rows, row_idx, 1, foreign_key=fk)
plot_type = self.add_widget(
self.data_list, row_idx, 2,
self.database_rows, row_idx, 2,
choices=[''] + sorted(plot_functions.keys()))
place_holder_text = ""
if plot_type in self.require_valuecolors_plottypes:
......@@ -81,9 +89,9 @@ class ParamDialog(UiDBInfoDialog):
place_holder_text = "Ex: 1:R|0:Y"
elif plot_type == "dotForTime":
place_holder_text = "Ex: G"
self.add_widget(self.data_list, row_idx, 3,
self.add_widget(self.database_rows, row_idx, 3,
place_holder_text=place_holder_text)
self.add_widget(self.data_list, row_idx, 4, range_values=[0, 10])
self.add_widget(self.database_rows, row_idx, 4, range_values=[0, 10])
self.add_delete_button_to_row(row_idx, fk)
def get_data_list(self):
......@@ -149,31 +157,6 @@ class ParamDialog(UiDBInfoDialog):
int(self.data_table_widget.cellWidget(row_idx, 4).value())
]
def update_data(self, row: List, widget_idx: int, list_idx: int) -> int:
"""
Prepare insert, update queries then update data of a row from
self.data_table_widgets' content.
:param row: list - data of a row
:param widget_idx: index of row in self.data_table_widgets
:param list_idx: index of row in self.data_list
"""
# The valueColors for each color mode is stored in a separate column.
# Seeing as we only need one of these columns for a color mode, we only
# pull the needed valueColors column from the database.
value_colors_column = 'valueColors' + self.color_mode
insert_sql = (f"INSERT INTO Parameters "
f"(param, plotType, {value_colors_column}, height) "
f"VALUES"
f"('{row[0]}', '{row[1]}', '{row[2]}', {row[3]})")
update_sql = (f"UPDATE Parameters SET param='{row[0]}', "
f"plotType='{row[1]}', {value_colors_column}='{row[2]}',"
f"height={row[3]} "
f"WHERE param='%s'")
return super().update_data(
row, widget_idx, list_idx, insert_sql, update_sql)
@QtCore.Slot()
def on_color_mode_changed(self, new_color_mode: ColorMode):
"""
......@@ -181,7 +164,16 @@ class ParamDialog(UiDBInfoDialog):
:param new_color_mode: the new color mode
"""
old_value_colors_column = 'valueColors' + self.color_mode
self.color_mode = new_color_mode
new_value_colors_column = 'valueColors' + self.color_mode
self.insert_sql_template = self.insert_sql_template.replace(
old_value_colors_column,
new_value_colors_column)
self.update_sql_template = self.update_sql_template.replace(
old_value_colors_column,
new_value_colors_column)
# Remove all rows in the table while keeping the widths of the columns
# intact
self.data_table_widget.setRowCount(0)
......