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 (4)
Showing
with 470 additions and 108 deletions
......@@ -16,3 +16,17 @@ The home button can be used to return to this page at any time.
+ [Search SOH n LOG](04%20_%20Search%20SOH%20n%20LOG.help.md)
+ [Select Waveforms](05%20_%20Select%20Waveforms.help.md)
+ [Select Mass Position](06%20_%20Select%20Mass%20Position.help.md)
+ [Select SOH](07%20_%20Select%20SOH.help.md)
+ [View-Edit Database Tables](07%20_%20View-Edit%20Database%20Tables.help.md)
+ [GPS Dialog](08%20_%20GPS%20Dialog.help.md)
+ [Read from Data Card](08%20_%20Read%20from%20Data%20Card.help.md)
+ [Search List of Directories](10%20_%20Search%20List%20of%20Directories.help.md)
......@@ -29,16 +29,23 @@ List all parameters available.
<img alt="Parameters Table" src="images/database_tables/parameters.png" width="590" />
<br />
+ Depend on the PlotType entered, ValueColors will be applied for the parameter or not. If ValueColors is available, there will be some hint for values to entered in grey color.
+ To add a new row to the table, click on ADD ROW button.
+ To save any changes to database, click on SAVE CHANGES. If ValueColors isn't applicable, the field will be cleared; otherwise it will be validated at this point.
+ To close the dialog, click on CLOSE. User will be asked to confirm closing if there have been any changes made.
+ Depend on the PlotType entered, ValueColors will be applied for the parameter or not. If ValueColors is available, there will be some hint for values to entered in grey color.
+ To add a new row to the table, click on ADD ROW button.
+ To save any changes to database, click on SAVE CHANGES. If ValueColors isn't applicable, the field will be cleared; otherwise it will be validated at this point.
+ To close the dialog, click on CLOSE. User will be asked to confirm closing if there have been any changes made.
+ Fields' meaning:
+ No.: Line number, start with 0.
+ No.: Line number, start with 0.
+ Param: Parameter's name.
+ Plot Type: Define how to plot the parameter. Description of Plot Type can be found in Plot Types Table.
+ ValueColors: mapping between values and colors or dot/line and color, or simply color of dots in the plots
+ Plot Type: Define how to plot the parameter. Description of Plot Type can be found in Plot Types Table.
+ ValueColors: mapping between values and colors or dot/line and color, or simply color of dots in the plots
+ Height: Height of plot in compare with other plots.
+ For each color mode available, a row in the database has a separate value for ValueColors. In order to change between the color mode, use the selector at the bottom right of the dialog. The color mode chosen when the Parameters table is opened is the one chosen in the main window.
<br />
<br />
<img alt="Parameters Table" src="images/database_tables/parameters_change_color_mode.jpeg" width="590" />
<br />
## Data Types Table
......
# Search List of Directories
---------------------------
---------------------------
## User Interface
The search bar is located above the list of directories in the main data
directory. When there is text in the search bar, a button that clears the
search will show up.
---------------------------
## How to use
Whenever the content of the search bar is updated, the list of current
directory will be filtered. The result is displayed above the directory list
as a list of directories that match the search text, prefaced with the text
"Found files:".
<br />
<br />
<img alt="Search results" src="images/search_directories/search_result.jpeg"
height="280" />
<br />
There are two methods to clear the search. The first one is to click on the
clear button that appears on the right edge of the search bar. The second one
is to delete the content of the search bar. Either way, the directory list is
reset and the search bar is cleared.
<br />
<br />
<img alt="Clear search" src="images/search_directories/clear_button_pressed.jpg"
height="280" />
<br />
documentation/images/database_tables/parameters_change_color_mode.jpeg

76.7 KiB

documentation/images/search_directories/clear_button_pressed.jpg

19.4 KiB

documentation/images/search_directories/search_result.jpeg

29.8 KiB

