From c2f716c0c013ee9b8024900a8f47a86a3e8b57ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Wed, 16 May 2018 02:00:53 -0400 Subject: [PATCH] PR: Move normal weather toolbutton from the hydrograph editor toolbar to the weather data manager toolbar (#220) * Remove dead code * Use more meaningfull name * Specify toolbox size in init arg * Move VSep and HSep to the widgets module * Specify size of OnOffToolButton * Refactor ToolBarWidget * Refactor ToolBarWidget (suite) * Use ToolBarWidget * Move ExportWeatherButton to weather_viewer.py * Remove WeatherViewer from HydroPrint * Code style * Code style * Add WeatherViewer to weather manager * Minor changes to splash messages * Create a SpinBox to show a list of strings * Rework the layout of the data manager * Set the workdir of the WeatherViewer * if __name__ == '__main__': * docstrings * if __name__ == '__main__': * codestyle * Better messages --- gwhat/HydroCalc2.py | 15 +- gwhat/HydroPrint2.py | 37 +---- gwhat/brf_mod/kgs_gui.py | 6 +- gwhat/common/icons.py | 12 +- gwhat/common/styles.py | 11 +- gwhat/common/widgets.py | 15 -- gwhat/gwrecharge/gwrecharge_gui.py | 3 +- gwhat/mainwindow.py | 15 +- gwhat/meteo/dwnld_weather_data.py | 7 +- gwhat/meteo/gapfill_weather_gui.py | 9 +- gwhat/meteo/search_weather_data.py | 6 +- gwhat/meteo/weather_viewer.py | 62 +++++++- gwhat/projet/manager_data.py | 245 +++++++++++++---------------- gwhat/projet/manager_projet.py | 9 +- gwhat/widgets/buttons.py | 31 ++-- gwhat/widgets/layout.py | 17 +- gwhat/widgets/spinboxes.py | 71 +++++++++ 17 files changed, 323 insertions(+), 248 deletions(-) create mode 100644 gwhat/widgets/spinboxes.py diff --git a/gwhat/HydroCalc2.py b/gwhat/HydroCalc2.py index ac3ba57d3..2e5983cb7 100644 --- a/gwhat/HydroCalc2.py +++ b/gwhat/HydroCalc2.py @@ -42,6 +42,7 @@ from gwhat.widgets.buttons import ToolBarWidget from gwhat.brf_mod import BRFManager from gwhat.widgets.buttons import OnOffToolButton +from gwhat.widgets.layout import VSep # mpl.rc('font', **{'family': 'sans-serif', 'sans-serif': ['Arial']}) @@ -246,19 +247,19 @@ def _setup_toolbar(self): self.btn_home.setToolTip('Reset original view.') self.btn_home.clicked.connect(self.home) - self.btn_pan = OnOffToolButton('pan') + self.btn_pan = OnOffToolButton('pan', size='normal') self.btn_pan.setToolTip( 'Pan axes with the left mouse button and zoom with the right') self.btn_pan.sig_value_changed.connect(self.pan_is_active_changed) - self.btn_zoom_to_rect = OnOffToolButton('zoom_to_rect') + self.btn_zoom_to_rect = OnOffToolButton('zoom_to_rect', size='normal') self.btn_pan.setToolTip( "Zoom in to the rectangle with the left mouse button and zoom" " out with the right mouse button.") self.btn_zoom_to_rect.sig_value_changed.connect( self.zoom_is_active_changed) - self.btn_wl_style = OnOffToolButton('showDataDots') + self.btn_wl_style = OnOffToolButton('showDataDots', size='normal') self.btn_wl_style.setToolTip( '

Show water lvl data as dots instead of a continuous line

') self.btn_wl_style.sig_value_changed.connect(self.setup_wl_style) @@ -276,13 +277,13 @@ def _setup_toolbar(self): # dformat: False -> Excel Numeric Date Format # True -> Matplotlib Date Format - self.btn_show_glue = OnOffToolButton('show_glue_wl') + self.btn_show_glue = OnOffToolButton('show_glue_wl', size='normal') self.btn_show_glue.setToolTip( """Show or hide GLUE water level 05/95 envelope.""") self.btn_show_glue.sig_value_changed.connect(self.draw_glue_wl) self.btn_show_glue.setValue(True, silent=True) - self.btn_show_weather = OnOffToolButton('show_meteo') + self.btn_show_weather = OnOffToolButton('show_meteo', size='normal') self.btn_show_weather.setToolTip("""Show or hide weather data.""") self.btn_show_weather.sig_value_changed.connect(self.draw_weather) self.btn_show_weather.setValue(True, silent=True) @@ -405,13 +406,13 @@ def __initUI__(self): self.right_panel.setSpacing(15) - # -------------------------------------------------------- MAIN GRID -- + # ---- Setup the main layout mainGrid = QGridLayout(self) mainGrid.addWidget(toolbar, 0, 0) mainGrid.addWidget(self.fig_frame_widget, 1, 0, 2, 1) - mainGrid.addWidget(myqt.VSep(), 0, 1, 3, 1) + mainGrid.addWidget(VSep(), 0, 1, 3, 1) mainGrid.addWidget(self.right_panel, 0, 2, 2, 1) mainGrid.setContentsMargins(10, 10, 10, 10) # (L, T, R, B) diff --git a/gwhat/HydroPrint2.py b/gwhat/HydroPrint2.py index 7d52fc9b1..77c548000 100644 --- a/gwhat/HydroPrint2.py +++ b/gwhat/HydroPrint2.py @@ -30,15 +30,13 @@ import gwhat.hydrograph4 as hydrograph import gwhat.mplFigViewer3 as mplFigViewer -from gwhat.meteo.weather_viewer import WeatherViewer from gwhat.colors2 import ColorsReader, ColorsSetupWin - from gwhat.common import QToolButtonNormal, QToolButtonSmall from gwhat.common import icons import gwhat.common.widgets as myqt from gwhat.common.utils import find_unique_filename from gwhat.projet.reader_waterlvl import load_waterlvl_measures -from gwhat.widgets.layout import OnOffToggleWidget +from gwhat.widgets.layout import OnOffToggleWidget, VSep from gwhat.gwrecharge.glue import GLUEDataFrameBase @@ -57,8 +55,6 @@ def __init__(self, datamanager, parent=None): self.dmngr.wldsetChanged.connect(self.wldset_changed) self.dmngr.wxdsetChanged.connect(self.wxdset_changed) - self.weather_avg_graph = WeatherViewer(self) - self.page_setup_win = PageSetupWin(self) self.page_setup_win.newPageSetupSent.connect(self.layout_changed) @@ -103,10 +99,6 @@ def __initUI__(self): btn_bestfit_time = QToolButtonNormal(icons.get_icon('fit_x')) btn_bestfit_time.setToolTip('Best fit the time scale') - btn_weather_normals = QToolButtonNormal(icons.get_icon('meteo')) - btn_weather_normals.setToolTip( - 'Show current weather dataset normals...') - self.btn_page_setup = QToolButtonNormal(icons.get_icon('page_setup')) self.btn_page_setup.setToolTip('Show the page setup window') self.btn_page_setup.clicked.connect(self.page_setup_win.show) @@ -143,13 +135,10 @@ def __initUI__(self): # LAYOUT : btn_list = [btn_save, btn_draw, - self.btn_load_layout, self.btn_save_layout, - myqt.VSep(), - btn_bestfit_waterlvl, btn_bestfit_time, - myqt.VSep(), btn_weather_normals, self.btn_page_setup, - btn_color_pick, - myqt.VSep(), - zoom_pan] + self.btn_load_layout, self.btn_save_layout, VSep(), + btn_bestfit_waterlvl, btn_bestfit_time, VSep(), + self.btn_page_setup, btn_color_pick, + VSep(), zoom_pan] subgrid_toolbar = QGridLayout() toolbar_widget = QWidget() @@ -217,7 +206,7 @@ def __initUI__(self): mainGrid = QGridLayout() mainGrid.addWidget(self.grid_layout_widget, 0, 0) - mainGrid.addWidget(myqt.VSep(), 0, 1) + mainGrid.addWidget(VSep(), 0, 1) mainGrid.addWidget(self.right_panel, 0, 2) mainGrid.setContentsMargins(10, 10, 10, 10) # (L, T, R, B) @@ -235,7 +224,6 @@ def __initUI__(self): btn_bestfit_time.clicked.connect(self.best_fit_time) btn_draw.clicked.connect(self.draw_hydrograph) btn_save.clicked.connect(self.select_save_path) - btn_weather_normals.clicked.connect(self.show_weather_averages) # Hydrograph Layout : @@ -452,16 +440,6 @@ def update_colors(self): self.hydrograph.update_colors() self.hydrograph_scrollarea.load_mpl_figure(self.hydrograph) - def show_weather_averages(self): - if self.wxdset is None: - msg = 'Please import a valid weather data file first.' - self.emit_warning(msg) - return - - self.weather_avg_graph.save_fig_dir = self.workdir - self.weather_avg_graph.set_weather_dataset(self.wxdset) - self.weather_avg_graph.show() - # ---- Datasets Handlers @property @@ -632,7 +610,6 @@ def update_graph_layout_parameter(self): # language : self.hydrograph.language = self.language_box.currentText() - self.weather_avg_graph.set_lang(self.language_box.currentText()) # Scales : @@ -1079,7 +1056,7 @@ def show(self): ft.setPointSize(11) app.setFont(ft) - pf = "C:/Users/jsgosselin/Desktop/2018 ACFAS/ACFAS2018/ACFAS2018.gwt" + pf = 'C:/Users/jsgosselin/GWHAT/Projects/Example/Example.gwt' pr = ProjetReader(pf) dm = DataManager() dm.set_projet(pr) diff --git a/gwhat/brf_mod/kgs_gui.py b/gwhat/brf_mod/kgs_gui.py index e3684f5f8..1f25f43be 100644 --- a/gwhat/brf_mod/kgs_gui.py +++ b/gwhat/brf_mod/kgs_gui.py @@ -36,7 +36,7 @@ # ---- Imports: Local import gwhat.common.widgets as myqt -from gwhat.common.widgets import VSep +from gwhat.widgets.layout import VSep, HSep from gwhat.common import StyleDB, QToolButtonNormal, QToolButtonSmall from gwhat.common import icons from gwhat import brf_mod as bm @@ -473,7 +473,7 @@ def __initGUI__(self): self.tbar.addWidget(btn, 1, self.tbar.columnCount()) row = self.tbar.columnCount() - self.tbar.addWidget(myqt.HSep(), 0, 0, 1, row+1) + self.tbar.addWidget(HSep(), 0, 0, 1, row+1) self.tbar.setColumnStretch(row, 100) self.tbar.setContentsMargins(10, 0, 10, 10) # (l, t, r, b) @@ -798,7 +798,7 @@ def __initGUI__(self): layout.addWidget(self._markersize['label'], row, 1) layout.addWidget(self._markersize['widget'], row, 2) row += 1 - layout.addWidget(myqt.HSep(), row, 1, 1, 2) + layout.addWidget(HSep(), row, 1, 1, 2) row += 1 layout.addLayout(axlayout, row, 1, 1, 2) row += 1 diff --git a/gwhat/common/icons.py b/gwhat/common/icons.py index f81fcb557..831e2ab2c 100644 --- a/gwhat/common/icons.py +++ b/gwhat/common/icons.py @@ -89,8 +89,9 @@ 'show_glue_wl': 'show_glue_wl', 'show_meteo': 'show_meteo'} -ICON_SIZES = {'iconSize': (32, 32), - 'iconSize2': (20, 20)} +ICON_SIZES = {'large': (32, 32), + 'normal': (28, 28), + 'small': (20, 20)} def get_icon(name): @@ -98,8 +99,7 @@ def get_icon(name): def get_iconsize(size): - w, h = ICON_SIZES[size] - return QSize(w, h) + return QSize(*ICON_SIZES[size]) class QToolButtonBase(QToolButton): @@ -128,13 +128,13 @@ def __init__(self, Qicon, *args, **kargs): class QToolButtonNormal(QToolButtonBase): def __init__(self, Qicon, *args, **kargs): super(QToolButtonNormal, self).__init__(Qicon, *args, **kargs) - self.setIconSize(QSize(28, 28)) + self.setIconSize(get_iconsize('normal')) class QToolButtonSmall(QToolButtonBase): def __init__(self, Qicon, *args, **kargs): super(QToolButtonSmall, self).__init__(Qicon, *args, **kargs) - self.setIconSize(QSize(20, 20)) + self.setIconSize(get_iconsize('small')) class QToolButtonVRectSmall(QToolButtonBase): diff --git a/gwhat/common/styles.py b/gwhat/common/styles.py index 674525376..03c14069c 100644 --- a/gwhat/common/styles.py +++ b/gwhat/common/styles.py @@ -19,20 +19,14 @@ class StyleDB(object): def __init__(self): - # ---------------------------------------------------------- frame ---- + # ---- frame self.frame = 22 self.HLine = 52 self.VLine = 53 - - self.size1 = 32 - - self.iconSize = QSize(32, 32) - self.iconSize2 = QSize(20, 20) - self.sideBarWidth = 275 - # --------------------------------------------------------- colors ---- + # ----- colors self.red = '#C83737' self.lightgray = '#E6E6E6' @@ -57,7 +51,6 @@ def __init__(self): elif platform.system() == 'Linux': self.fontfamily = "Ubuntu" - # self.fontSize1.setPointSize(11) # 17 = QtGui.QFrame.Box | QtGui.QFrame.Plain diff --git a/gwhat/common/widgets.py b/gwhat/common/widgets.py index 22d89e3a8..dbf081d34 100644 --- a/gwhat/common/widgets.py +++ b/gwhat/common/widgets.py @@ -87,21 +87,6 @@ def __init__(self, *args, **kargs): Qt.AlignVCenter) -# ============================================================ Separators ===== - - -class HSep(QFrame): # horizontal separators - def __init__(self, parent=None): - super(HSep, self).__init__(parent) - self.setFrameStyle(52) - - -class VSep(QFrame): # vertical separators - def __init__(self, parent=None): - super(VSep, self).__init__(parent) - self.setFrameStyle(53) - - # ========================================================= Messsage Box ====== diff --git a/gwhat/gwrecharge/gwrecharge_gui.py b/gwhat/gwrecharge/gwrecharge_gui.py index b98eb9c88..5e58b1f0b 100644 --- a/gwhat/gwrecharge/gwrecharge_gui.py +++ b/gwhat/gwrecharge/gwrecharge_gui.py @@ -20,7 +20,8 @@ # ---- Imports: local from gwhat.widgets.buttons import ExportDataButton -from gwhat.common.widgets import QFrameLayout, QDoubleSpinBox, HSep +from gwhat.common.widgets import QFrameLayout, QDoubleSpinBox +from gwhat.widgets.layout import HSep from gwhat.gwrecharge.gwrecharge_calc2 import RechgEvalWorker from gwhat.gwrecharge.gwrecharge_plot_results import FigureStackManager from gwhat.gwrecharge.glue import GLUEDataFrameBase diff --git a/gwhat/mainwindow.py b/gwhat/mainwindow.py index 782326c5b..5b9491ff8 100644 --- a/gwhat/mainwindow.py +++ b/gwhat/mainwindow.py @@ -43,7 +43,7 @@ app.setFont(ft) from gwhat import __namever__, __appname__ -splash.showMessage("Starting %s." % __namever__) +splash.showMessage("Starting %s..." % __namever__) # ---- Standard library imports @@ -97,10 +97,11 @@ def __init__(self, parent=None): # Setup the project and data managers : - splash.showMessage("Initializing project and data managers.") + splash.showMessage("Initializing project and data managers...") self.pmanager = ProjetManager(self) self.pmanager.currentProjetChanged.connect(self.new_project_loaded) self.dmanager = DataManager(parent=self, pm=self.pmanager) + self.dmanager.setMaximumWidth(250) # Generate the GUI : @@ -123,22 +124,22 @@ def __initUI__(self): # download weather data : - splash.showMessage("Initializing download weather data.") + splash.showMessage("Initializing download weather data...") self.tab_dwnld_data = DwnldWeatherWidget(self) self.tab_dwnld_data.set_workdir(self.projectdir) # gapfill weather data : - splash.showMessage("Initializing gapfill weather data.") + splash.showMessage("Initializing gapfill weather data...") self.tab_fill_weather_data = GapFillWeatherGUI(self) self.tab_fill_weather_data.set_workdir(self.projectdir) # hydrograph : - splash.showMessage("Initializing plot hydrograph.") + splash.showMessage("Initializing plot hydrograph...") self.tab_hydrograph = HydroPrint.HydroprintGUI(self.dmanager) - splash.showMessage("Initializing analyse hydrograph.") + splash.showMessage("Initializing analyse hydrograph...") self.tab_hydrocalc = HydroCalc.WLCalc(self.dmanager) self.tab_hydrocalc.rechg_eval_widget.sig_new_gluedf.connect( self.tab_hydrograph.glue_wl_changed) @@ -156,7 +157,7 @@ def __initUI__(self): # ---- Main Console - splash.showMessage("Initializing main window.") + splash.showMessage("Initializing main window...") self.main_console = QTextEdit() self.main_console.setReadOnly(True) self.main_console.setLineWrapMode(QTextEdit.NoWrap) diff --git a/gwhat/meteo/dwnld_weather_data.py b/gwhat/meteo/dwnld_weather_data.py index fb37e46ec..3072e460c 100644 --- a/gwhat/meteo/dwnld_weather_data.py +++ b/gwhat/meteo/dwnld_weather_data.py @@ -33,6 +33,7 @@ from gwhat.common import StyleDB, QToolButtonNormal, QToolButtonSmall from gwhat.common import icons import gwhat.common.widgets as myqt +from gwhat.widgets.layout import VSep from gwhat.widgets.buttons import DropDownButton from gwhat.common.utils import calc_dist_from_coord from gwhat.meteo.search_weather_data import WeatherStationBrowser @@ -202,14 +203,14 @@ def __initUI__(self): self.btn_selectRaw.setIcon(icons.get_icon('openFile')) self.btn_selectRaw.setToolTip( "Select and concatenate raw weather data files.") - self.btn_selectRaw.setIconSize(icons.get_iconsize('iconSize2')) + self.btn_selectRaw.setIconSize(icons.get_iconsize('small')) self.btn_selectRaw.clicked.connect(self.btn_selectRaw_isClicked) self.btn_saveMerge = QPushButton('Save') self.btn_saveMerge.setToolTip( "Save the concatenated weather dataset in a csv file.") self.btn_saveMerge.setIcon(icons.get_icon('save')) - self.btn_saveMerge.setIconSize(icons.get_iconsize('iconSize2')) + self.btn_saveMerge.setIconSize(icons.get_iconsize('small')) self.btn_saveMerge.clicked.connect(self.btn_saveMerge_isClicked) rightPanel_grid = QGridLayout() @@ -239,7 +240,7 @@ def __initUI__(self): main_grid.addLayout(toolbar, 0, 0) main_grid.addWidget(self.station_table, 1, 0) - main_grid.addWidget(myqt.VSep(), 0, 1, 2, 1) + main_grid.addWidget(VSep(), 0, 1, 2, 1) main_grid.addWidget(display_label, 0, 2) main_grid.addWidget(rightPanel_widg, 1, 2) diff --git a/gwhat/meteo/gapfill_weather_gui.py b/gwhat/meteo/gapfill_weather_gui.py index 14b5a0a54..eda125ae7 100644 --- a/gwhat/meteo/gapfill_weather_gui.py +++ b/gwhat/meteo/gapfill_weather_gui.py @@ -46,6 +46,7 @@ from gwhat.common import icons import gwhat.common.widgets as myqt from gwhat.common.utils import delete_file +from gwhat.widgets.layout import HSep class GapFillWeatherGUI(QWidget): @@ -78,12 +79,12 @@ def __initUI__(self): self.btn_fill = QPushButton('Fill Station') self.btn_fill.setIcon(icons.get_icon('fill_data')) - self.btn_fill.setIconSize(icons.get_iconsize('iconSize2')) + self.btn_fill.setIconSize(icons.get_iconsize('small')) self.btn_fill.setToolTip('

Fill the gaps in the daily weather data ' ' for the selected weather station.

') self.btn_fill_all = QPushButton('Fill All Stations') - self.btn_fill_all.setIconSize(icons.get_iconsize('iconSize2')) + self.btn_fill_all.setIconSize(icons.get_iconsize('small')) self.btn_fill_all.setIcon(icons.get_icon('fill_all_data')) self.btn_fill_all.setToolTip('

Fill the gaps in the daily weather ' ' data for all the weather stations' + @@ -353,11 +354,11 @@ def advanced_settings(self): row += 1 grid_leftPanel.addWidget(self.fillDates_widg, row, 0) row += 1 - grid_leftPanel.addWidget(myqt.HSep(), row, 0) + grid_leftPanel.addWidget(HSep(), row, 0) row += 1 grid_leftPanel.addWidget(self.stack_widget, row, 0) row += 2 - grid_leftPanel.addWidget(myqt.HSep(), row, 0) + grid_leftPanel.addWidget(HSep(), row, 0) row += 1 grid_leftPanel.addWidget(widget_toolbar, row, 0) diff --git a/gwhat/meteo/search_weather_data.py b/gwhat/meteo/search_weather_data.py index a5767edce..0f7dce5cb 100644 --- a/gwhat/meteo/search_weather_data.py +++ b/gwhat/meteo/search_weather_data.py @@ -259,20 +259,20 @@ def __initUI__(self): self.btn_addSta = btn_addSta = QPushButton('Add') btn_addSta.setIcon(icons.get_icon('add2list')) - btn_addSta.setIconSize(icons.get_iconsize('iconSize2')) + btn_addSta.setIconSize(icons.get_iconsize('small')) btn_addSta.setToolTip('Add selected found weather stations to the ' 'current list of weather stations.') btn_addSta.clicked.connect(self.btn_addSta_isClicked) btn_save = QPushButton('Save') btn_save.setIcon(icons.get_icon('save')) - btn_save.setIconSize(icons.get_iconsize('iconSize2')) + btn_save.setIconSize(icons.get_iconsize('small')) btn_save.setToolTip('Save current found stations info in a csv file.') btn_save.clicked.connect(self.btn_save_isClicked) self.btn_fetch = btn_fetch = QPushButton('Fetch') btn_fetch.setIcon(icons.get_icon('refresh')) - btn_fetch.setIconSize(icons.get_iconsize('iconSize2')) + btn_fetch.setIconSize(icons.get_iconsize('small')) btn_fetch.setToolTip("Updates the climate station database by" " fetching it again from the ECCC ftp server.") btn_fetch.clicked.connect(self.btn_fetch_isClicked) diff --git a/gwhat/meteo/weather_viewer.py b/gwhat/meteo/weather_viewer.py index 609ecfa1c..3747f71b3 100644 --- a/gwhat/meteo/weather_viewer.py +++ b/gwhat/meteo/weather_viewer.py @@ -22,6 +22,7 @@ from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg from PyQt5.QtCore import Qt, QSize +from PyQt5.QtCore import pyqtSlot as QSlot from PyQt5.QtWidgets import (QMenu, QToolButton, QGridLayout, QWidget, QFileDialog, QApplication, QTableWidget, QTableWidgetItem, QLabel, QHBoxLayout, @@ -33,11 +34,13 @@ from gwhat.common import StyleDB from gwhat.common import icons from gwhat.common.icons import QToolButtonVRectSmall, QToolButtonNormal -from gwhat.common.widgets import DialogWindow, VSep +from gwhat.common.widgets import DialogWindow +from gwhat.widgets.layout import VSep from gwhat.widgets.buttons import RangeSpinBoxes from gwhat.meteo.weather_reader import calcul_monthly_normals from gwhat.common.utils import save_content_to_file -from gwhat.projet.manager_data import ExportWeatherButton +from gwhat.meteo.weather_reader import WXDataFrameBase +from gwhat.widgets.buttons import ExportDataButton mpl.rc('font', **{'family': 'sans-serif', 'sans-serif': ['Arial']}) @@ -47,17 +50,16 @@ class WeatherViewer(DialogWindow): GUI that allows to plot weather normals, save the graphs to file, see various stats about the dataset, etc... """ - def __init__(self, parent=None): + def __init__(self, parent=None, workdir=None): super(WeatherViewer, self).__init__(parent, False, False) self.wxdset = None self.normals = None - - self.save_fig_dir = os.getcwd() self.meteo_dir = os.getcwd() self.language = 'English' self.__initUI__() + self.set_workdir(workdir) def __initUI__(self): self.setWindowTitle('Weather Averages') @@ -168,6 +170,11 @@ def set_lang(self, lang): self.fig_weather_normals.set_lang(lang) self.fig_weather_normals.draw() + def set_workdir(self, workdir): + """Set the working directory of the widget.""" + self.save_fig_dir = os.getcwd() if workdir is None else workdir + self.btn_export.set_workdir(self.save_fig_dir) + def set_weather_dataset(self, wxdset): """ Generate the graph, update the table, and update the GUI for @@ -878,6 +885,51 @@ def calcul_height(self): return h +class ExportWeatherButton(ExportDataButton): + """ + A toolbutton with a popup menu that handles the export of the weather + dataset in various format. + """ + MODEL_TYPE = WXDataFrameBase + TOOLTIP = "Export weather data." + + def __init__(self, model=None, workdir=None, parent=None): + super(ExportWeatherButton, self).__init__(model, workdir, parent) + + def setup_menu(self): + """Setup the menu of the button tailored to the model.""" + super(ExportWeatherButton, self).setup_menu() + self.menu().addAction('Export daily time series as...', + lambda: self.select_export_file('daily')) + self.menu().addAction('Export monthly time series as...', + lambda: self.select_export_file('monthly')) + self.menu().addAction('Export yearly time series as...', + lambda: self.select_export_file('yearly')) + + # ---- Export Time Series + + @QSlot(str) + def select_export_file(self, time_frame): + """ + Prompt a dialog to select a file and save the weather data time series + to a file in the specified format and time frame. + """ + fname = self.select_savefilename( + 'Export %s' % time_frame, + 'Weather%s_%s' % (time_frame.capitalize(), + self.model['Station Name']), + '*.xlsx;;*.xls;;*.csv') + + if fname: + QApplication.setOverrideCursor(Qt.WaitCursor) + try: + self.model.export_dataset_to_file(fname, time_frame) + except PermissionError: + self.show_permission_error() + self.select_export_file(time_frame) + QApplication.restoreOverrideCursor() + + # %% if __name__ == '__main__' if __name__ == '__main__': diff --git a/gwhat/projet/manager_data.py b/gwhat/projet/manager_data.py index bd4231c47..d92a483e1 100644 --- a/gwhat/projet/manager_data.py +++ b/gwhat/projet/manager_data.py @@ -16,23 +16,22 @@ from PyQt5.QtCore import Qt, QCoreApplication, QSize from PyQt5.QtCore import pyqtSignal as QSignal -from PyQt5.QtCore import pyqtSlot as QSlot -from PyQt5.QtWidgets import (QWidget, QComboBox, QGridLayout, QTextEdit, - QLabel, QMessageBox, QLineEdit, QPushButton, - QFileDialog, QApplication, QDialog, QMenu, - QGroupBox) +from PyQt5.QtWidgets import (QWidget, QComboBox, QGridLayout, QLabel, + QMessageBox, QLineEdit, QPushButton, + QFileDialog, QApplication, QDialog, QGroupBox) -# ---- Imports: Local Libraries +# ---- Local library imports -from gwhat.common.icons import QToolButtonSmall, QToolButtonBase +from gwhat.meteo.weather_viewer import WeatherViewer, ExportWeatherButton +from gwhat.common.icons import QToolButtonSmall from gwhat.common import icons import gwhat.common.widgets as myqt from gwhat.hydrograph4 import LatLong2Dist import gwhat.projet.reader_waterlvl as wlrd from gwhat.projet.reader_projet import INVALID_CHARS, is_dsetname_valid import gwhat.meteo.weather_reader as wxrd -from gwhat.meteo.weather_reader import WXDataFrameBase -from gwhat.widgets.buttons import ExportDataButton +from gwhat.widgets.buttons import ToolBarWidget +from gwhat.widgets.spinboxes import StrSpinBox class DataManager(QWidget): @@ -49,6 +48,8 @@ def __init__(self, parent=None, projet=None, pm=None, pytesting=False): self.setWindowIcon(icons.get_icon('master')) self.setMinimumWidth(250) + self.weather_avg_graph = None + self.new_waterlvl_win = NewDatasetDialog( 'water level', parent, projet) self.new_waterlvl_win.sig_new_dataset_imported.connect( @@ -59,16 +60,26 @@ def __init__(self, parent=None, projet=None, pm=None, pytesting=False): self.new_weather_win.sig_new_dataset_imported.connect( self.new_wxdset_imported) - self.__initUI__() + self.setup_manager() self.set_projet(projet) if pm: pm.currentProjetChanged.connect(self.set_projet) self.set_projet(pm.projet) - def __initUI__(self): + def setup_manager(self): + """Setup the layout of the manager.""" + layout = QGridLayout(self) + layout.setSpacing(5) + layout.setContentsMargins(0, 0, 0, 0) - # ---- Water Level Dataset Toolbar + layout.addWidget(self.setup_wldset_mngr(), 0, 0) + layout.addWidget(self.setup_wxdset_mngr(), 2, 0) + + def setup_wldset_mngr(self): + """Setup the manager for the water level datasets.""" + + # ---- Toolbar self.wldsets_cbox = QComboBox() self.wldsets_cbox.currentIndexChanged.connect(self.update_wldset_info) @@ -82,22 +93,30 @@ def __initUI__(self): self.btn_del_wldset.setToolTip('Delete current dataset.') self.btn_del_wldset.clicked.connect(self.del_current_wldset) - wltb = QGridLayout() - wltb.setContentsMargins(0, 0, 0, 0) + wl_toolbar = ToolBarWidget() + for widg in [self.btn_load_wl, self.btn_del_wldset]: + wl_toolbar.addWidget(widg) + + # ---- Info Box + + self.well_info_widget = StrSpinBox() + + # ---- Main Layout - widgets = [self.wldsets_cbox, self.btn_load_wl, self.btn_del_wldset] - for col, widg in enumerate(widgets): - wltb.addWidget(widg, 0, col) + grpbox = QGroupBox('Water Level Dataset : ') + layout = QGridLayout(grpbox) + layout.setSpacing(5) - # ---- Water Level Dataset Info Box + layout.addWidget(self.wldsets_cbox, 1, 0) + layout.addWidget(self.well_info_widget, 2, 0) + layout.addWidget(wl_toolbar, 3, 0) - self.well_info_widget = QTextEdit() - self.well_info_widget.setReadOnly(True) - self.well_info_widget.setFixedHeight(100) + return grpbox - # ---- Weather Dataset Toolbar + def setup_wxdset_mngr(self): + """Setup the manager for the weather datasets.""" - # Generate the widgets : + # ---- Toolbar self.wxdsets_cbox = QComboBox() self.wxdsets_cbox.currentIndexChanged.connect(self.update_wxdset_info) @@ -116,44 +135,35 @@ def __initUI__(self): ' from the observation well.

') btn_closest_meteo.clicked.connect(self.set_closest_wxdset) + btn_weather_normals = QToolButtonSmall(icons.get_icon('meteo')) + btn_weather_normals.setToolTip( + "Show the normals for the current weather dataset.") + btn_weather_normals.clicked.connect(self.show_weather_normals) + self.btn_export_weather = ExportWeatherButton(workdir=self.workdir) self.btn_export_weather.setIconSize(QSize(20, 20)) - # Generate the layout : - - wxtb = QGridLayout() - wxtb.setContentsMargins(0, 0, 0, 0) - - widgets = [self.wxdsets_cbox, self.btn_load_meteo, self.btn_del_wxdset, - btn_closest_meteo, self.btn_export_weather] - - for col, widg in enumerate(widgets): - wxtb.addWidget(widg, 0, col) + wx_toolbar = ToolBarWidget() + for widg in [self.btn_load_meteo, + self.btn_del_wxdset, btn_closest_meteo, + btn_weather_normals, self.btn_export_weather]: + wx_toolbar.addWidget(widg) - # Weather Dataset Info Box + # ---- Info Box - self.meteo_info_widget = QTextEdit() - self.meteo_info_widget.setReadOnly(True) - self.meteo_info_widget.setFixedHeight(100) + self.meteo_info_widget = StrSpinBox() # ---- Main Layout - layout = QGridLayout() - - layout.addWidget(QLabel('Water Level Dataset :'), 1, 0) - layout.addLayout(wltb, 2, 0) - layout.addWidget(self.well_info_widget, 3, 0) - - layout.setRowMinimumHeight(4, 10) - - layout.addWidget(QLabel('Weather Dataset :'), 5, 0) - layout.addLayout(wxtb, 6, 0) - layout.addWidget(self.meteo_info_widget, 7, 0) - + grpbox = QGroupBox('Weather Dataset : ') + layout = QGridLayout(grpbox) layout.setSpacing(5) - layout.setContentsMargins(0, 0, 0, 0) - self.setLayout(layout) + layout.addWidget(self.wxdsets_cbox, 1, 0) + layout.addWidget(self.meteo_info_widget, 2, 0) + layout.addWidget(wx_toolbar, 3, 0) + + return grpbox @property def workdir(self): @@ -237,33 +247,38 @@ def update_wldsets(self, name=None): self.wldsets_cbox.blockSignals(False) def update_wldset_info(self): - self.well_info_widget.clear() + """Update the infos of the wldset.""" wldset = self.get_current_wldset() if wldset is not None: - name = wldset['Well'] - lat = wldset['Latitude'] - lon = wldset['Longitude'] - alt = wldset['Elevation'] - mun = wldset['Municipality'] - - html = wlrd.generate_HTML_table(name, lat, lon, alt, mun) - self.well_info_widget.setText(html) + model = ["Well : %s" % wldset['Well'], + "Well ID : %s" % wldset['Well ID'], + "Latitude : %0.3f°" % wldset['Latitude'], + "Longitude : %0.3f°" % wldset['Longitude'], + "Elevation : %0.1f m" % wldset['Elevation'], + "Municipality : %s" % wldset['Municipality'], + "Province : %s" % wldset['Province']] + else: + model = None + self.well_info_widget.set_model(model) def wldset_changed(self): - """Handles when the currently selected water level dataset changed.""" + """Handle when the currently selected water level dataset changed.""" self.wldsetChanged.emit(self.get_current_wldset()) def get_current_wldset(self): + """Return the currently selected water level dataset.""" if self.wldsets_cbox.currentIndex() == -1: return None else: return self.projet.get_wldset(self.wldsets_cbox.currentText()) def del_current_wldset(self): + """Delete the currently selected water level dataset.""" if self.wldsets_cbox.count() > 0: name = self.wldsets_cbox.currentText() - msg = ('Do you want to delete the dataset %s? ' + - 'All data will be deleted from the project database, ' + + msg = ('Do you want to delete the dataset %s?' + '

' + 'All data will be deleted from the project database, ' 'but the original data files will be preserved') % name reply = QMessageBox.question( self, 'Delete current dataset', msg, @@ -322,43 +337,41 @@ def update_wxdsets(self, name=None, silent=False): self.wxdsets_cbox.blockSignals(False) def update_wxdset_info(self): - self.meteo_info_widget.clear() - if self.wxdsets_cbox.count() > 0: - wxdset = self.get_current_wxdset() - - staname = wxdset['Station Name'] - lat = wxdset['Latitude'] - lon = wxdset['Longitude'] - alt = wxdset['Elevation'] - prov = wxdset['Province'] - climID = wxdset['Climate Identifier'] - - html = wxrd.generate_weather_HTML(staname, prov, lat, - climID, lon, alt) - self.meteo_info_widget.setText(html) + """Update the infos of the wxdset.""" + wxdset = self.get_current_wxdset() + if wxdset is not None: + model = ["Station : %s" % wxdset['Station Name'], + "Climate ID : %s" % wxdset['Climate Identifier'], + "Latitude : %0.3f°" % wxdset['Latitude'], + "Longitude : %0.3f°" % wxdset['Longitude'], + "Elevation : %0.1f m" % wxdset['Elevation'], + "Province : %s" % wxdset['Province']] + else: + model = None + self.meteo_info_widget.set_model(model) def wxdset_changed(self): - """Handles when the currently selected weather dataset changed.""" + """Handle when the currently selected weather dataset changed.""" self.btn_export_weather.set_model(self.get_current_wxdset()) self.wxdsetChanged.emit(self.get_current_wxdset()) def del_current_wxdset(self): + """Delete the currently selected weather dataset.""" if self.wxdsets_cbox.count() > 0: name = self.wxdsets_cbox.currentText() - msg = ('Do you want to delete the weather dataset %s? ' + - 'All data will be deleted from the project database, ' + + msg = ('Do you want to delete the weather dataset %s?' + '

' + 'All data will be deleted from the project database, ' 'but the original data files will be preserved') % name reply = QMessageBox.question( self, 'Delete current dataset', msg, QMessageBox.Yes | QMessageBox.No) - if reply == QMessageBox.No: - return - - self.projet.del_wxdset(name) - self.update_wxdsets() - self.update_wxdset_info() - self.wxdset_changed() + if reply == QMessageBox.Yes: + self.projet.del_wxdset(name) + self.update_wxdsets() + self.update_wxdset_info() + self.wxdset_changed() def get_current_wxdset(self): """Return the currently selected weather dataset dataframe.""" @@ -397,6 +410,17 @@ def set_closest_wxdset(self): self.set_current_wxdset(closest) + def show_weather_normals(self): + """Show the weather normals for the current weather dataset.""" + if self.get_current_wxdset() is None: + return + if self.weather_avg_graph is None: + self.weather_avg_graph = WeatherViewer() + + self.weather_avg_graph.set_workdir(self.workdir) + self.weather_avg_graph.set_weather_dataset(self.get_current_wxdset()) + self.weather_avg_graph.show() + class NewDatasetDialog(QDialog): """ @@ -753,57 +777,12 @@ def close(self): self.update_gui() -class ExportWeatherButton(ExportDataButton): - """ - A toolbutton with a popup menu that handles the export of the weather - dataset in various format. - """ - MODEL_TYPE = WXDataFrameBase - TOOLTIP = "Export weather data." - - def __init__(self, model=None, workdir=None, parent=None): - super(ExportWeatherButton, self).__init__(model, workdir, parent) - - def setup_menu(self): - """Setup the menu of the button tailored to the model.""" - super(ExportWeatherButton, self).setup_menu() - self.menu().addAction('Export daily time series as...', - lambda: self.select_export_file('daily')) - self.menu().addAction('Export monthly time series as...', - lambda: self.select_export_file('monthly')) - self.menu().addAction('Export yearly time series as...', - lambda: self.select_export_file('yearly')) - - # ---- Export Time Series - - @QSlot(str) - def select_export_file(self, time_frame): - """ - Prompt a dialog to select a file and save the weather data time series - to a file in the specified format and time frame. - """ - fname = self.select_savefilename( - 'Export %s' % time_frame, - 'Weather%s_%s' % (time_frame.capitalize(), - self.model['Station Name']), - '*.xlsx;;*.xls;;*.csv') - - if fname: - QApplication.setOverrideCursor(Qt.WaitCursor) - try: - self.model.export_dataset_to_file(fname, time_frame) - except PermissionError: - self.show_permission_error() - self.select_export_file(time_frame) - QApplication.restoreOverrideCursor() - - # %% if __name__ == '__main__' if __name__ == '__main__': from reader_projet import ProjetReader import sys - projet = ProjetReader("C:/Users/User/gwhat/Projects/Example/Example.gwt") + projet = ProjetReader("C:/Users/jsgosselin/gwhat/Projects/Example/Example.gwt") app = QApplication(sys.argv) diff --git a/gwhat/projet/manager_projet.py b/gwhat/projet/manager_projet.py index 8736ccf70..a26704bca 100644 --- a/gwhat/projet/manager_projet.py +++ b/gwhat/projet/manager_projet.py @@ -30,6 +30,7 @@ from gwhat.projet.manager_data import DataManager from gwhat.projet.reader_waterlvl import init_waterlvl_measures import gwhat.common.widgets as myqt +from gwhat.widgets.layout import VSep, HSep from gwhat import __namever__ @@ -213,7 +214,7 @@ def __initUI__(self): loc_coord.addWidget(QLabel('North'), row, 3) loc_coord.setColumnStretch(4, 100) - loc_coord.addWidget(myqt.VSep(), row, 5) + loc_coord.addWidget(VSep(), row, 5) loc_coord.setColumnStretch(6, 100) loc_coord.addWidget(QLabel('Longitude :'), row, 7) @@ -239,7 +240,7 @@ def __initUI__(self): btn_browse = QToolButton() btn_browse.setAutoRaise(True) btn_browse.setIcon(icons.get_icon('openFolder')) - btn_browse.setIconSize(icons.get_iconsize('iconSize2')) + btn_browse.setIconSize(icons.get_iconsize('small')) btn_browse.setToolTip('Browse...') btn_browse.setFocusPolicy(Qt.NoFocus) btn_browse.clicked.connect(self.browse_saveIn_folder) @@ -282,9 +283,9 @@ def __initUI__(self): main_layout = QGridLayout(self) main_layout.addLayout(projet_info, 0, 0) - main_layout.addWidget(myqt.HSep(), 1, 0) + main_layout.addWidget(HSep(), 1, 0) main_layout.addLayout(loc_coord, 2, 0) - main_layout.addWidget(myqt.HSep(), 3, 0) + main_layout.addWidget(HSep(), 3, 0) main_layout.addLayout(browse, 4, 0) main_layout.addLayout(toolbar, 5, 0) diff --git a/gwhat/widgets/buttons.py b/gwhat/widgets/buttons.py index 95b45d0e0..fca27c963 100644 --- a/gwhat/widgets/buttons.py +++ b/gwhat/widgets/buttons.py @@ -21,36 +21,33 @@ from PyQt5.QtCore import pyqtSlot as QSlot from PyQt5.QtCore import QSize, Qt, QEvent from PyQt5.QtWidgets import (QApplication, QDoubleSpinBox, QFileDialog, - QListWidget, QMenu, QMessageBox, QStyle, - QToolButton, QWidget, QToolBar) + QGridLayout, QListWidget, QMenu, QMessageBox, + QStyle, QToolButton, QWidget) # ---- Local imports from gwhat.common.icons import QToolButtonBase from gwhat.common import icons from gwhat.common.utils import find_unique_filename +from gwhat.widgets.layout import VSep -class ToolBarWidget(QToolBar): +class ToolBarWidget(QWidget): """A standard toolbar with some layout specifics.""" def __init__(self, parent=None): super(ToolBarWidget, self).__init__(parent) - self.setIconSize(QSize(28, 28)) - self.layout().setContentsMargins(0, 0, 0, 0) - self.layout().setSpacing(5) + layout = QGridLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(5) def addWidget(self, widget): - """Qt method override.""" + """Add the widget to the toolbar layout.""" + lay = self.layout() if widget is None: - super(ToolBarWidget, self).addSeparator() - elif isinstance(widget, QToolButton): - # This is required because it seems that autoRaise is set to True - # automatically when adding a widget to the toolbar. - auto_raise = widget.autoRaise() - super(ToolBarWidget, self).addWidget(widget) - widget.setAutoRaise(auto_raise) - else: - super(ToolBarWidget, self).addWidget(widget) + widget = VSep() + lay.setColumnStretch(lay.columnCount()-1, 0) + lay.addWidget(widget, 0, lay.columnCount()) + lay.setColumnStretch(lay.columnCount(), 100) class SmartSpinBox(QDoubleSpinBox): @@ -391,7 +388,7 @@ def __init__(self, icon, icon_raised=None, size=None, parent=None): super(OnOffToolButton, self).__init__(self._icon, parent) self.installEventFilter(self) if size is not None: - self.setIconSize(QSize(*size)) + self.setIconSize(icons.get_iconsize(size)) def eventFilter(self, widget, event): if event.type() == QEvent.MouseButtonPress: diff --git a/gwhat/widgets/layout.py b/gwhat/widgets/layout.py index 0b58f5134..1af44f7f7 100644 --- a/gwhat/widgets/layout.py +++ b/gwhat/widgets/layout.py @@ -9,7 +9,8 @@ # ---- Third party imports -from PyQt5.QtWidgets import QGridLayout, QLabel, QWidget, QRadioButton +from PyQt5.QtWidgets import (QGridLayout, QLabel, QWidget, QRadioButton, + QFrame) class OnOffToggleWidget(QWidget): @@ -45,3 +46,17 @@ def set_value(self, value): self.toggle_on.toggle() else: self.toggle_off.toggle() + + +class HSep(QFrame): + """An horizontal frame separator.""" + def __init__(self, parent=None): + super(HSep, self).__init__(parent) + self.setFrameStyle(52) + + +class VSep(QFrame): + """A vertical frame separator.""" + def __init__(self, parent=None): + super(VSep, self).__init__(parent) + self.setFrameStyle(53) diff --git a/gwhat/widgets/spinboxes.py b/gwhat/widgets/spinboxes.py new file mode 100644 index 000000000..05280a1fe --- /dev/null +++ b/gwhat/widgets/spinboxes.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- + +# Copyright © 2014-2018 GWHAT Project Contributors +# https://github.com/jnsebgosselin/gwhat +# +# This file is part of GWHAT (Ground-Water Hydrograph Analysis Toolbox). +# Licensed under the terms of the GNU General Public License. + +# ---- Imports: Standard Libraries + +import sys +import os +import os.path as osp +from abc import abstractmethod + +# ---- Imports: Third Parties + +from PyQt5.QtWidgets import (QApplication, QDoubleSpinBox, QFileDialog, + QGridLayout, QListWidget, QMenu, QMessageBox, + QStyle, QToolButton, QWidget, QSpinBox, + QLineEdit) + + +class StrSpinBox(QSpinBox): + + def __init__(self, model=None): + super(StrSpinBox, self).__init__() + self.set_model(model) + self.setValue(0) + self.setWrapping(True) + self.lineEdit().setReadOnly(True) + + @property + def model(self): + """Return the data model of the spin box.""" + return self.__model + + def set_model(self, model): + """Set the data model of the spin box.""" + if model is None: + self.__model = None + self.setMaximum(0) + else: + if isinstance(model, list): + self.__model = model + self.setMaximum(len(model)-1) + else: + raise ValueError("The model must be a dict") + self.setEnabled(self.__model is not None) + + # Force a refresh of the spinbox when the model changes : + self.setValue(self.value()) + + def textFromValue(self, value): + """Qt method override.""" + if self.model is not None: + return str(self.model[value]) + else: + return ' ' + + +# %% if __name__ == '__main__' + +if __name__ == '__main__': + app = QApplication(sys.argv) + + model = {'test': 'patate', 'test2': 'orange'} + text_sp = DictSpinBox(model) + text_sp.show() + + sys.exit(app.exec_())