diff --git a/src/python/dm/aps_beamline_tools/gui/dmStationUi.py b/src/python/dm/aps_beamline_tools/gui/dmStationUi.py index dd38696e3794a966e232a27ca9ff9e16ea1b3c73..1be8bbc48aa3ce5a16361a0680d92e2fb893b31d 100755 --- a/src/python/dm/aps_beamline_tools/gui/dmStationUi.py +++ b/src/python/dm/aps_beamline_tools/gui/dmStationUi.py @@ -4,13 +4,15 @@ from __future__ import print_function import sys import os -from PyQt4.QtGui import QMainWindow, QStackedLayout, QApplication, QWidget, QTabWidget, QSplashScreen, QPixmap, QMenu, QAction, QCursor, QMessageBox, QAbstractButton +from PyQt4.QtGui import QMainWindow, QStackedLayout, QApplication, QWidget, QTabWidget, QSplashScreen, QPixmap, QMenu, \ + QAction, QCursor, QMessageBox, QAbstractButton, QIcon from PyQt4.QtCore import Qt, QTimer from time import sleep, time import smtplib from email.mime.text import MIMEText from traceback import format_tb +from dm.aps_beamline_tools.gui.settingsTab import SettingsTab from dm.common.constants import dmStatus from dm.common.exceptions.dmException import DmException from dm.common.exceptions.configurationError import ConfigurationError @@ -104,6 +106,7 @@ class DmStationUi(QMainWindow): self.fileTab = FileTab(self.stationName, self) self.workflowTab = WorkflowTab(self.stationName, self) self.processingJobsTab = ProcessingJobsTab(self.stationName, self) + self.settingsTab = SettingsTab(self) # Add the windows to the stack. self.stackedLayout.addWidget(self.experimentsTab) @@ -124,6 +127,19 @@ class DmStationUi(QMainWindow): # Hide until ready for release self.tabs.addTab(self.workflowTab, "Workflows") self.tabs.addTab(self.processingJobsTab, "Processing Jobs") + homeDir = os.path.dirname(sys.argv[0]) + settingsIcon = QIcon(homeDir + '/images/settings.svg') + self.tabs.addTab(self.settingsTab, settingsIcon, "Settings") + + self.refreshDaqsTimer = QTimer() + self.refreshDaqsTimer.timeout.connect(self.refreshDaqs) + self.refreshUploadsTimer = QTimer() + self.refreshUploadsTimer.timeout.connect(self.refreshUploads) + self.refreshExperimentsTimer = QTimer() + self.refreshExperimentsTimer.timeout.connect(self.refreshExperiments) + self.refreshJobsTimer = QTimer() + self.refreshJobsTimer.timeout.connect(self.refreshJobs) + self.setUpRefreshTimers() # Set a central widget to hold everything self.setCentralWidget(self.tabs) @@ -204,6 +220,34 @@ class DmStationUi(QMainWindow): def clearSelection(self, table): table.clearSelection() + def setUpRefreshTimers(self): + self.refreshDaqsTimer.stop() + self.refreshExperimentsTimer.stop() + self.refreshUploadsTimer.stop() + + self._startRefreshTimer(self.refreshDaqsTimer, self.settingsTab.fetchRefCycleDaqs()) + self._startRefreshTimer(self.refreshUploadsTimer, self.settingsTab.fetchRefCycleUploads()) + self._startRefreshTimer(self.refreshExperimentsTimer, self.settingsTab.fetchRefCycleExperiments()) + self._startRefreshTimer(self.refreshJobsTimer, self.settingsTab.fetchRefCycleJobs()) + + def _startRefreshTimer(self, timer, seconds): + # 0 means that no refresh cycle will occur + if seconds != 0: + interval = seconds * 1000 + timer.start(interval) + + def refreshExperiments(self): + self.experimentsTab.updateList() + + def refreshDaqs(self): + self.daqsTab.updateList() + + def refreshUploads(self): + self.uploadsTab.updateList() + + def refreshJobs(self): + self.processingJobsTab.updateList() + # Refreshes the experiments, DAQ, and Uploads tables. def refreshTables(self): self.experimentsTab.updateList() @@ -292,10 +336,6 @@ if __name__ == "__main__": sleep(0.001) app.processEvents() window = DmStationUi() - # Calls refreshTables every minute - timer = QTimer() - timer.timeout.connect(window.refreshTables) - timer.start(60000) splashScreen.finish(window) window.show() window.raise_() diff --git a/src/python/dm/aps_beamline_tools/gui/images/settings.svg b/src/python/dm/aps_beamline_tools/gui/images/settings.svg new file mode 100644 index 0000000000000000000000000000000000000000..672eed9b22067dcf00034a0a097a12e0033408f4 --- /dev/null +++ b/src/python/dm/aps_beamline_tools/gui/images/settings.svg @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="iso-8859-1"?> + +<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + viewBox="0 0 478.703 478.703" style="enable-background:new 0 0 478.703 478.703;" xml:space="preserve"> +<g> + <g> + <path d="M454.2,189.101l-33.6-5.7c-3.5-11.3-8-22.2-13.5-32.6l19.8-27.7c8.4-11.8,7.1-27.9-3.2-38.1l-29.8-29.8 + c-5.6-5.6-13-8.7-20.9-8.7c-6.2,0-12.1,1.9-17.1,5.5l-27.8,19.8c-10.8-5.7-22.1-10.4-33.8-13.9l-5.6-33.2 + c-2.4-14.3-14.7-24.7-29.2-24.7h-42.1c-14.5,0-26.8,10.4-29.2,24.7l-5.8,34c-11.2,3.5-22.1,8.1-32.5,13.7l-27.5-19.8 + c-5-3.6-11-5.5-17.2-5.5c-7.9,0-15.4,3.1-20.9,8.7l-29.9,29.8c-10.2,10.2-11.6,26.3-3.2,38.1l20,28.1 + c-5.5,10.5-9.9,21.4-13.3,32.7l-33.2,5.6c-14.3,2.4-24.7,14.7-24.7,29.2v42.1c0,14.5,10.4,26.8,24.7,29.2l34,5.8 + c3.5,11.2,8.1,22.1,13.7,32.5l-19.7,27.4c-8.4,11.8-7.1,27.9,3.2,38.1l29.8,29.8c5.6,5.6,13,8.7,20.9,8.7c6.2,0,12.1-1.9,17.1-5.5 + l28.1-20c10.1,5.3,20.7,9.6,31.6,13l5.6,33.6c2.4,14.3,14.7,24.7,29.2,24.7h42.2c14.5,0,26.8-10.4,29.2-24.7l5.7-33.6 + c11.3-3.5,22.2-8,32.6-13.5l27.7,19.8c5,3.6,11,5.5,17.2,5.5l0,0c7.9,0,15.3-3.1,20.9-8.7l29.8-29.8c10.2-10.2,11.6-26.3,3.2-38.1 + l-19.8-27.8c5.5-10.5,10.1-21.4,13.5-32.6l33.6-5.6c14.3-2.4,24.7-14.7,24.7-29.2v-42.1 + C478.9,203.801,468.5,191.501,454.2,189.101z M451.9,260.401c0,1.3-0.9,2.4-2.2,2.6l-42,7c-5.3,0.9-9.5,4.8-10.8,9.9 + c-3.8,14.7-9.6,28.8-17.4,41.9c-2.7,4.6-2.5,10.3,0.6,14.7l24.7,34.8c0.7,1,0.6,2.5-0.3,3.4l-29.8,29.8c-0.7,0.7-1.4,0.8-1.9,0.8 + c-0.6,0-1.1-0.2-1.5-0.5l-34.7-24.7c-4.3-3.1-10.1-3.3-14.7-0.6c-13.1,7.8-27.2,13.6-41.9,17.4c-5.2,1.3-9.1,5.6-9.9,10.8l-7.1,42 + c-0.2,1.3-1.3,2.2-2.6,2.2h-42.1c-1.3,0-2.4-0.9-2.6-2.2l-7-42c-0.9-5.3-4.8-9.5-9.9-10.8c-14.3-3.7-28.1-9.4-41-16.8 + c-2.1-1.2-4.5-1.8-6.8-1.8c-2.7,0-5.5,0.8-7.8,2.5l-35,24.9c-0.5,0.3-1,0.5-1.5,0.5c-0.4,0-1.2-0.1-1.9-0.8l-29.8-29.8 + c-0.9-0.9-1-2.3-0.3-3.4l24.6-34.5c3.1-4.4,3.3-10.2,0.6-14.8c-7.8-13-13.8-27.1-17.6-41.8c-1.4-5.1-5.6-9-10.8-9.9l-42.3-7.2 + c-1.3-0.2-2.2-1.3-2.2-2.6v-42.1c0-1.3,0.9-2.4,2.2-2.6l41.7-7c5.3-0.9,9.6-4.8,10.9-10c3.7-14.7,9.4-28.9,17.1-42 + c2.7-4.6,2.4-10.3-0.7-14.6l-24.9-35c-0.7-1-0.6-2.5,0.3-3.4l29.8-29.8c0.7-0.7,1.4-0.8,1.9-0.8c0.6,0,1.1,0.2,1.5,0.5l34.5,24.6 + c4.4,3.1,10.2,3.3,14.8,0.6c13-7.8,27.1-13.8,41.8-17.6c5.1-1.4,9-5.6,9.9-10.8l7.2-42.3c0.2-1.3,1.3-2.2,2.6-2.2h42.1 + c1.3,0,2.4,0.9,2.6,2.2l7,41.7c0.9,5.3,4.8,9.6,10,10.9c15.1,3.8,29.5,9.7,42.9,17.6c4.6,2.7,10.3,2.5,14.7-0.6l34.5-24.8 + c0.5-0.3,1-0.5,1.5-0.5c0.4,0,1.2,0.1,1.9,0.8l29.8,29.8c0.9,0.9,1,2.3,0.3,3.4l-24.7,34.7c-3.1,4.3-3.3,10.1-0.6,14.7 + c7.8,13.1,13.6,27.2,17.4,41.9c1.3,5.2,5.6,9.1,10.8,9.9l42,7.1c1.3,0.2,2.2,1.3,2.2,2.6v42.1H451.9z"/> + <path d="M239.4,136.001c-57,0-103.3,46.3-103.3,103.3s46.3,103.3,103.3,103.3s103.3-46.3,103.3-103.3S296.4,136.001,239.4,136.001 + z M239.4,315.601c-42.1,0-76.3-34.2-76.3-76.3s34.2-76.3,76.3-76.3s76.3,34.2,76.3,76.3S281.5,315.601,239.4,315.601z"/> + </g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +</svg> diff --git a/src/python/dm/aps_beamline_tools/gui/settingsTab.py b/src/python/dm/aps_beamline_tools/gui/settingsTab.py new file mode 100644 index 0000000000000000000000000000000000000000..eb4cd3003eab0b116af2730f9875903f00a020dc --- /dev/null +++ b/src/python/dm/aps_beamline_tools/gui/settingsTab.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python + +from PyQt4.QtGui import QGridLayout, QSpacerItem, QSizePolicy, QPushButton, QWidget, \ + QFont, QLabel, QTableWidget, QTableWidgetItem, QColor, QAbstractItemView, QHBoxLayout, QPalette, QFormLayout, \ + QGroupBox, QLineEdit, QDoubleValidator, QIntValidator +from PyQt4.QtCore import Qt, QSettings, QString + +from dm.aps_beamline_tools.gui.apiFactory import ApiFactory +from .subclasses import customSelectionModel, customStyledDelegate + + +# Define the Processing Jobs tab content: +class SettingsTab(QWidget): + + ORG_NAME="UChicagoArgonneLLC" + APP_NAME="dmStationUi" + + REF_CYCLE_EXPERIMENTS = "experimentsRefreshCycle" + REF_CYCLE_DAQS = "daqsRefreshCycle" + REF_CYCLE_UPLOADS = "uploadsRefreshCycle" + REF_CYCLE_JOBS = "processingJobsRefreshCycle" + + DEFAULT_INTERVAL = 60 + + def __init__(self, parent): + super(SettingsTab, self).__init__(parent) + self.parent = parent + self.settingsLayout() + self.settings = QSettings(SettingsTab.ORG_NAME, SettingsTab.APP_NAME, self) + self.loadSettings() + + # GUI layout where each block is a row on the grid + def settingsLayout(self): + grid = QGridLayout() + + labelFont = QFont('Arial', 18, QFont.Bold) + lbl = QLabel('Settings', self) + lbl.setAlignment(Qt.AlignCenter) + lbl.setFont(labelFont) + grid.addWidget(lbl, 0, 0) + + refIntervalGroup = QGroupBox() + refIntervalGroup.setAlignment(Qt.AlignCenter) + refIntervalGroup.setTitle("Refresh Intervals (seconds)") + + refIntervalsFormLayout = QFormLayout() + self.experimentsRefInternvalVal = self._createIntInput(refIntervalsFormLayout, "Experiments", "Specify the interval in seconds for experiments refresh cycle (0 for no refresh)") + self.daqsRefInternvalVal = self._createIntInput(refIntervalsFormLayout, "Daqs", "Specify the interval in seconds for daqs refresh cycle (0 for no refresh)") + self.uploadsRefInternvalVal = self._createIntInput(refIntervalsFormLayout, "Uploads", "Specify the interval in seconds for uploads refresh cycle (0 for no refresh)") + self.jobsRefInternvalVal = self._createIntInput(refIntervalsFormLayout, "Processing Jobs", + "Specify the interval in seconds for processing jobs refresh cycle (0 for no refresh)") + refIntervalGroup.setLayout(refIntervalsFormLayout) + + grid.addWidget(refIntervalGroup, 2, 0) + + saveBtn = QPushButton('Save', self) + saveBtn.clicked.connect(self.saveSettings) + + grid.addWidget(saveBtn, 3, 0, Qt.AlignCenter) + self.setLayout(grid) + + def _createIntInput(self, formLayout, promptText, tooltip, min=0, max=1000): + minMaxText = " (" +str(min) + "-" + str(max)+")" + promptTextLabel = QLabel(promptText + minMaxText) + inputObject = QLineEdit() + inputObject.setValidator(QIntValidator(min, max, self)) + inputObject.setToolTip(tooltip) + + formLayout.addRow(promptTextLabel, inputObject) + return inputObject + + def loadSettings(self): + self._loadIntervalSettings(SettingsTab.REF_CYCLE_UPLOADS, self.uploadsRefInternvalVal) + self._loadIntervalSettings(SettingsTab.REF_CYCLE_DAQS, self.daqsRefInternvalVal) + self._loadIntervalSettings(SettingsTab.REF_CYCLE_EXPERIMENTS, self.experimentsRefInternvalVal) + self._loadIntervalSettings(SettingsTab.REF_CYCLE_JOBS, self.jobsRefInternvalVal) + + def _loadIntervalSettings(self, key, settingInputWidget): + refCycle = self.settings.value(key, SettingsTab.DEFAULT_INTERVAL).toString() + if (refCycle == ""): + refCycle = QString(str(SettingsTab.DEFAULT_INTERVAL)) + + settingInputWidget.setText(refCycle) + + def saveSettings(self): + self.settings.setValue(SettingsTab.REF_CYCLE_DAQS, self.daqsRefInternvalVal.text()) + self.settings.setValue(SettingsTab.REF_CYCLE_EXPERIMENTS, self.experimentsRefInternvalVal.text()) + self.settings.setValue(SettingsTab.REF_CYCLE_UPLOADS, self.uploadsRefInternvalVal.text()) + self.settings.setValue(SettingsTab.REF_CYCLE_JOBS, self.jobsRefInternvalVal.text()) + + self.loadSettings() + + # Reload with the new values + self.parent.setUpRefreshTimers() + + def fetchRefCycleExperiments(self): + return int(self.experimentsRefInternvalVal.text()) + + def fetchRefCycleDaqs(self): + return int(self.daqsRefInternvalVal.text()) + + def fetchRefCycleUploads(self): + return int(self.uploadsRefInternvalVal.text()) + + def fetchRefCycleJobs(self): + return int(self.jobsRefInternvalVal.text())