import sys
if sys.version_info.minor >= 8:
from typing import Literal
# waveform pattern
WF_1ST = 'A-HLM-V'
WF_2ND = 'HLN'
......@@ -45,6 +49,9 @@ TABLE_CONTENTS = "01 _ Table of Contents.help.md"
# name of search through all documents file
SEARCH_RESULTS = "Search Results.md"
# the list of all color modes
ALL_COLOR_MODES = {'B', 'W'}
# ================================================================= #
# PLOTTING CONSTANT
# ================================================================= #
......@@ -60,3 +67,12 @@ Z_ORDER = {'AXIS_SPINES': 0, 'CENTER_LINE': 1, 'LINE': 2, 'GAP': 3, 'DOT': 3}
# Distance from 'Hour' label to timestamp bar
HOUR_TO_TMBAR_D = 100
# ================================================================= #
# TYPING CONSTANT
# ================================================================= #
if sys.version_info.minor >= 8:
ColorMode = Literal['B', 'W']
else:
ColorMode = str
from typing import Dict
from sohstationviewer.conf.constants import ColorMode
from sohstationviewer.database.process_db import execute_db_dict, execute_db
from sohstationviewer.conf.dbSettings import dbConf
def get_chan_plot_info(org_chan_id, chan_info, data_type):
def get_chan_plot_info(org_chan_id: str, chan_info: Dict, data_type: str,
color_mode: ColorMode = 'B') -> Dict:
"""
Given chanID read from raw data file and detected dataType
Return plotting info from DB for that channel
......@@ -23,16 +26,19 @@ def get_chan_plot_info(org_chan_id, chan_info, data_type):
chan = 'Disk Usage?'
if dbConf['seisRE'].match(chan):
chan = 'SEISMIC'
o_sql = ("SELECT channel, plotType, height, unit, linkedChan,"
" convertFactor, label, fixPoint, valueColors "
"FROM Channels as C, Parameters as P")
# 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' + color_mode
o_sql = (f"SELECT channel, plotType, height, unit, linkedChan,"
f" convertFactor, label, fixPoint, "
f"{value_colors_column} AS valueColors "
f"FROM Channels as C, Parameters as P")
if data_type == 'Unknown':
sql = f"{o_sql} WHERE channel='{chan}' and C.param=P.param"
else:
sql = (f"{o_sql} WHERE channel='{chan}' and C.param=P.param"
f" and dataType='{data_type}'")
# print("SQL:", sql)
chan_db_info = execute_db_dict(sql)
if len(chan_db_info) == 0:
......@@ -64,9 +70,21 @@ def get_chan_plot_info(org_chan_id, chan_info, data_type):
return chan_db_info[0]
def get_wf_plot_info(org_chan):
def get_wf_plot_info(org_chan: str) -> Dict:
"""
Get plotting information for waveform plots from the database.
:param org_chan: the original name of the channel.
:return: the plotting information for org_chan.
"""
# Waveform plot's color is fixed to NULL in the database, so we do not need
# to get the valueColors columns from the database.
chan_info = execute_db_dict(
"SELECT * FROM Parameters WHERE param='Seismic data'")
"SELECT param, plotType, height "
"FROM Parameters WHERE param='Seismic data'")
# The plotting API still requires that the key 'valueColors' is mapped to
# something, so we are setting it to None.
chan_info[0]['valueColors'] = None
chan_info[0]['label'] = get_chan_label(org_chan)
chan_info[0]['unit'] = ''
chan_info[0]['channel'] = 'SEISMIC'
......
No preview for this file type
......@@ -11,7 +11,8 @@ from sohstationviewer.controller.processing import (
from sohstationviewer.controller.util import display_tracking_info
from sohstationviewer.view.util.enums import LogType
from sohstationviewer.view.util.one_instance_at_a_time import \
OneWindowAtATimeDialog
INSTRUCTION = """
Select the list of SOH channels to be used in plotting.\n
......@@ -44,7 +45,7 @@ class InputDialog(QDialog):
return self.text_box.toPlainText()
class ChannelPreferDialog(QtWidgets.QWidget):
class ChannelPreferDialog(OneWindowAtATimeDialog):
def __init__(self, parent, dir_names):
"""
Dialog to create lists of preferred SOH channels that users want to
......
from PySide2 import QtWidgets, QtGui, QtCore
from __future__ import annotations
from typing import Set, Dict
from PySide2 import QtWidgets, QtGui, QtCore
from sohstationviewer.database.process_db import execute_db
from sohstationviewer.view.util.one_instance_at_a_time import \
OneWindowAtATimeDialog
def set_widget_color(widget, changed=False, read_only=False):
......@@ -42,10 +46,15 @@ def set_widget_color(widget, changed=False, read_only=False):
widget.setPalette(palette)
class UiDBInfoDialog(QtWidgets.QWidget):
class UiDBInfoDialog(OneWindowAtATimeDialog):
"""
Superclass for info database dialogs under database menu.
"""
# This line is only to type hint the class attribute inherited from
# OneWindowAtATimeDialog.
current_instance: UiDBInfoDialog
def __init__(self, parent, column_headers, col_name, table_name,
resize_content_columns=[], required_columns={},
need_data_type_choice=False, check_fk=True):
......@@ -98,14 +107,20 @@ class UiDBInfoDialog(QtWidgets.QWidget):
Color code is given at the end of the dialog.
Other instructions should be given when hover over the widgets.
"""
if self.table_name != '':
# Not really used in this (base) class, but made available so that
# any children can use the empty space on the bottom right of the
# widget.
self.bottom_layout = QtWidgets.QGridLayout()
instruction = ("Background: LIGHT BLUE - Non Editable due to "
"FK constrain; "
"WHITE - Editable. "
"Text: BLACK - Saved; RED - Not saved")
"WHITE - Editable.\n"
"Text: BLACK - Saved; RED - Not saved.")
# TODO: add question mark button to give instruction
main_layout.addWidget(QtWidgets.QLabel(instruction))
self.bottom_layout.addWidget(QtWidgets.QLabel(instruction),
0, 0, 1, 1)
main_layout.addLayout(self.bottom_layout)
def add_widget(self, data_list, row_idx, col_idx, foreign_key=False,
choices=None, range_values=None, field_name='',
......
......@@ -3,8 +3,13 @@ param_dialog.py
GUI to add/dit/remove params
NOTE: Cannot remove or change params that are already used for channels.
"""
from PySide2 import QtWidgets
from typing import List
from PySide2 import QtWidgets, QtCore
from PySide2.QtCore import Qt
from PySide2.QtWidgets import QComboBox, QWidget
from sohstationviewer.conf.constants import ColorMode, ALL_COLOR_MODES
from sohstationviewer.view.util.plot_func_names import plot_functions
from sohstationviewer.view.db_config.db_config_dialog import UiDBInfoDialog
......@@ -14,10 +19,13 @@ from sohstationviewer.conf.dbSettings import dbConf
class ParamDialog(UiDBInfoDialog):
def __init__(self, parent):
def __init__(self, parent: QWidget, color_mode: ColorMode) -> None:
"""
:param parent: QMainWindow/QWidget - the parent widget
:param color_mode: the initial color mode of the dialog
"""
self.color_mode = color_mode
self.require_valuecolors_plottypes = [
p for p in plot_functions.keys()
if 'ValueColors' in plot_functions[p][0]]
......@@ -27,8 +35,27 @@ class ParamDialog(UiDBInfoDialog):
'param', 'parameters',
resize_content_columns=[0, 3])
self.setWindowTitle("Edit/Add/Delete Parameters")
self.add_color_selector(color_mode)
def add_color_selector(self, initial_color_mode: ColorMode) -> None:
"""
Add a color mode selector widget to the dialog.
:param initial_color_mode: the color mode to use as the initial
selection when the dialog is shown.
"""
color_mode_label = QtWidgets.QLabel('Color mode:')
color_selector = QComboBox()
color_selector.insertItem(0, initial_color_mode)
other_color_modes = ALL_COLOR_MODES - {initial_color_mode}
color_selector.insertItems(1, other_color_modes)
color_selector.setFixedWidth(100)
color_selector.currentTextChanged.connect(self.on_color_mode_changed)
self.bottom_layout.addWidget(color_mode_label, 0, 1, 1, 1,
Qt.AlignRight)
self.bottom_layout.addWidget(color_selector, 0, 2, 1, 1)
def add_row(self, row_idx, fk=False):
def add_row(self, row_idx: int, fk: bool = False) -> None:
"""
Add a row of widgets to self.data_table_widgets.
......@@ -60,11 +87,15 @@ class ParamDialog(UiDBInfoDialog):
"""
Get list of data to fill self.data_table_widgets' content
"""
# 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
param_rows = execute_db(
"SELECT param,"
" IFNULL(plotType, '') AS plotType,"
" IFNULL(valueColors, '') AS valueColors,"
" IFNULL(height, 0) AS height FROM Parameters")
f"SELECT param, "
f"IFNULL(plotType, '') AS plotType, "
f"IFNULL({value_colors_column}, '') AS valueColors, "
f"IFNULL(height, 0) AS height FROM Parameters")
return [[d[0], d[1], d[2], int(d[3])]
for d in param_rows]
......@@ -115,7 +146,7 @@ class ParamDialog(UiDBInfoDialog):
int(self.data_table_widget.cellWidget(row_idx, 4).value())
]
def update_data(self, row, widget_idx, list_idx):
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.
......@@ -124,11 +155,36 @@ class ParamDialog(UiDBInfoDialog):
: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 Parameters VALUES"
# 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]}', valueColors='{row[2]}',"
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):
"""
Slot called when the color mode is changed in the color mode selector.
:param new_color_mode: the new color mode
"""
self.color_mode = new_color_mode
# Remove all rows in the table while keeping the widths of the columns
# intact
self.data_table_widget.setRowCount(0)
# Repopulates the table with data from the database. A custom routine
# could have been written to replace only the ValueColors column in the
# table, but that would take too much time for only a marginal increase
# in performance, especially considering the Parameters table is
# expected to be pretty small.
self.update_data_table_widget_items()
......@@ -7,7 +7,9 @@ from copy import deepcopy
from pathlib import Path
from PySide2 import QtCore, QtWidgets, QtGui
from PySide2.QtWidgets import QListWidgetItem, QMessageBox
from PySide2.QtCore import QSize
from PySide2.QtGui import QFont, QPalette, QColor
from PySide2.QtWidgets import QFrame, QListWidgetItem, QMessageBox
from sohstationviewer.conf import constants
from sohstationviewer.model.data_loader import DataLoader
......@@ -46,7 +48,9 @@ from sohstationviewer.controller.util import (
from sohstationviewer.database.process_db import execute_db_dict, execute_db
from sohstationviewer.conf.constants import TM_FORMAT
from sohstationviewer.conf.constants import TM_FORMAT, ColorMode
from sohstationviewer.view.util.one_instance_at_a_time import \
DialogAlreadyOpenedError
class MainWindow(QtWidgets.QMainWindow, UIMainWindow):
......@@ -60,6 +64,10 @@ class MainWindow(QtWidgets.QMainWindow, UIMainWindow):
"""
self.dir_names: List[Path] = []
"""
current_dir: str - the current main data directory
"""
self.current_dir = ''
"""
rt130_das_dict: dict by rt130 for data paths, so user can choose
dasses to assign list of data paths to selected_rt130_paths
"""
......@@ -73,6 +81,11 @@ class MainWindow(QtWidgets.QMainWindow, UIMainWindow):
data_type: str - type of data set
"""
self.data_type: str = 'Unknown'
"""
color_mode: str - the current color mode of the plot; can be either 'B'
or 'W'
"""
self.color_mode: ColorMode = 'B'
self.data_loader: DataLoader = DataLoader()
self.data_loader.finished.connect(self.replot_loaded_data)
......@@ -172,36 +185,56 @@ class MainWindow(QtWidgets.QMainWindow, UIMainWindow):
self.background_black_radio_button.toggle()
@QtCore.Slot()
def open_data_type(self):
def open_data_type(self) -> None:
"""
Open a dialog to add/edit data types in DB
Open a dialog to add/edit data types in DB If a dialog of this type is
currently open, raise it to the front.
"""
win = DataTypeDialog(self)
win.show()
try:
win = DataTypeDialog(self)
win.show()
except DialogAlreadyOpenedError:
DataTypeDialog.current_instance.activateWindow()
DataTypeDialog.current_instance.raise_()
@QtCore.Slot()
def open_param(self):
def open_param(self) -> None:
"""
Open a dialog to add/edit parameters in DB
Open a dialog to add/edit parameters in DB If a dialog of this type is
currently open, raise it to the front.
"""
win = ParamDialog(self)
win.show()
try:
win = ParamDialog(self, self.color_mode)
win.show()
except DialogAlreadyOpenedError:
ParamDialog.current_instance.activateWindow()
ParamDialog.current_instance.raise_()
@QtCore.Slot()
def open_channel(self):
def open_channel(self) -> None:
"""
Open a dialog to add/edit channels in DB
Open a dialog to add/edit channels in DB If a dialog of this type is
currently open, raise it to the front.
"""
win = ChannelDialog(self)
win.show()
try:
win = ChannelDialog(self)
win.show()
except DialogAlreadyOpenedError:
ChannelDialog.current_instance.activateWindow()
ChannelDialog.current_instance.raise_()
@QtCore.Slot()
def open_plot_type(self):
def open_plot_type(self) -> None:
"""
Open a dialog to view plot types and their description
Open a dialog to view plot types and their description If a dialog of
this type is currently open, raise it to the front.
"""
win = PlotTypeDialog(self)
win.show()
try:
win = PlotTypeDialog(self)
win.show()
except DialogAlreadyOpenedError:
PlotTypeDialog.current_instance.activateWindow()
PlotTypeDialog.current_instance.raise_()
@QtCore.Slot()
def open_calendar(self):
......@@ -220,23 +253,44 @@ class MainWindow(QtWidgets.QMainWindow, UIMainWindow):
self.help_browser.raise_()
@QtCore.Slot()
def open_channel_preferences(self):
def open_channel_preferences(self) -> None:
"""
Open a dialog to view, select, add, edit, scan for preferred channels
list.
list. If a dialog of this type is currently open, raise it to the
front.
"""
try:
self.read_from_file_list()
except Exception as e:
QtWidgets.QMessageBox.warning(self, "Select directory", str(e))
return
win = ChannelPreferDialog(self, self.dir_names)
win.show()
try:
win = ChannelPreferDialog(self, self.dir_names)
win.show()
except DialogAlreadyOpenedError:
ChannelPreferDialog.current_instance.activateWindow()
ChannelPreferDialog.current_instance.raise_()
@QtCore.Slot()
def from_data_card_check_box_clicked(self):
def from_data_card_check_box_clicked(self, is_from_data_card_checked):
self.open_files_list.clear()
self.set_open_files_list_texts()
# self.search_button.setEnabled(not is_from_data_card_checked)
self.search_line_edit.setEnabled(not is_from_data_card_checked)
# QLineEdit does not change its color when it is disabled unless
# there is text inside, so we have to do it manually.
palette = self.search_line_edit.palette()
if is_from_data_card_checked:
# We are copying the color of a disabled button
search_line_edit_color = QColor(246, 246, 246)
else:
search_line_edit_color = QColor(255, 255, 255)
palette.setColor(QPalette.Base, search_line_edit_color)
self.search_line_edit.setPalette(palette)
if not is_from_data_card_checked and self.search_line_edit.text():
self.filter_folder_list(self.search_line_edit.text())
@QtCore.Slot()
def all_wf_chans_clicked(self):
......@@ -672,7 +726,7 @@ class MainWindow(QtWidgets.QMainWindow, UIMainWindow):
self.plotting_widget.plot_channels(
self.start_tm, self.end_tm, sel_key,
do.data_time[sel_key], soh_chans, time_tick_total,
soh_data, mp_data, gaps)
soh_data, mp_data, gaps, self.color_mode)
except Exception:
fmt = traceback.format_exc()
msg = f"Can't plot SOH data due to error: {str(fmt)}"
......@@ -718,7 +772,7 @@ class MainWindow(QtWidgets.QMainWindow, UIMainWindow):
self.waveform_dlg.plotting_widget.plot_channels(
self.start_tm, self.end_tm, sel_key,
do.data_time[sel_key], time_tick_total,
wf_data, mp_data)
wf_data, mp_data, self.color_mode)
self.add_action_to_forms_menu('Raw Data Plot', self.waveform_dlg)
else:
self.waveform_dlg.hide()
......@@ -779,6 +833,7 @@ class MainWindow(QtWidgets.QMainWindow, UIMainWindow):
# Signal current_directory_changed, and gather list of files in new
# current directory
self.current_directory_changed.emit(path)
self.current_dir = path
execute_db(f'UPDATE PersistentData SET FieldValue="{path}" WHERE '
'FieldName="currentDirectory"')
self.set_open_files_list_texts()
......@@ -930,7 +985,7 @@ class MainWindow(QtWidgets.QMainWindow, UIMainWindow):
Close all forms related to forms_menu.
Add no_forms_action to let user know that there are no forms available.
"""
self.forms_menu.clear() # remove all actions
self.forms_menu.clear() # remove all actions
for i in range(len(self.forms_in_forms_menu) - 1, -1, -1):
form = self.forms_in_forms_menu.pop(i)
form.close()
......@@ -947,7 +1002,74 @@ class MainWindow(QtWidgets.QMainWindow, UIMainWindow):
self.gps_dialog.raise_()
@QtCore.Slot()
def set_plots_color(self, checked, color_mode):
def set_plots_color(self, checked: bool, color_mode: ColorMode) -> None:
"""
Slot called when a new color mode radio button is pressed.
:param checked: whether the button that calls this slot is checked
:param color_mode: the color mode associated with the button that calls
this slot
"""
if not checked:
return
self.color_mode = color_mode
self.plotting_widget.set_colors(color_mode)
self.waveform_dlg.plotting_widget.set_colors(color_mode)
self.tps_dlg.plotting_widget.set_colors(color_mode)
self.gps_dialog.set_colors(color_mode)
@QtCore.Slot()
def clear_file_search(self):
"""
Clear the content of the file search widget.
"""
self.search_line_edit.clear()
self.set_current_directory(self.current_dir)
def filter_folder_list(self, search_text: str) -> None:
"""
Filter the current list of folders based on the search input.
"""
if search_text == '':
self.clear_file_search()
return
self.set_current_directory(self.current_dir)
open_file_paths = [
self.open_files_list.item(i).file_path
for i in range(self.open_files_list.count())
]
filtered_file_paths = [
path
for path in open_file_paths
if search_text.casefold() in path.casefold()
]
# We are inserting the widgets in reverse order because doing so means
# that we only have to insert into the 0th row. Otherwise, we would
# have to keep track of the current index to insert into.
# Create a line that separate filtered list of directories and list of
# directories.
separator_list_item = QListWidgetItem()
separator_list_item.setFlags(QtCore.Qt.NoItemFlags)
separator_list_item.setSizeHint(
QSize(separator_list_item.sizeHint().width(), 10) # noqa
)
self.open_files_list.insertItem(0, separator_list_item)
line = QFrame()
line.setFrameShape(QFrame.HLine)
self.open_files_list.setItemWidget(separator_list_item, line)
for path in reversed(filtered_file_paths):
current_file_list_item = FileListItem(path)
self.open_files_list.insertItem(0, current_file_list_item)
found_files_list_item = QListWidgetItem('Found files:')
bold_font = QFont()
bold_font.setBold(True)
found_files_list_item.setFont(bold_font)
found_files_list_item.setFlags(QtCore.Qt.NoItemFlags)
found_files_list_item.setForeground(QtCore.Qt.black)
self.open_files_list.insertItem(0, found_files_list_item)
......@@ -19,7 +19,7 @@ from sohstationviewer.view.util.enums import LogType
class SOHWidget(plotting_widget.PlottingWidget):
def plot_channels(self, start_tm, end_tm, key, data_time,
channel_list, time_ticks_total,
soh_data, mass_pos_data, gaps):
soh_data, mass_pos_data, gaps, color_mode):
"""
Recursively plot each channels for soh_data and masspos_data.
......@@ -67,7 +67,8 @@ class SOHWidget(plotting_widget.PlottingWidget):
for chan_id in self.plotting_data1:
chan_db_info = extract_data.get_chan_plot_info(
chan_id, self.plotting_data1[chan_id], self.parent.data_type
chan_id, self.plotting_data1[chan_id], self.parent.data_type,
color_mode
)
if chan_db_info['height'] == 0:
# not draw
......@@ -88,7 +89,9 @@ class SOHWidget(plotting_widget.PlottingWidget):
for chan_id in self.plotting_data2:
chan_db_info = extract_data.get_chan_plot_info(
chan_id, self.plotting_data2[chan_id], self.parent.data_type)
chan_id, self.plotting_data2[chan_id], self.parent.data_type,
color_mode
)
self.plotting_data2[chan_id]['chan_db_info'] = chan_db_info
self.get_zoom_data(self.plotting_data2[chan_id], chan_id, True)
......
......@@ -2,10 +2,11 @@
# TODO: add more descriptive progress message
from __future__ import annotations
from typing import List
from typing import List, Union, Tuple, Dict
from PySide2 import QtCore, QtWidgets
from sohstationviewer.conf.constants import ColorMode
from sohstationviewer.view.plotting.waveform_processor import (
WaveformChannelProcessor
)
......@@ -107,9 +108,11 @@ class WaveformWidget(plotting_widget.PlottingWidget):
else:
return True
def plot_channels(self, start_tm, end_tm, key,
data_time, time_ticks_total,
waveform_data, mass_pos_data):
def plot_channels(self, start_tm: float, end_tm: float,
key: Union[str, Tuple[str, str]],
data_time: List[float], time_ticks_total: int,
waveform_data: Dict, mass_pos_data: Dict,
color_mode: ColorMode) -> None:
"""
Prepare to plot waveform and mass-position data by creating a data
processor for each channel in the waveform data (mass-position data is
......@@ -126,7 +129,11 @@ class WaveformWidget(plotting_widget.PlottingWidget):
refer to DataTypeModel.__init__.waveform_data[key]['read_data']
:param mass_pos_data: dict - mass position data of the selected
data set, refer to DataTypeModel.__init__.mass_pos_data[key]
:param color_mode: str - the mode used to determine the color of the
plots
"""
# Store the color mode for use when plotting
self.c_mode = color_mode
if not self.is_working:
self.reset_widget()
self.is_working = True
......@@ -197,7 +204,7 @@ class WaveformWidget(plotting_widget.PlottingWidget):
channel_processor.finished.connect(self.process_channel)
channel_processor.stopped.connect(self.has_stopped)
def plot_mass_pos_channels(self):
def plot_mass_pos_channels(self) -> None:
"""
Plot the mass-position data. Because mass-position data has already
been processed in SOH widget, this method does no further processing
......@@ -205,7 +212,9 @@ class WaveformWidget(plotting_widget.PlottingWidget):
"""
for chan_id in self.plotting_data2:
chan_db_info = extract_data.get_chan_plot_info(
chan_id, self.plotting_data2[chan_id], self.parent.data_type)
chan_id, self.plotting_data2[chan_id], self.parent.data_type,
self.c_mode
)
self.plotting_data2[chan_id]['chan_db_info'] = chan_db_info
self.plot_single_channel(self.plotting_data2[chan_id], chan_id)
......
# UI and connectSignals for main_window
from typing import Union, List
from typing import Union, List, Optional, Iterable
from PySide2 import QtCore, QtGui, QtWidgets
from PySide2.QtCore import QObject
from PySide2.QtGui import QIcon, QPixmap
from PySide2.QtWidgets import (
QMainWindow, QWidget, QTextBrowser, QPushButton, QLineEdit, QDateEdit,
QListWidget, QCheckBox, QRadioButton, QMenu, QAction, QLabel, QFrame,
QVBoxLayout, QHBoxLayout, QGridLayout, QAbstractItemView, QShortcut,
QActionGroup, QButtonGroup
QActionGroup, QButtonGroup, QStyle,
)
from sohstationviewer.view.calendar.calendar_widget import CalendarWidget
......@@ -75,6 +77,15 @@ class UIMainWindow(object):
"""
self.plotting_widget: Union[SOHWidget, None] = None
"""
search_line_edit: textbox for user to search in
self.open_file_list
"""
self.search_line_edit: Union[QLineEdit, None] = None
"""
clear_button: clear search_line_edit
"""
self.clear_search_action: Optional[QAction] = None
"""
open_files_list: Widget that display the list of directories in
current directory for user to select
"""
......@@ -85,29 +96,10 @@ class UIMainWindow(object):
"""
self.from_data_card_check_box: Union[QCheckBox, None] = None
"""
search_line_edit: textbox for user to search in
self.open_file_list
"""
self.search_line_edit: Union[QLineEdit, None] = None
"""
clear_button: clear search_line_edit
"""
self.clear_button: Union[QPushButton, None] = None
"""
replot_button: reset plotting without re-loading data
"""
self.replot_button: Union[QPushButton, None] = None
"""
file_list_log_check_box: If checked show only ".log" files
in open_files_list
"""
self.file_list_log_check_box: Union[QCheckBox, None] = None
"""
file_list_zip_check_box: If checked show only ".zip" files
in open_files_list
"""
self.file_list_zip_check_box: Union[QCheckBox, None] = None
"""
background_black_radio_button: If selected, plotting_widget will turn
to black mode
"""
......@@ -376,6 +368,34 @@ class UIMainWindow(object):
"""
self.open_files_list = QListWidget(
self.central_widget)
self.search_line_edit = QLineEdit(self.central_widget)
self.search_line_edit.setPlaceholderText('Search...')
self.search_line_edit.setToolTip('Filter the list of files based on '
'the content.')
self.search_line_edit.textChanged.connect(
self.main_window.filter_folder_list)
self.search_line_edit.setClearButtonEnabled(True)
try:
# This value was obtained from the C++ source of QT. We use it here
# so that the QAction found is guaranteed to be the clear button.
clear_action_name = '_q_qlineeditclearaction'
found_actions = self.search_line_edit.findChildren(
QAction, clear_action_name
)
self.clear_search_action: QAction = found_actions[0]
self.clear_search_action.triggered.connect(
self.main_window.clear_file_search
)
except IndexError:
# If the name of the clear button is changed in the C++ source of
# QT, nothing will be found. We raise an error to indicate this
# problem.
raise ValueError('No clear button could be found. Check its '
'objectName attribute using QObject.findChildren '
'without a name.')
left_layout.addWidget(self.search_line_edit)
left_layout.addWidget(self.open_files_list, 1)
pal = self.open_files_list.palette()
pal.setColor(QtGui.QPalette.Highlight, QtGui.QColor(128, 255, 128))
......@@ -385,33 +405,25 @@ class UIMainWindow(object):
self.open_files_list.setSelectionMode(
QAbstractItemView.ExtendedSelection)
search_grid = QGridLayout()
left_layout.addLayout(search_grid)
self.search_line_edit = QLineEdit(self.central_widget)
self.search_line_edit.setPlaceholderText('Search...')
search_grid.addWidget(self.search_line_edit, 0, 0, 1, 3)
self.clear_button = QPushButton('Clear', self.central_widget)
self.clear_button.setFixedWidth(65)
search_grid.addWidget(self.clear_button, 0, 3, 1, 1)
read_option_grid = QGridLayout()
left_layout.addLayout(read_option_grid)
self.from_data_card_check_box = QCheckBox(
'From Data Card', self.central_widget)
search_grid.addWidget(self.from_data_card_check_box, 1, 0, 1, 2)
read_option_grid.addWidget(self.from_data_card_check_box, 0, 0, 1, 2)
self.replot_button = QPushButton('RePlot', self.central_widget)
self.replot_button.setFixedWidth(95)
search_grid.addWidget(self.replot_button, 1, 3, 1, 1)
read_option_grid.addWidget(self.replot_button, 0, 3, 1, 1)
data_group = QButtonGroup()
self.data_radio_button = QRadioButton('data', self.central_widget)
data_group.addButton(self.data_radio_button)
self.data_radio_button.setEnabled(False)
search_grid.addWidget(self.data_radio_button, 2, 0, 1, 1)
read_option_grid.addWidget(self.data_radio_button, 1, 0, 1, 1)
self.sdata_radio_button = QRadioButton('sdata', self.central_widget)
self.sdata_radio_button.setEnabled(False)
data_group.addButton(self.sdata_radio_button)
search_grid.addWidget(self.sdata_radio_button, 2, 1, 1, 1)
read_option_grid.addWidget(self.sdata_radio_button, 1, 1, 1, 1)
color_tip_fmt = ('Set the background color of the plot '
' to {0}')
......@@ -747,9 +759,14 @@ class UIMainWindow(object):
self.open_files_list.itemDoubleClicked.connect(
main_window.open_files_list_item_double_clicked)
self.from_data_card_check_box.clicked.connect(
self.from_data_card_check_box.toggled.connect(
main_window.from_data_card_check_box_clicked)
# Filter files on pressing Enter
self.search_line_edit.returnPressed.connect(
main_window.filter_folder_list
)
self.replot_button.clicked.connect(main_window.replot_loaded_data)
self.background_black_radio_button.toggled.connect(
......
from __future__ import annotations
from PySide2.QtGui import QCloseEvent
from PySide2.QtWidgets import QWidget
class DialogAlreadyOpenedError(Exception):
"""
Error to raise when a child of OneWindowAtATimeDialog already has a window
shown.
"""
pass
class OneWindowAtATimeDialog(QWidget):
"""
A widget type which can only have one instance at a time.
The implementation of this class is similar to that of a singleton widget.
The main difference is that when the sole existing instance of this class
is closed, it is also deleted, allowing another instance to be created.
"""
current_instance: OneWindowAtATimeDialog = None
def __init__(self) -> None:
super().__init__()
# Allow each database dialog to only have one window open at a time.
# If we allow a database dialog to have multiple windows open at one
# time, we would need to somehow sync the windows when one of them
# update the database. While it is possible to do that (keep a list
# of all windows and update them when one window updates the database),
# that would be too much work for a feature that might not be used too
# much.
if self.__class__.current_instance:
raise DialogAlreadyOpenedError()
self.__class__.current_instance = self
def closeEvent(self, event: QCloseEvent) -> None:
"""
When the currently opened window is closed, remove the stored reference
to it.
:param event: the event emitted when the currently opened window is
closed
"""
self.__class__.current_instance = None
super().closeEvent(event)
......@@ -125,9 +125,9 @@ class TestExtractData(unittest.TestCase):
handled in tests for get_chan_label.
"""
result = get_wf_plot_info('CH1')
expected_keys = ('param', 'plotType', 'valueColors', 'height',
'label', 'unit', 'channel')
self.assertTupleEqual(tuple(result.keys()), expected_keys)
expected_keys = {'param', 'plotType', 'valueColors', 'height',
'label', 'unit', 'channel'}
self.assertSetEqual(set(result.keys()), expected_keys)
def test_get_chan_label_good_channel_id(self):
"""
......