diff --git a/HISTORY.rst b/HISTORY.rst index a11eba1dbaaf89fe3edd23aef4f0ce3f323948b5..cc55785c82c0e320b44944cc5b4fac2e0d37e20e 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -6,3 +6,8 @@ History -------- * First release + +2023.1.0.1 +-------- + +* Fixed a problem with SOHViewer not fitting on smaller resolutions diff --git a/conda.recipe/meta.yaml b/conda.recipe/meta.yaml index 62621b47e87de2ea12d6334f6c0437ebba27707b..85c88f6da3497683abd645dd3caf947c640ec730 100644 --- a/conda.recipe/meta.yaml +++ b/conda.recipe/meta.yaml @@ -1,6 +1,6 @@ package: name: sohviewer - version: 2023.1.0.0 + version: 2023.1.0.1 source: path: ../ diff --git a/setup.py b/setup.py index c1e87d5a3c4b8d50f5b49600df7619c752abdb22..f39f957cfba0bbc849b3a904505f8e172cb4304b 100644 --- a/setup.py +++ b/setup.py @@ -51,6 +51,6 @@ setup( name='sohstationviewer', packages=find_packages(include=['sohstationviewer*']), url='https://git.passcal.nmt.edu/software_public/passoft/sohstationviewer', - version='2023.1.0.0', + version='2023.1.0.1', zip_safe=False, ) diff --git a/sohstationviewer/conf/constants.py b/sohstationviewer/conf/constants.py index 887062311ad6caced57d3e2729d62e45f55a108b..eaa03f70e9de9a08e258a826332c182bcb828994 100644 --- a/sohstationviewer/conf/constants.py +++ b/sohstationviewer/conf/constants.py @@ -1,7 +1,7 @@ from typing import Literal # The current version of SOHStationViewer -SOFTWARE_VERSION = '2023.1.0.0' +SOFTWARE_VERSION = '2023.1.0.1' # waveform pattern WF_1ST = 'A-HLM-V' diff --git a/sohstationviewer/images/edit_icon_black_background.png b/sohstationviewer/images/edit_icon_black_background.png new file mode 100644 index 0000000000000000000000000000000000000000..12b52393c45fc4d156d15a83e6f4156542dec75a Binary files /dev/null and b/sohstationviewer/images/edit_icon_black_background.png differ diff --git a/sohstationviewer/images/edit_icon_white_background.png b/sohstationviewer/images/edit_icon_white_background.png new file mode 100644 index 0000000000000000000000000000000000000000..2f374595e8ed857d346ba0af0fc3e37fb1863447 Binary files /dev/null and b/sohstationviewer/images/edit_icon_white_background.png differ diff --git a/sohstationviewer/main.py b/sohstationviewer/main.py index a25d1b15a3f6e3d3e04707a131472287d279c00d..e09e707700dec1f93cad45a377edd9c7b663dbf0 100755 --- a/sohstationviewer/main.py +++ b/sohstationviewer/main.py @@ -6,6 +6,7 @@ import traceback from pathlib import Path from PySide2 import QtWidgets +from PySide2.QtGui import QGuiApplication from PySide2.QtWidgets import QMessageBox from sohstationviewer.view.main_window import MainWindow @@ -14,7 +15,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. @@ -26,47 +26,91 @@ if os_name == 'macOS': os.environ['QT_MAC_WANTS_LAYER'] = '1' -def main(): - # Change the working directory so that relative paths work correctly. +def fix_relative_paths() -> None: + """ + Change the working directory so that the relative paths in the code work + correctly. + """ current_file_path = os.path.abspath(__file__) current_file_dir = Path(current_file_path).parent os.chdir(current_file_dir) + +def resize_windows(main_window: MainWindow): + """ + Resize the plotting windows in the program so that they fit well on all + screen resolutions. + """ + screen_size = QtWidgets.QApplication.primaryScreen().size().toTuple() + screen_width, screen_height = screen_size + main_window.resize(screen_width * 1, screen_height * 1) + main_window.waveform_dlg.resize(screen_width * (2 / 3), + screen_height * (2 / 3)) + main_window.tps_dlg.resize(screen_width * (2 / 3), screen_height * (2 / 3)) + + main_right_x = (main_window.x() + main_window.frameSize().width()) + # This offset is based on the hard-coded geometry previously assigned to + # the waveform and TPS dialogs + wf_tps_x_offset = 50 + # We are moving the TPS dialog so that it is slightly offset from the right + # edge of the main window + tps_dlg_x = main_right_x - main_window.tps_dlg.width() - wf_tps_x_offset + wf_tps_y = 50 + main_window.waveform_dlg.move(wf_tps_x_offset, wf_tps_y) + main_window.tps_dlg.move(tps_dlg_x, wf_tps_y) + + gps_dlg_y = main_window.height() / 5 + main_window.gps_dialog.move(main_window.gps_dialog.y(), gps_dlg_y) + + +def check_if_user_want_to_reset_config() -> bool: + """ + Show a dialog asking the user if they want to reset the config. + :return: whether the user wants to reset the config. + """ + bad_config_dialog = QMessageBox() + bad_config_dialog.setText('Something went wrong when reading the ' + 'config.') + 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 + + +def main(): + # Change the working directory so that relative paths work correctly. + fix_relative_paths() + app = QtWidgets.QApplication(sys.argv) wnd = MainWindow() + config = ConfigProcessor() config.load_config() - need_reset = False + do_reset = None try: config.validate_config() config.apply_config(wnd) except (BadConfigError, ValueError) as e: - bad_config_dialog = QMessageBox() - bad_config_dialog.setText('Something went wrong when reading the ' - 'config.') - 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_() - if reset_choice == QMessageBox.Ok: - need_reset = True - else: - sys.exit(1) - if need_reset: + do_reset = check_if_user_want_to_reset_config() + if do_reset: try: config.reset() except OSError: QMessageBox.critical(None, 'Cannot reset config', - 'Config file cannot be reset. Please ' - 'ensure that it is not opened in another ' - 'program.', + 'Config file cannot be reset. Please ensure ' + 'that it is not opened in another program.', QMessageBox.Close) sys.exit(1) + elif do_reset is not None: + sys.exit(1) config.apply_config(wnd) + + resize_windows(wnd) wnd.show() sys.exit(app.exec_()) diff --git a/sohstationviewer/view/db_config/value_color_helper/value_color_widget.py b/sohstationviewer/view/db_config/value_color_helper/value_color_widget.py new file mode 100644 index 0000000000000000000000000000000000000000..914655d5f2e22b25606e5dad769056aedd2b2287 --- /dev/null +++ b/sohstationviewer/view/db_config/value_color_helper/value_color_widget.py @@ -0,0 +1,136 @@ +import sys +import platform +import os +from pathlib import Path +from typing import Optional + +from PySide2 import QtWidgets, QtGui, QtCore +from PySide2.QtCore import Qt +from PySide2.QtWidgets import QWidget, QDialog, QTextEdit + +from sohstationviewer.view.db_config.value_color_helper.functions import \ + convert_value_color_str, prepare_value_color_html + + +class ValueColorWidget(QTextEdit): + def __init__(self, parent: Optional[QWidget], background: str): + """ + Widget to display valueColors and call a dialog to edit tha value + :param parent: the parent widget + :param background: 'B'/'W': flag indicating background color + """ + QtWidgets.QTextEdit.__init__(self, parent) + self.set_background(background) + # string for value color to be saved in DB + self.value_color_str: str = '' + # dialog that pop up when clicking on edit_button to help edit color + # and value + self.edit_value_color_dialog: Optional[QWidget] = None + # type of channel's plot + self.plot_type: str = '' + + self.setReadOnly(True) + # change cursor to Arrow so user know they can't edit directly + self.viewport().setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor)) + # to see all info + self.setFixedHeight(28) + self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.verticalScrollBar().setDisabled(True) + + self.edit_button = QtWidgets.QToolButton(self) + self.edit_button.setCursor(Qt.PointingHandCursor) + current_file_path = os.path.abspath(__file__) + root_path = Path(current_file_path).parent.parent.parent.parent + if background == 'B': + img_file = f"{root_path}/images/edit_icon_black_background.png" + else: + img_file = f"{root_path}/images/edit_icon_white_background.png" + self.edit_button.setIcon(QtGui.QIcon(img_file)) + self.edit_button.setStyleSheet( + "background: transparent; border: none;") + self.edit_button.clicked.connect(self.on_edit) + + layout = QtWidgets.QHBoxLayout(self) + layout.addWidget(self.edit_button, 0, Qt.AlignRight) + + layout.setSpacing(0) + layout.setMargin(5) + + def set_background(self, background: str): + """ + Set black background for user to have better feeling how the colors + displayed on black background. Text and PlaceholderText's colors + have to be changed to be readable on the black background too. + :param background: 'B'/'W': sign for background color + """ + palette = self.palette() + if background == 'B': + palette.setColor(QtGui.QPalette.Text, Qt.white) + palette.setColor(QtGui.QPalette.Base, Qt.black) + palette.setColor(QtGui.QPalette.PlaceholderText, Qt.lightGray) + self.setPalette(palette) + + def set_value_color(self, plot_type: str, value_color_str: str) \ + -> None: + """ + Set value_color_str, value_color_edit_dialog and display value color + string in html to show color for user to have the feeling. + :param plot_type: type of channel's plot + :param value_color_str: string for value color to be saved in DB + """ + + self.plot_type = plot_type + # Won't need to convert after database's valueColors are changed + self.value_color_str = convert_value_color_str( + plot_type, value_color_str) + value_color_html = prepare_value_color_html(self.value_color_str) + self.setHtml(value_color_html) + + def on_edit(self): + print('edit value color') + + +class TestDialog(QDialog): + def __init__(self): + super(TestDialog, self).__init__(None) + main_layout = QtWidgets.QVBoxLayout() + self.setLayout(main_layout) + + self.value_colorb_widget = ValueColorWidget(self, 'B') + main_layout.addWidget(self.value_colorb_widget) + self.value_colorw_widget = ValueColorWidget(self, 'W') + main_layout.addWidget(self.value_colorw_widget) + + # linesDots + self.value_colorb_widget.set_value_color('linesDots', 'L:R|D:G') + self.value_colorw_widget.set_value_color('linesDots', 'L:R|D:G') + + # triColorLines + # self.value_colorb_widget.set_value_color( + # 'triColorLines', '-1:M|0:R|1:G') + # self.value_colorw_widget.set_value_color( + # 'triColorLines', '-1:M|0:R|1:G') + + # multiColorDotsEqualOnUpperBound + # self.value_colorb_widget.set_value_color( + # 'multiColorDotsEqualOnUpperBound', '0:R|1:Y|2:G|+2:M') + # self.value_colorw_widget.set_value_color( + # 'multiColorDotsEqualOnUpperBound', '0:R|1:Y|2:G|+2:M') + + # multiColorDotsEqualOnLowerBound + # self.value_colorb_widget.set_value_color('multiColorDotsEqualOnLowerBound', + # '3:R|3.3:Y|=3.3:G') + # self.value_colorw_widget.set_value_color('multiColorDotsEqualOnLowerBound', + # '3:R|3.3:Y|=3.3:G') + + +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 = TestDialog() + test.exec_() + sys.exit(app.exec_()) diff --git a/sohstationviewer/view/plotting/gps_plot/gps_dialog.py b/sohstationviewer/view/plotting/gps_plot/gps_dialog.py index ebdc9d4f1500493ea850ff21ae6a40b69196a0ac..c1be4ff05b1d799e85ddd9748ff1f36e2bff72b9 100644 --- a/sohstationviewer/view/plotting/gps_plot/gps_dialog.py +++ b/sohstationviewer/view/plotting/gps_plot/gps_dialog.py @@ -3,7 +3,7 @@ import sys import os import traceback from pathlib import Path -from typing import List, Optional, Literal, Iterable +from typing import List, Optional, Literal, Iterable, Any from PySide2 import QtWidgets, QtCore from matplotlib.axes import Axes @@ -31,7 +31,7 @@ class GPSWidget(QtWidgets.QWidget): # one point for each coordinate. self.unique_gps_points: Optional[List[GPSPoint]] = None - self.fig = Figure(figsize=(6, 6), dpi=100) + self.fig = Figure(figsize=(6, 6), dpi=100, facecolor='#ECECEC') self.canvas = Canvas(self.fig) self.canvas.mpl_connect('pick_event', self.on_pick_event) @@ -156,7 +156,7 @@ class GPSWidget(QtWidgets.QWidget): self.repaint() self.tracking_box.clear() - def on_pick_event(self, event) -> None: + def on_pick_event(self, event) -> Any: """ On a GPS point being picked, display the data of that point on the tracking box. @@ -172,13 +172,13 @@ class GPSWidget(QtWidgets.QWidget): # and longitude follows correspondingly. lat_dir = 'N' if picked_point.latitude > 0 else 'S' long_dir = 'E' if picked_point.longitude > 0 else 'W' - meta_separator = ' ' * 22 - loc_separator = ' ' * 10 + meta_separator = ' ' * 7 + loc_separator = ' ' * 5 msg = ( - f'Mark: {picked_point.last_timemark}{meta_separator}' + f' Mark: {picked_point.last_timemark}{meta_separator}' f'Fix: {picked_point.fix_type}{meta_separator}' f'Sats: {picked_point.num_satellite_used}<br>' - f'Lat: {lat_dir}{abs(picked_point.latitude):.6f}{loc_separator}' + f' Lat: {lat_dir}{abs(picked_point.latitude):.6f}{loc_separator}' f'Long: {long_dir}{abs(picked_point.longitude):.6f}{loc_separator}' f'Elev: {picked_point.height}{picked_point.height_unit}' ) @@ -254,7 +254,7 @@ class GPSDialog(QtWidgets.QWidget): bottom_layout = QtWidgets.QVBoxLayout() bottom_layout.addLayout(button_layout) - self.info_text_browser.setFixedHeight(42) + self.info_text_browser.setFixedHeight(45) bottom_layout.addWidget(self.info_text_browser) main_layout.addLayout(bottom_layout) diff --git a/sohstationviewer/view/plotting/time_power_square/time_power_squared_dialog.py b/sohstationviewer/view/plotting/time_power_square/time_power_squared_dialog.py index 36d2bcde405a21b652770413cf67ab81b20efb03..97244a870fcdd70bee47f311e16419487d2dcc54 100755 --- a/sohstationviewer/view/plotting/time_power_square/time_power_squared_dialog.py +++ b/sohstationviewer/view/plotting/time_power_square/time_power_squared_dialog.py @@ -40,7 +40,6 @@ class TimePowerSquaredDialog(QtWidgets.QWidget): """ self.date_format: str = 'YYYY-MM-DD' - self.setGeometry(50, 50, 1200, 800) self.setWindowTitle("TPS Plot") main_layout = QtWidgets.QVBoxLayout() diff --git a/sohstationviewer/view/plotting/time_power_square/time_power_squared_widget.py b/sohstationviewer/view/plotting/time_power_square/time_power_squared_widget.py index b82e7cb8fe591ff9ad18b4ad829308cb0c7fe491..9b475c7af53f20032d35c8be55351149f367796e 100644 --- a/sohstationviewer/view/plotting/time_power_square/time_power_squared_widget.py +++ b/sohstationviewer/view/plotting/time_power_square/time_power_squared_widget.py @@ -231,7 +231,7 @@ class TimePowerSquaredWidget(plotting_widget.PlottingWidget): ax = self.create_axes(self.plotting_bot, plot_h) ax.spines[['right', 'left', 'top', 'bottom']].set_visible(False) ax.text( - -0.12, 1, + -0.15, 1, f"{get_seismic_chan_label(chan_id)} {c_data['samplerate']}sps", horizontalalignment='left', verticalalignment='top', diff --git a/sohstationviewer/view/plotting/waveform_dialog.py b/sohstationviewer/view/plotting/waveform_dialog.py index 28ad89de67668eccf4bb78efc7c885c0f5381425..f6d8334504aa11c4e6a50ee26f3965984fe8bcba 100755 --- a/sohstationviewer/view/plotting/waveform_dialog.py +++ b/sohstationviewer/view/plotting/waveform_dialog.py @@ -77,7 +77,6 @@ class WaveformDialog(QtWidgets.QWidget): date_format: format for date """ self.date_format: str = 'YYYY-MM-DD' - self.setGeometry(50, 10, 1600, 700) self.setWindowTitle("Raw Data Plot") main_layout = QtWidgets.QVBoxLayout() diff --git a/sohstationviewer/view/ui/main_ui.py b/sohstationviewer/view/ui/main_ui.py index 02e92845edcc28646a4869b7181cb4fd96c5579c..c1e13a3003f4bca9c6f4977aa87da20a96d576ba 100755 --- a/sohstationviewer/view/ui/main_ui.py +++ b/sohstationviewer/view/ui/main_ui.py @@ -7,6 +7,7 @@ from PySide2.QtWidgets import ( QMainWindow, QWidget, QTextBrowser, QPushButton, QLineEdit, QDateEdit, QListWidget, QCheckBox, QRadioButton, QMenu, QLabel, QFrame, QVBoxLayout, QHBoxLayout, QGridLayout, QAbstractItemView, QButtonGroup, + QSplitter, ) from PySide2.QtWidgets import ( QAction, QActionGroup, QShortcut @@ -279,7 +280,6 @@ class UIMainWindow(object): :param main_window: QMainWindow - main GUI for user to interact with """ self.main_window = main_window - main_window.resize(1798, 1110) main_window.setWindowTitle("SOH Station Viewer") self.central_widget = QWidget(main_window) main_window.setCentralWidget(self.central_widget) @@ -294,8 +294,6 @@ class UIMainWindow(object): self.set_first_row(main_layout) self.set_second_row(main_layout) - self.tracking_info_text_browser.setFixedHeight(80) - main_layout.addWidget(self.tracking_info_text_browser) self.create_menu_bar(main_window) self.connect_signals(main_window) self.create_shortcuts(main_window) @@ -352,7 +350,7 @@ class UIMainWindow(object): left_widget = QWidget(self.central_widget) h_layout.addWidget(left_widget) left_widget.setFixedWidth(240) - left_widget.setMinimumHeight(650) + # left_widget.setMinimumHeight(650) left_layout = QVBoxLayout() left_layout.setContentsMargins(0, 0, 0, 0) left_layout.setSpacing(0) @@ -360,12 +358,22 @@ class UIMainWindow(object): self.set_control_column(left_layout) + plot_splitter = QSplitter(QtCore.Qt.Orientation.Vertical) + h_layout.addWidget(plot_splitter, 2) + self.plotting_widget = SOHWidget(self.main_window, self.tracking_info_text_browser, 'SOH', self.main_window) + plot_splitter.addWidget(self.plotting_widget) - h_layout.addWidget(self.plotting_widget, 2) + self.tracking_info_text_browser.setMinimumHeight(60) + self.tracking_info_text_browser.setMaximumHeight(80) + plot_splitter.addWidget(self.tracking_info_text_browser) + tracking_browser_idx = plot_splitter.indexOf( + self.tracking_info_text_browser + ) + plot_splitter.setCollapsible(tracking_browser_idx, False) def set_control_column(self, left_layout): """