From 9afbe093a0f9a829eb3fb3f77e946e5dc8eee858 Mon Sep 17 00:00:00 2001 From: Kien Le <kien.le@earthscope.org> Date: Fri, 1 Nov 2024 14:35:38 -0600 Subject: [PATCH] Add revert button to value color editor --- sohstationviewer/attributions.txt | 1 + .../images/revert_icon_black_background.png | Bin 0 -> 13978 bytes .../images/revert_icon_white_background.png | Bin 0 -> 11837 bytes .../db_config/value_color_helper/functions.py | 2 +- .../value_color_helper/value_color_edit.py | 179 ++++++++++++------ .../value_color_helper/test_functions.py | 42 ++-- 6 files changed, 149 insertions(+), 75 deletions(-) create mode 100644 sohstationviewer/attributions.txt create mode 100644 sohstationviewer/images/revert_icon_black_background.png create mode 100644 sohstationviewer/images/revert_icon_white_background.png diff --git a/sohstationviewer/attributions.txt b/sohstationviewer/attributions.txt new file mode 100644 index 000000000..48af1494c --- /dev/null +++ b/sohstationviewer/attributions.txt @@ -0,0 +1 @@ +<a href="https://www.flaticon.com/free-icons/restart" title="restart icons">Restart icons created by Icon Mart - Flaticon</a> diff --git a/sohstationviewer/images/revert_icon_black_background.png b/sohstationviewer/images/revert_icon_black_background.png new file mode 100644 index 0000000000000000000000000000000000000000..877b4af0689ef05c10d5ede8fa3cb2a208669f82 GIT binary patch literal 13978 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 Lj?}Wfe&_xl!YtXE literal 0 HcmV?d00001 diff --git a/sohstationviewer/images/revert_icon_white_background.png b/sohstationviewer/images/revert_icon_white_background.png new file mode 100644 index 0000000000000000000000000000000000000000..3953f2709b70eedc55703b3516705fc3f7ec9fdd GIT binary patch literal 11837 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 literal 0 HcmV?d00001 diff --git a/sohstationviewer/view/db_config/value_color_helper/functions.py b/sohstationviewer/view/db_config/value_color_helper/functions.py index 5d9684cdc..41889f31a 100644 --- a/sohstationviewer/view/db_config/value_color_helper/functions.py +++ b/sohstationviewer/view/db_config/value_color_helper/functions.py @@ -79,7 +79,7 @@ def prepare_value_color_html(value_colors: str) -> str: else: c_html = ( f"{value}:" - f"<span style='color: {vcs[1]}; font-size:25px;'>∎" + f"<span style='color: {vcs[1]}; font-size:18px;'>■" f"</span>{size}") html_color_parts.append(c_html) diff --git a/sohstationviewer/view/db_config/value_color_helper/value_color_edit.py b/sohstationviewer/view/db_config/value_color_helper/value_color_edit.py index 5c2ed843e..851ca6e5b 100644 --- a/sohstationviewer/view/db_config/value_color_helper/value_color_edit.py +++ b/sohstationviewer/view/db_config/value_color_helper/value_color_edit.py @@ -2,36 +2,38 @@ from typing import Optional, Type from PySide6 import QtWidgets, QtGui, QtCore from PySide6.QtCore import Qt -from PySide6.QtWidgets import QWidget, QTextEdit, QMessageBox +from PySide6.QtGui import QPalette, QColor +from PySide6.QtWidgets import ( + QWidget, QMessageBox, QLabel, QFrame, +) from sohstationviewer.conf.constants import ROOT_PATH -from sohstationviewer.view.db_config.value_color_helper.\ +from sohstationviewer.view.db_config.param_helper import \ + validate_value_color_str +from sohstationviewer.view.db_config.value_color_helper. \ + edit_value_color_dialog import DotForTimeDialog +from sohstationviewer.view.db_config.value_color_helper. \ edit_value_color_dialog import EditValueColorDialog -from sohstationviewer.view.db_config.value_color_helper.\ +from sohstationviewer.view.db_config.value_color_helper. \ edit_value_color_dialog import LineDotDialog -from sohstationviewer.view.db_config.value_color_helper.\ - edit_value_color_dialog import ( - MultiColorDotLowerEqualDialog, MultiColorDotUpperEqualDialog) -from sohstationviewer.view.db_config.value_color_helper.\ +from sohstationviewer.view.db_config.value_color_helper. \ + edit_value_color_dialog import (MultiColorDotLowerEqualDialog, + MultiColorDotUpperEqualDialog) +from sohstationviewer.view.db_config.value_color_helper. \ edit_value_color_dialog import TriColorLinesDialog -from sohstationviewer.view.db_config.value_color_helper.\ +from sohstationviewer.view.db_config.value_color_helper. \ edit_value_color_dialog import UpDownDialog -from sohstationviewer.view.db_config.value_color_helper.\ - edit_value_color_dialog import DotForTimeDialog from sohstationviewer.view.db_config.value_color_helper.functions import \ prepare_value_color_html -from sohstationviewer.view.db_config.param_helper import \ - validate_value_color_str -from sohstationviewer.view.util.plot_type_info import plot_types from sohstationviewer.view.util.color import COLOR - +from sohstationviewer.view.util.plot_type_info import plot_types plot_types_with_value_colors = [ p for p in plot_types if 'pattern' in plot_types[p]] -class ValueColorEdit(QTextEdit): +class ValueColorEdit(QFrame): """ Widget to display valueColors and call a dialog to edit tha value """ @@ -54,19 +56,37 @@ class ValueColorEdit(QTextEdit): :param errmsg_header: header for error message to help user identify the box that has value color string error. """ - QtWidgets.QTextEdit.__init__(self, parent) + super().__init__(parent) + # Background color for setting border color match with background - self.background: str = 'black' if background == 'B' else 'white' - self.set_background_color(background) + self.background: QColor = ( + QColor(Qt.GlobalColor.black) + if background == 'B' + else QColor(Qt.GlobalColor.white) + ) # Type of channel's plot self.plot_type: str = plot_type - # Original value color string to know if value color string has been - # changed. - self.org_value_color_str: str = value_color_str + # Value colors string given as the input. Only used to determine if the + # currently displayed value colors string is different from the one + # given as the input. + self.original_value_color_str: str = value_color_str # Current value color string displayed on widget self.value_color_str: str = '' # Flag showing if original value color string passes validation self.is_valid: bool = True + # dialog that pop up when clicking on edit_button to help edit color + # and value + self.edit_value_color_dialog: Optional[QWidget] = None + # Component widgets of this widget + self.value_color_display = QLabel(self) + self.revert_button = QtWidgets.QToolButton(self) + self.edit_button = QtWidgets.QToolButton(self) + + self.setup_ui(background, plot_type) + + # Value colors string to revert to. Might be different from the input + # because we always want to revert to a valid value colors string. + self.base_value_color_str = '' if plot_type in plot_types_with_value_colors: if value_color_str == '': # Use default value_color_str for the empty one @@ -78,7 +98,7 @@ class ValueColorEdit(QTextEdit): raise ValueError( f"{errmsg_header}{err_msg}\n\n The ValueColors string " f"will be replaced with {plot_type}'s default one.") - self.set_value_color(value_color_str) + self.base_value_color_str = value_color_str except ValueError as e: QMessageBox.critical( self, f'Invalid ValueColors:{plot_type}', str(e)) @@ -86,27 +106,53 @@ class ValueColorEdit(QTextEdit): # We set the value colors to the default value (specified in # plot_type_info.py) when there is a problem with the given # value colors string. - self.set_value_color( + self.base_value_color_str = ( plot_types[plot_type]['default_value_color'] ) + self.set_value_color(self.base_value_color_str) - # dialog that pop up when clicking on edit_button to help edit color - # and value - self.edit_value_color_dialog: Optional[QWidget] = None + def setup_ui(self, background: str, plot_type: str): + # Set up the border of the widget. + self.setFrameShape(QFrame.Shape.Box) + self.setFrameShadow(QFrame.Shadow.Plain) + self.set_border_color(self.background) + # If we make the border disappear altogether when it is not needed, the + # buttons will be moved a bit when the border actually shows up. This + # does not look the best when we have multiple of this widget in a row + # (e.g. in the Params table editor). Instead, we make it so that the + # border always shows up but blend in with the widget's background + # unless needed. + self.setLineWidth(4) - self.setReadOnly(True) - # change cursor to Arrow so user know they can't edit directly - self.viewport().setCursor( - QtGui.QCursor(QtCore.Qt.CursorShape.ArrowCursor) - ) - # to see all info - self.setFixedHeight(28) - self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - self.verticalScrollBar().setDisabled(True) + # Without this line, the background of the widget will not be filled + # with the color we want. + self.setAutoFillBackground(True) + self.set_background_color(background) - self.edit_button = QtWidgets.QToolButton(self) - self.edit_button.setCursor(Qt.PointingHandCursor) + # QLabels expands only enough to fit their content, so we need to + # expand their width manually to make space for longer value colors + # strings. + # The actual width was chosen pretty arbitrarily. It is simply the + # first value we tested that makes the UI looks good. + self.value_color_display.setFixedWidth(250) + + self.revert_button.setCursor(Qt.PointingHandCursor) + if background == 'B': + img_file = f"{ROOT_PATH}/images/revert_icon_black_background.png" + else: + img_file = f"{ROOT_PATH}/images/revert_icon_white_background.png" + self.revert_button.setIcon(QtGui.QIcon(img_file)) + self.revert_button.setStyleSheet( + "background: transparent; border: none;") + self.revert_button.clicked.connect(self.revert) + # Make it so that the widget does not change size when the revert + # button shows up after being hidden. + size_policy = self.revert_button.sizePolicy() + size_policy.setRetainSizeWhenHidden(True) + self.revert_button.setSizePolicy(size_policy) + self.revert_button.hide() + + self.edit_button.setCursor(Qt.CursorShape.PointingHandCursor) if background == 'B': img_file = f"{ROOT_PATH}/images/edit_icon_black_background.png" else: @@ -119,21 +165,30 @@ class ValueColorEdit(QTextEdit): self.edit_button.hide() layout = QtWidgets.QHBoxLayout(self) - layout.addWidget(self.edit_button, 0, Qt.AlignRight) - layout.setSpacing(0) - layout.setContentsMargins(5, 5, 5, 5) + layout.setContentsMargins(5, 0, 5, 0) - self.textChanged.connect( - lambda: self.value_color_edited.emit(self.value_color_str) - ) + layout.addWidget(self.value_color_display) + layout.addWidget(self.revert_button) + layout.addWidget(self.edit_button) - def set_border_color(self, color): + def set_border_color(self, color: QColor): """ Set border color for the widget. :param color: color to set to border """ - self.setStyleSheet("QTextEdit {border: 4px solid %s;}" % color) + palette = self.palette() + # The border color of a QFrame is handled by its foreground color + # roles. This is not explicitly documented in the official QT doc. + # Instead, this fact is only hinted. The stackoverflow question below + # is where this information is found. + # https://stackoverflow.com/questions/51056997/how-to-set-color-of-frame-of-qframe # noqa + # The color role used for the border color of a QFrame depends on + # whether it is embedded in a QTableWidget or is used as a standalone + # widget. This is understandable, but not entirely expected. + palette.setColor(QPalette.ColorRole.Text, color) + palette.setColor(QPalette.ColorRole.WindowText, color) + self.setPalette(palette) def set_background_color(self, background: str): """ @@ -143,13 +198,23 @@ class ValueColorEdit(QTextEdit): :param background: 'B'/'W': sign for background color """ palette = self.palette() + display_palette = self.value_color_display.palette() + # The color role actually used in a palette depends on whether the + # widget is embedded in a QTableWidget (and maybe other views) or is + # used as a standalone widget. This is understandable, but not entirely + # expected. if background == 'B': - palette.setColor(QtGui.QPalette.ColorRole.Text, Qt.white) - palette.setColor(QtGui.QPalette.ColorRole.Base, Qt.black) + display_palette.setColor(QPalette.ColorRole.Text, Qt.white) + display_palette.setColor(QPalette.ColorRole.WindowText, Qt.white) + palette.setColor(QPalette.ColorRole.Base, Qt.black) + palette.setColor(QPalette.ColorRole.Window, Qt.black) else: - palette.setColor(QtGui.QPalette.ColorRole.Text, Qt.black) - palette.setColor(QtGui.QPalette.ColorRole.Base, Qt.white) + display_palette.setColor(QPalette.ColorRole.Text, Qt.black) + display_palette.setColor(QPalette.ColorRole.WindowText, Qt.black) + palette.setColor(QPalette.ColorRole.Base, Qt.white) + palette.setColor(QPalette.ColorRole.Window, Qt.white) self.setPalette(palette) + self.value_color_display.setPalette(display_palette) def set_value_color(self, value_color_str: str) -> None: """ @@ -160,8 +225,13 @@ class ValueColorEdit(QTextEdit): """ self.value_color_str = value_color_str value_color_html = prepare_value_color_html(self.value_color_str) - self.setHtml(value_color_html) + self.value_color_display.setText(value_color_html) + self.value_color_edited.emit(self.value_color_str) self.set_border_color_according_to_value_color_status() + if value_color_str != self.base_value_color_str: + self.revert_button.show() + else: + self.revert_button.hide() def set_border_color_according_to_value_color_status(self): """ @@ -169,13 +239,16 @@ class ValueColorEdit(QTextEdit): + Blue for changed + Same color as background (no border) for other case. """ - if self.value_color_str != self.org_value_color_str: + if self.value_color_str != self.original_value_color_str: # Show that value_color_str has been changed - self.set_border_color(COLOR['changedStatus']) + self.set_border_color(QColor(COLOR['changedStatus'])) else: # Reset border the same color as background self.set_border_color(self.background) + def revert(self): + self.set_value_color(self.base_value_color_str) + def edit(self): """ Show user an editor to edit the value colors of this widget. diff --git a/tests/view/db_config/value_color_helper/test_functions.py b/tests/view/db_config/value_color_helper/test_functions.py index 91ae92c14..829e36e5e 100644 --- a/tests/view/db_config/value_color_helper/test_functions.py +++ b/tests/view/db_config/value_color_helper/test_functions.py @@ -10,74 +10,74 @@ class TestPrepareValueColorHTML(BaseTestCase): with self.subTest("Line and dot values"): expected_value_colors = ( "<p>Line:" - "<span style='color: #00FF00; font-size:25px;'>∎</span>" + "<span style='color: #00FF00; font-size:18px;'>■</span>" "|Dot:" - "<span style='color: #00FF00; font-size:25px;'>∎</span>" + "<span style='color: #00FF00; font-size:18px;'>■</span>" "</p>") result = prepare_value_color_html("Line:#00FF00|Dot:#00FF00") - self.assertEqual(result, expected_value_colors) + self.assertEqual(expected_value_colors, result) with self.subTest("Line value"): expected_value_colors = ( - "<p>Line:<span style='color: #00FF00; font-size:25px;'>∎" + "<p>Line:<span style='color: #00FF00; font-size:18px;'>■" "</span></p>") result = prepare_value_color_html("Line:#00FF00") - self.assertEqual(result, expected_value_colors) + self.assertEqual(expected_value_colors, result) def test_up_down_dots(self): expected_value_colors = ( "<p>Down:" - "<span style='color: #FF0000; font-size:25px;'>∎</span>" + "<span style='color: #FF0000; font-size:18px;'>■</span>" "|Up:" - "<span style='color: #00FF00; font-size:25px;'>∎</span>" + "<span style='color: #00FF00; font-size:18px;'>■</span>" "</p>") result = prepare_value_color_html("Down:#FF0000|Up:#00FF00") - self.assertEqual(result, expected_value_colors) + self.assertEqual(expected_value_colors, result) def test_multi_color_dots_equal_on_upper_bound(self): expected_value_colors = ( "<p>≤0:not plot" "|≤1:" - "<span style='color: #FFFF00; font-size:25px;'>∎</span>" + "<span style='color: #FFFF00; font-size:18px;'>■</span>" ":▲|≤2:" - "<span style='color: #00FF00; font-size:25px;'>∎</span>" + "<span style='color: #00FF00; font-size:18px;'>■</span>" "|2<:" - "<span style='color: #FF00FF; font-size:25px;'>∎</span>" + "<span style='color: #FF00FF; font-size:18px;'>■</span>" "</p>") result = prepare_value_color_html( '<=0:not plot|<=1:#FFFF00:bigger|<=2:#00FF00|2<:#FF00FF') - self.assertEqual(result, expected_value_colors) + self.assertEqual(expected_value_colors, result) def test_multi_color_dots_equal_on_lower_bound(self): expected_value_colors = ( "<p><3:not plot" "|<3.3:" - "<span style='color: #FFFF00; font-size:25px;'>∎</span>" + "<span style='color: #FFFF00; font-size:18px;'>■</span>" "|=3.3:" - "<span style='color: #00FF00; font-size:25px;'>∎</span>" + "<span style='color: #00FF00; font-size:18px;'>■</span>" ":▼</p>") result = prepare_value_color_html( '<3:not plot|<3.3:#FFFF00|=3.3:#00FF00:smaller') - self.assertEqual(result, expected_value_colors) + self.assertEqual(expected_value_colors, result) def test_tri_color_lines(self): expected_value_colors = ( "<p>-1:" - "<span style='color: #FF00FF; font-size:25px;'>∎</span>" - "|0:<span style='color: #FF0000; font-size:25px;'>∎</span>" - "|1:<span style='color: #00FF00; font-size:25px;'>∎</span>" + "<span style='color: #FF00FF; font-size:18px;'>■</span>" + "|0:<span style='color: #FF0000; font-size:18px;'>■</span>" + "|1:<span style='color: #00FF00; font-size:18px;'>■</span>" "</p>" ) result = prepare_value_color_html('-1:#FF00FF|0:#FF0000|1:#00FF00') - self.assertEqual(result, expected_value_colors) + self.assertEqual(expected_value_colors, result) def test_dot_for_time(self): expected_value_colors = ( "<p>C:" - "<span style='color: #FF00FF; font-size:25px;'>∎</span>" + "<span style='color: #FF00FF; font-size:18px;'>■</span>" "</p>" ) result = prepare_value_color_html('C:#FF00FF') - self.assertEqual(result, expected_value_colors) + self.assertEqual(expected_value_colors, result) def test_empty_input(self): expected = '' -- GitLab