Skip to content
Snippets Groups Projects
Commit b75be0b9 authored by Lan Dam's avatar Lan Dam
Browse files

dialog to edit valueColors for MultiColorDots

parent 245f83b3
No related branches found
No related tags found
1 merge request!217Implement dialogs to edit valueColors for 2 multiColorDots plot types
import sys
import platform
import os
from typing import List, Dict
from PySide2 import QtWidgets, QtCore, QtGui
from PySide2.QtWidgets import QWidget
from sohstationviewer.view.db_config.value_color_helper.\
edit_value_color_dialog.edit_value_color_dialog_super_class import \
EditValueColorDialog, display_color
TOTAL = 7
class BoundValidator(QtGui.QValidator):
"""
Validator that allow float value and empty string
Value '-' to allow typing negative float. If user type '-' only, it will
be checked when editing is finished.
"""
def validate(self, input, pos):
if input in ['', '-']:
return QtGui.QValidator.Acceptable
try:
input = float(input)
except ValueError:
return QtGui.QValidator.Invalid
if -20 <= input <= 20:
return QtGui.QValidator.Acceptable
else:
return QtGui.QValidator.Invalid
class MultiColorDotDialog(EditValueColorDialog):
def __init__(
self, parent: QWidget, value_color_str: str, upper_equal: bool):
"""
Dialog to edit color for Multi-color Dot Plot
:param parent: the parent widget
:param value_color_str: string for value color to be saved in DB
:param upper_equal: flag to know if equal is on upper bound
or lower_bound
"""
self.upper_equal = upper_equal
# list of widgets to display lower bound which is read only of the
# value range of rows
self.lower_bound_lnedits: List[QtWidgets.QLineEdit] = []
# list of widgets to display lower bound of the value range of rows
self.higher_bound_lnedits: List[QtWidgets.QLineEdit] = []
# List of buttons that allow user to add/edit color
self.select_color_btns: List[QtWidgets.QPushButton] = []
# list of labels to display color of the data points defined by
# the value range of rows
self.color_labels: List[QtWidgets.QLabel] = []
# After a higher bound widget is edited and enter is hitted, button's
# clicked signal will be called before higher_bound_editting_finished
# is reached. The following flags are to change that order.
self.changed_rowid = None
self.select_color_btn_clicked = False
self.save_colors_btn_clicked = False
# Check box for plotting data point less than all the rest
self.include_less_than_chkbox = QtWidgets.QCheckBox('Include')
# Check box for including data point greater than all the rest
self.include_greater_than_chkbox = QtWidgets.QCheckBox('Include')
# Keep track of value by rowid
self.rowid_value_dict: Dict[int, float] = {}
super(MultiColorDotDialog, self).__init__(parent, value_color_str)
self.setWindowTitle("Edit Multi Color Dot Plotting")
# set focus policy to not be clicked by hitting enter in higher bound
self.cancel_btn.setFocusPolicy(QtCore.Qt.NoFocus)
self.save_colors_btn.setFocusPolicy(QtCore.Qt.NoFocus)
self.set_value_rowid_dict()
def setup_ui(self) -> None:
for i in range(TOTAL):
if i == 0:
self.main_layout.addWidget(
self.include_less_than_chkbox, 0, 0, 1, 1)
self.include_less_than_chkbox.setChecked(True)
if i == TOTAL - 1:
self.main_layout.addWidget(
self.include_greater_than_chkbox, TOTAL - 1, 0, 1, 1)
(lower_bound_lnedit, higher_bound_lnedit,
select_color_btn, color_label) = self.add_row(i)
self.lower_bound_lnedits.append(lower_bound_lnedit)
self.higher_bound_lnedits.append(higher_bound_lnedit)
self.select_color_btns.append(select_color_btn)
self.color_labels.append(color_label)
self.set_color_enabled(i, False)
self.setup_complete_buttons(TOTAL)
def connect_signals(self) -> None:
self.include_greater_than_chkbox.clicked.connect(
lambda: self.on_include(TOTAL - 1,
self.include_greater_than_chkbox))
self.include_less_than_chkbox.clicked.connect(
lambda: self.on_include(0, self.include_less_than_chkbox))
for i in range(TOTAL):
self.higher_bound_lnedits[i].textChanged.connect(
lambda arg, idx=i: setattr(self, 'changed_rowid', idx))
self.higher_bound_lnedits[i].editingFinished.connect(
lambda idx=i: self.on_higher_bound_editing_finished(idx))
# some how pass False to on_select_color, have to handle this
# in add_row() instead
# self.select_color_btns[i].clicked.connect(
# lambda idx=i: self.on_select_color(idx))
super().connect_signals()
def on_higher_bound_editing_finished(self, rowid: int):
"""
Check value entered for higher bound:
+ If the last row is empty string, that value will be eliminated
from condition.
+ If other row is empty string, give error message
+ If value not greater than previous row and less than latter row,
give error message. -20 and 20 are given to previous or latter
row if not exist b/c they are out of range of bound [-10, 10]
Call set_value_rowid_dict to keep track of value by rowid.
"""
if self.changed_rowid is None:
# When the function isn't called from user's changing text on
# a higher_bound_lnedit, this function will be ignored
return
if len(self.rowid_value_dict) < self.changed_rowid < TOTAL - 1:
# When user edit the row that skips some row from the last row
# the current higher_bound_lnedit will be cleared
self.higher_bound_lnedits[self.changed_rowid].setText('')
self.changed_rowid = None
return
self.changed_rowid = None
prev_higher_bound = (
float(self.higher_bound_lnedits[rowid - 1].text())
if rowid > 0 else -20)
try:
curr_higher_bound = float(self.higher_bound_lnedits[rowid].text())
except ValueError:
# When the current higher_bound_lnedit is cleared.
if rowid < len(self.rowid_value_dict) - 1:
# If the cleared one isn't the last one, it shouldn't be
# allowed. A warning should be given.
msg = "Higher bound must be a number"
QtWidgets.QMessageBox.information(self, "Error", msg)
self.higher_bound_lnedits[rowid].setText(
str(self.rowid_value_dict[rowid]))
else:
# If the cleared one is the last one, the lower_bound_lnedit
# of the next row will be cleared too.
self.set_color_enabled(rowid, False)
self.lower_bound_lnedits[rowid + 1].setText('')
self.set_value_rowid_dict()
self.select_color_btn_clicked = False
self.save_colors_btn_clicked = False
return
if rowid >= len(self.rowid_value_dict) - 1:
# When the current higher_bound_lnedits is on the last row
# set the limit 20 to be the next_higher_bound
next_higher_bound = 20
else:
next_higher_bound = float(
self.higher_bound_lnedits[rowid + 1].text())
if (curr_higher_bound <= prev_higher_bound
or curr_higher_bound >= next_higher_bound):
# If value enter to the current higher_bound_lnedit make it out
# of increasing sorting, a warning will be given, and the
# widget will be set to the original value.
cond = (f"{prev_higher_bound} < "
if prev_higher_bound != -20 else '')
cond += (f"d < {next_higher_bound}"
if next_higher_bound != 20 else 'd')
msg = f"Value entered must be: {cond}"
QtWidgets.QMessageBox.information(self, "Error", msg)
try:
self.higher_bound_lnedits[rowid].setText(
str(self.rowid_value_dict[rowid]))
except KeyError:
# If the current row is the last one, there's no original value
# So the widget will be cleared.
self.higher_bound_lnedits[rowid].setText('')
self.select_color_btn_clicked = False
self.save_colors_btn_clicked = False
return
if ((rowid == 0 and self.include_less_than_chkbox.isChecked())
or rowid != 0):
# Enable button Select Color unless the row is the first row but
# Include checkbox isn't checked
self.set_color_enabled(rowid, True)
self.lower_bound_lnedits[rowid + 1].setText(str(curr_higher_bound))
self.set_value_rowid_dict()
if self.save_colors_btn_clicked:
self.on_save_color()
if self.select_color_btn_clicked:
self.on_select_color(rowid)
def set_value_rowid_dict(self):
"""
Update rowid_value_dict to the current higher bound
Update lower bound of the last row which is for greater than all the
rest to be the max of all higher bound
"""
self.rowid_value_dict = {i: float(self.higher_bound_lnedits[i].text())
for i in range(TOTAL - 1)
if self.higher_bound_lnedits[i].text() != ''}
last_row_lnedit = (self.lower_bound_lnedits[TOTAL - 1]
if self.upper_equal
else self.higher_bound_lnedits[TOTAL - 1])
if len(self.rowid_value_dict) == 0:
last_row_lnedit.clear()
else:
last_row_lnedit.setText(str(max(self.rowid_value_dict.values())))
def set_color_enabled(self, rowid: int, enabled: bool):
"""
Enable color: allow to edit and display color
Disable color: disallow to edit and hide color
"""
self.select_color_btns[rowid].setEnabled(enabled)
self.color_labels[rowid].setHidden(not enabled)
def on_select_color(self, rowid: int):
"""
When clicking on Select Color button, Color Picker will pop up with
the default color is color_label's color.
User will select a color then save to update the selected color to
the color_label.
"""
if self.changed_rowid is not None:
self.select_color_btn_clicked = True
self.on_higher_bound_editing_finished(self.changed_rowid)
return
self.select_color_btn_clicked = False
super().on_select_color(self.color_labels[rowid])
def on_include(self, rowid: int, chkbox: QtWidgets.QCheckBox):
"""
When include_less_than_chkbox/include_greater_than_chkbox is clicked
decide to allow user select color based on the status of the
check box.
"""
self.set_color_enabled(rowid, chkbox.isChecked())
# to be able to click on buttons after include checkbox is checked
self.changed_rowid = None
def set_row(self, vc_idx: int, value: float, color: str):
"""
Add values to widgets in a row
+ row 0: consider uncheck include checkbox if color='not plot' and
check otherwise.
+ row TOTAL-1: check include checkbox and set value for lower bound
+ all row other than TOTAL-1, set value for higher bound and
next row's lower bound, enable and display color
"""
if vc_idx == 0:
if color == 'not plot':
self.include_less_than_chkbox.setChecked(False)
else:
self.include_less_than_chkbox.setChecked(True)
if vc_idx < TOTAL - 1:
self.higher_bound_lnedits[vc_idx].setText(str(value))
self.lower_bound_lnedits[vc_idx + 1].setText(str(value))
else:
self.include_greater_than_chkbox.setChecked(True)
if self.upper_equal:
self.lower_bound_lnedits[TOTAL - 1].setText(str(value))
else:
self.higher_bound_lnedits[TOTAL - 1].setText(str(value))
if color == 'not plot':
self.set_color_enabled(vc_idx, False)
else:
self.set_color_enabled(vc_idx, True)
display_color(self.color_labels[vc_idx], color)
def on_save_color(self):
"""
Create value_color_str from GUI's info and close the GUI.
+ Skip row that has no color
+ color = color_label's color name
+ if include_less_than_chkbox is not checked, 'not_plot' will be
set for the first color
+ Format for value_color of all row other than TOTAL - 1th:
<=value:color
+ If include_greater_than_chkbox is checked, format will be:
value<:color
"""
if self.changed_rowid and self.changed_rowid < TOTAL - 1:
print("on_save_color")
self.save_colors_btn_clicked = True
self.on_higher_bound_editing_finished(self.changed_rowid)
return
self.save_colors_btn_clicked = False
value_color_list = []
for i in range(TOTAL - 1):
if self.color_labels[i].isHidden() and i != 0:
continue
value = self.higher_bound_lnedits[i].text()
color = self.color_labels[i].palette(
).window().color().name()
if i == 0 and not self.include_less_than_chkbox.isChecked():
color = 'not plot'
operator = '<=' if self.upper_equal else '<'
value_color_list.append(f"{operator}{value}:{color}")
if self.include_greater_than_chkbox.isChecked():
color = self.color_labels[TOTAL - 1].palette().window(
).color().name()
if self.upper_equal:
val = f"{self.lower_bound_lnedits[TOTAL - 1].text()}"
value_color_list.append(f"{val}<:{color}")
else:
val = f"{self.higher_bound_lnedits[TOTAL - 1].text()}"
value_color_list.append(f"={val}:{color}")
self.value_color_str = '|'.join(value_color_list)
self.close()
class MultiColorDotLowerEqualDialog(MultiColorDotDialog):
def __init__(self, parent, value_color_str):
super(MultiColorDotLowerEqualDialog, self).__init__(
parent, value_color_str, upper_equal=False)
def add_row(self, rowid):
"""
Adding a row including Lower bound line dit, comparing operator label,
higher bound line edit, select color button, display color label
"""
lower_bound_lnedit = QtWidgets.QLineEdit()
self.main_layout.addWidget(lower_bound_lnedit, rowid, 1, 1, 1)
lower_bound_lnedit.setReadOnly(True)
if rowid == 0:
lower_bound_lnedit.setHidden(True)
comp_text = " d <"
elif rowid < TOTAL - 1:
lower_bound_lnedit.setEnabled(False)
comp_text = "<= d <"
else:
lower_bound_lnedit.setHidden(True)
comp_text = " d ="
self.main_layout.addWidget(QtWidgets.QLabel(comp_text), rowid, 2, 1, 1)
higher_bound_lnedit = QtWidgets.QLineEdit()
validator = BoundValidator()
higher_bound_lnedit.setValidator(validator)
self.main_layout.addWidget(higher_bound_lnedit, rowid, 3, 1, 1)
if rowid == TOTAL - 1:
higher_bound_lnedit.setEnabled(False)
select_color_btn = QtWidgets.QPushButton("Select Color")
# set focus policy to not be clicked by hitting enter in higher bound
select_color_btn.setFocusPolicy(QtCore.Qt.NoFocus)
self.main_layout.addWidget(select_color_btn, rowid, 4, 1, 1)
color_label = QtWidgets.QLabel()
color_label.setFixedWidth(30)
color_label.setAutoFillBackground(True)
self.main_layout.addWidget(color_label, rowid, 5, 1, 1)
select_color_btn.clicked.connect(
lambda: self.on_select_color(rowid))
return (lower_bound_lnedit, higher_bound_lnedit,
select_color_btn, color_label)
def set_value(self):
"""
Change the corresponding color_labels's color, higher/lower bound,
include check boxes according to value_color_str.
"""
self.include_greater_than_chkbox.setChecked(False)
self.include_less_than_chkbox.setChecked(False)
if self.value_color_str == "":
return
vc_parts = self.value_color_str.split('|')
count = 0
for vc_str in vc_parts:
value, color = vc_str.split(':')
if value.startswith('<'):
# Ex: <1:#00FFFF
# Ex: <0:not plot (can be on first value only)
value = value.replace('<', '')
self.set_row(count, float(value), color)
count += 1
else:
# Ex: =1:#FF00FF
value = value.replace('=', '')
self.set_row(TOTAL - 1, float(value), color)
class MultiColorDotUpperEqualDialog(MultiColorDotDialog):
def __init__(self, parent, value_color_str):
super(MultiColorDotUpperEqualDialog, self).__init__(
parent, value_color_str, upper_equal=True)
def add_row(self, rowid):
"""
Adding a row including Lower bound line dit, comparing operator label,
higher bound line edit, select color button, display color label
"""
lower_bound_lnedit = QtWidgets.QLineEdit()
self.main_layout.addWidget(lower_bound_lnedit, rowid, 1, 1, 1)
lower_bound_lnedit.setReadOnly(True)
if rowid == 0:
lower_bound_lnedit.setHidden(True)
comp_text = " d <="
elif rowid < TOTAL - 1:
lower_bound_lnedit.setEnabled(False)
comp_text = "< d <="
else:
lower_bound_lnedit.setReadOnly(True)
comp_text = "< d "
self.main_layout.addWidget(QtWidgets.QLabel(comp_text), rowid, 2, 1, 1)
higher_bound_lnedit = QtWidgets.QLineEdit()
validator = BoundValidator()
higher_bound_lnedit.setValidator(validator)
self.main_layout.addWidget(higher_bound_lnedit, rowid, 3, 1, 1)
if rowid == TOTAL - 1:
higher_bound_lnedit.setHidden(True)
select_color_btn = QtWidgets.QPushButton("Select Color")
# set focus policy to not be clicked by hitting enter in higher bound
select_color_btn.setFocusPolicy(QtCore.Qt.NoFocus)
self.main_layout.addWidget(select_color_btn, rowid, 4, 1, 1)
color_label = QtWidgets.QLabel()
color_label.setFixedWidth(30)
color_label.setAutoFillBackground(True)
self.main_layout.addWidget(color_label, rowid, 5, 1, 1)
select_color_btn.clicked.connect(
lambda: self.on_select_color(rowid))
return (lower_bound_lnedit, higher_bound_lnedit,
select_color_btn, color_label)
def set_value(self):
"""
Change the corresponding color_labels's color, higher/lower bound,
include check boxes according to value_color_str.
"""
self.include_greater_than_chkbox.setChecked(False)
self.include_less_than_chkbox.setChecked(False)
if self.value_color_str == "":
return
vc_parts = self.value_color_str.split('|')
count = 0
for vc_str in vc_parts:
value, color = vc_str.split(':')
if value.startswith('<='):
# Ex: <=1:#00FFFF
# Ex: <=0:not plot (can be on first value only)
value = value.replace('<=', '')
self.set_row(count, float(value), color)
count += 1
else:
# Ex: 1<:#FF00FF
value = value.replace('<', '')
self.set_row(TOTAL - 1, float(value), color)
if __name__ == '__main__':
os_name, version, *_ = platform.platform().split('-')
if os_name == 'macOS':
os.environ['QT_MAC_WANTS_LAYER'] = '1'
app = QtWidgets.QApplication(sys.argv)
# test = MultiColorDotLowerEqualDialog(
# None, '<3:#FF0000|<3.3:#FFFF00|=3.3:#00FF00')
test = MultiColorDotUpperEqualDialog(
None, '<=0:not plot|<=1:#FFFF00|<=2:#00FF00|2<:#FF00FF')
test.exec_()
sys.exit(app.exec_())
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment