Newer
Older
"""
Main window for reduction UIs
"""
from __future__ import (absolute_import, division, print_function)
import six
IS_IN_MANTIDPLOT = False
try:
IS_IN_MANTIDPLOT = True
from mantid.kernel import ConfigService
except:
import sip
sip.setapi('QString',2)
sip.setapi('QVariant',2)
from PyQt4 import QtGui, QtCore # noqa
REDUCTION_WARNING = False
WARNING_MESSAGE = ""
if IS_IN_MANTIDPLOT:
try:
import reduction
if os.path.splitext(os.path.basename(reduction.__file__))[0] == "reduction":
REDUCTION_WARNING = True
home_dir = os.path.expanduser('~')
if os.path.abspath(reduction.__file__).startswith(home_dir):
WARNING_MESSAGE = "The following file is in your home area, please delete it and restart Mantid:\n\n"
else:
WARNING_MESSAGE = "If the following file is in your home area, please delete it and restart Mantid:\n\n"
WARNING_MESSAGE += os.path.abspath(reduction.__file__)
except:
REDUCTION_WARNING = True
WARNING_MESSAGE = "Please contact the Mantid team with the following message:\n\n\n"
WARNING_MESSAGE += unicode(traceback.format_exc())
from reduction_gui.instruments.instrument_factory import instrument_factory, INSTRUMENT_DICT # noqa
from reduction_gui.settings.application_settings import GeneralSettings # noqa
import ui.ui_reduction_main # noqa
import ui.ui_instrument_dialog # noqa
Doucet, Mathieu
committed
class ReductionGUI(QtGui.QMainWindow, ui.ui_reduction_main.Ui_SANSReduction):
Doucet, Mathieu
committed
def __init__(self, instrument=None, instrument_list=None):
QtGui.QMainWindow.__init__(self)
if REDUCTION_WARNING:
message = "The reduction application has problems starting:\n\n"
message += WARNING_MESSAGE
QtGui.QMessageBox.warning(self, "WARNING", message)
# Application settings
settings = QtCore.QSettings()
# Name handle for the instrument
instrument = unicode(settings.value("instrument_name", ''))
if instrument_list is not None and instrument not in instrument_list:
self._instrument = instrument
Doucet, Mathieu
committed
# List of allowed instrument
# Reduction interface
self._interface = None
# Recent files
self._recent_files = settings.value("recent_files", [])
if self._recent_files is None: # An empty list saved to QSettings comes back as 'None'
self._recent_files = []
# Folder to open files in
self._last_directory = unicode(settings.value("last_directory", '.'))
self._last_export_directory = unicode(settings.value("last_export_directory", '.'))
# Cluster credentials and options
self._cluster_details_set = False
self._number_of_nodes = 1
self._cores_per_node = 16
self._compute_resources = ['Fermi']
if IS_IN_MANTIDPLOT \
and hasattr(ConfigService.Instance().getFacility(), "computeResources"):
self._compute_resources = ConfigService.Instance().getFacility().computeResources()
# Internal flag for clearing all settings and restarting the application
self._clear_and_restart = False
# General settings shared by all widgets
self.general_settings = GeneralSettings(settings)
Doucet, Mathieu
committed
self.setupUi(self)
if not IS_IN_MANTIDPLOT:
self.reduce_button.hide()
self.connect(self.export_button, QtCore.SIGNAL("clicked()"), self._export)
self.connect(self.reduce_button, QtCore.SIGNAL("clicked()"), self.reduce_clicked)
self.connect(self.save_button, QtCore.SIGNAL("clicked()"), self._save)
self.connect(self.interface_chk, QtCore.SIGNAL("clicked(bool)"), self._interface_choice)
self.interface_chk.setChecked(self.general_settings.advanced)
# Of the widgets that are part of the application, one is the ApplicationWindow.
# The ApplicationWindow will send a shutting_down() signal when quitting,
# after which we should close this window.
# Note: there is no way to identify which Widget is the ApplicationWindow.
for w in QtCore.QCoreApplication.instance().topLevelWidgets():
self.connect(w, QtCore.SIGNAL("shutting_down()"), self.close)
self.general_settings.progress.connect(self._progress_updated)
Sets the window title using the instrument name and the
title = "%s Reduction" % self._instrument
if self._filename is not None:
title += ": %s" % self._filename
self.setWindowTitle(title)
def _progress_updated(self, value):
self.progress_bar.setValue(value)
def setup_layout(self, load_last=False):
"""
Sets up the instrument-specific part of the UI layout
"""
# Clean up the widgets that have already been created
self.tabWidget.clear()
self.progress_bar.hide()
if self._instrument == '' or self._instrument is None:
self._update_file_menu()
if self._interface is not None:
self._interface.destroy()
self.general_settings.instrument_name = self._instrument
# Find corresponding facility
if self._facility is None:
for facility in INSTRUMENT_DICT.keys():
if self._instrument in INSTRUMENT_DICT[facility].keys():
self._facility = facility
break
if self._facility is None:
self._facility = ConfigService.Instance().getFacility().name()
self.general_settings.facility_name = self._facility
self._interface = instrument_factory(self._instrument, settings=self.general_settings)
tab_list = self._interface.get_tabs()
for tab in tab_list:
self.tabWidget.addTab(tab[1], tab[0])
# Show the "advanced interface" check box if needed
if self._interface.has_advanced_version():
self.interface_chk.show()
else:
self.interface_chk.hide()
if load_last:
self._interface.load_last_reduction()
print("Could not generate an interface for instrument %s" % self._instrument)
def _update_file_menu(self):
"""
Set up the File menu and update the menu with recent files
self.file_menu.clear()
newAction = QtGui.QAction("&New Reduction...", self)
newAction.setShortcut("Ctrl+N")
newAction.setStatusTip("Start a new reduction")
self.connect(newAction, QtCore.SIGNAL("triggered()"), self._new)
openAction = QtGui.QAction("&Open...", self)
openAction.setShortcut("Ctrl+O")
openAction.setStatusTip("Open an XML file containing reduction parameters")
self.connect(openAction, QtCore.SIGNAL("triggered()"), self._file_open)
saveAsAction = QtGui.QAction("Save as...", self)
saveAsAction.setStatusTip("Save the reduction parameters to XML")
self.connect(saveAsAction, QtCore.SIGNAL("triggered()"), self._save_as)
saveAction = QtGui.QAction("&Save...", self)
saveAction.setShortcut("Ctrl+S")
saveAction.setStatusTip("Save the reduction parameters to XML")
self.connect(saveAction, QtCore.SIGNAL("triggered()"), self._save)
exportAction = QtGui.QAction("&Export...", self)
exportAction.setShortcut("Ctrl+E")
exportAction.setStatusTip("Export to python script for Mantid")
self.connect(exportAction, QtCore.SIGNAL("triggered()"), self._export)
quitAction = QtGui.QAction("&Quit", self)
quitAction.setShortcut("Ctrl+Q")
self.connect(quitAction, QtCore.SIGNAL("triggered()"), self.close)
self.file_menu.addAction(newAction)
self.file_menu.addAction(openAction)
self.file_menu.addAction(saveAction)
self.file_menu.addAction(saveAsAction)
self.file_menu.addAction(exportAction)
if self.general_settings.debug:
clearAction = QtGui.QAction("&Clear settings and quit", self)
clearAction.setStatusTip("Restore initial application settings and close the application")
self.connect(clearAction, QtCore.SIGNAL("triggered()"), self._clear_and_close)
self.file_menu.addAction(clearAction)
self.file_menu.addAction(quitAction)
# TOOLS menu
instrAction = QtGui.QAction("Change &instrument...", self)
instrAction.setShortcut("Ctrl+I")
instrAction.setStatusTip("Select a new instrument")
self.connect(instrAction, QtCore.SIGNAL("triggered()"), self._change_instrument)
debug_menu_item_str = "Turn debug mode ON"
if self.general_settings.debug:
debug_menu_item_str = "Turn debug mode OFF"
debugAction = QtGui.QAction(debug_menu_item_str, self)
debugAction.setStatusTip(debug_menu_item_str)
self.connect(debugAction, QtCore.SIGNAL("triggered()"), self._debug_mode)
self.tools_menu.clear()
self.tools_menu.addAction(instrAction)
self.tools_menu.addAction(debugAction)
recent_files = []
for fname in self._recent_files:
if fname != self._filename and QtCore.QFile.exists(fname) and fname not in recent_files:
if len(recent_files)>0:
self.file_menu.addSeparator()
for i, fname in enumerate(recent_files):
action = QtGui.QAction("&%d %s" % (i+1, QtCore.QFileInfo(fname).fileName()), self)
action.setData(fname)
self.connect(action, QtCore.SIGNAL("triggered()"), self.open_file)
self.file_menu.addAction(action)
def _debug_mode(self, mode=None):
"""
Set debug mode
@param mode: debug mode (True or False). If None, the debug mode will simply be flipped
"""
if mode is None:
mode = not self.general_settings.debug
self.general_settings.debug = mode
self._new()
self.setup_layout()
def _interface_choice(self, advanced_ui=None):
if advanced_ui is None:
advanced_ui = self.general_settings.advanced
self.general_settings.advanced = advanced_ui
self._new()
self.setup_layout()
def _change_instrument(self):
"""
Invoke an instrument selection dialog
"""
class InstrDialog(QtGui.QDialog, ui.ui_instrument_dialog.Ui_Dialog):
Doucet, Mathieu
committed
QtGui.QDialog.__init__(self)
self.instrument_list = instrument_list
Doucet, Mathieu
committed
self.setupUi(self)
Doucet, Mathieu
committed
self.instr_combo.clear()
instruments.reverse()
for facility in instruments:
self.facility_combo.addItem(QtGui.QApplication.translate("Dialog", facility, None, QtGui.QApplication.UnicodeUTF8))
self._facility_changed(instruments[0])
self.connect(self.facility_combo, QtCore.SIGNAL("activated(QString)"), self._facility_changed)
def _facility_changed(self, facility):
self.instr_combo.clear()
instr_list = sorted(INSTRUMENT_DICT[unicode(facility)].keys())
for item in instr_list:
if self.instrument_list is None or item in self.instrument_list:
self.instr_combo.addItem(QtGui.QApplication.translate("Dialog", item, None, QtGui.QApplication.UnicodeUTF8))
dialog = InstrDialog(self._instrument_list)
dialog.exec_()
if dialog.result()==1:
self._instrument = dialog.instr_combo.currentText()
self._facility = dialog.facility_combo.currentText()
return True
else:
self.close()
return False
def _clear_and_close(self):
"""
Clear all QSettings parameters
"""
self._clear_and_restart = True
self.close()
# If we make it here, the user canceled the close, which
# means that we need to reset the clear&close flag so
# that the state is properly saved on the next close.
self._clear_and_restart = False
def closeEvent(self, event):
Executed when the application closes
reply = QtGui.QMessageBox.question(self, 'Message',
"Are you sure you want to quit this application?",
QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
if reply == QtGui.QMessageBox.Yes:
event.accept()
else:
event.ignore()
# Save application settings
if self._clear_and_restart:
self._clear_and_restart = False
QtCore.QSettings().clear()
settings.setValue("instrument_name", self._instrument)
settings.setValue("last_file", self._filename)
settings.setValue("recent_files", self._recent_files)
settings.setValue("last_directory", str(self._last_directory))
settings.setValue("last_export_directory", str(self._last_export_directory))
def reduce_clicked(self):
"""
Create an object capable of using the information in the
interface and turn it into a reduction process.
"""
self.reduce_button.setEnabled(False)
self.save_button.setEnabled(False)
self.interface_chk.setEnabled(False)
self.file_menu.setEnabled(False)
self.tools_menu.setEnabled(False)
if self._interface is not None:
self._interface.reduce()
self.reduce_button.setEnabled(True)
self.save_button.setEnabled(True)
self.interface_chk.setEnabled(True)
self.file_menu.setEnabled(True)
self.tools_menu.setEnabled(True)

Bilheux, Jean-Christophe
committed
def cluster_clicked(self):
"""
Submit for parallel reduction
"""
if not self._cluster_details_set:
self._cluster_details_dialog()
if self._interface is not None \
and self.general_settings.cluster_user is not None \
and self.general_settings.cluster_pass is not None:
# Chose a name for the job
if self._filename is not None:
job_name = os.path.basename(self._filename).strip()
toks = os.path.splitext(job_name)
job_name = toks[0]
else:
job_name = ''
self._interface.cluster_submit(self.general_settings.cluster_user,
self.general_settings.cluster_pass,
resource=self.general_settings.compute_resource,
nodes=self._number_of_nodes,
cores_per_node=self._cores_per_node,
job_name=job_name)
def open_file(self, file_path=None):
"""
Open an XML file and populate the UI
@param file_path: path to the file to be loaded
"""
if file_path is None:
action = self.sender()
if isinstance(action, QtGui.QAction):
file_path = unicode(action.data())
# don't try to load if the file doesn't exist
if not os.path.exists(file_path):
return
Doucet, Mathieu
committed
# Check whether the file describes the current instrument
try:
found_instrument = self._interface.scripter.verify_instrument(file_path)
except:
msg = "The file you attempted to load doesn't have a recognized format:\n" \
+ file_path+"\n\n" \
+ "Please make sure it has been produced by this application."
QtGui.QMessageBox.warning(self, "Error loading reduction parameter file", msg)
Doucet, Mathieu
committed
if not found_instrument == self._instrument:
self._instrument = found_instrument
self.setup_layout()
self.reduce_button.setEnabled(False)
self.save_button.setEnabled(False)
self.interface_chk.setEnabled(False)
self._interface.load_file(file_path)
self.reduce_button.setEnabled(True)
self.export_button.setEnabled(True)
self.save_button.setEnabled(True)
self.interface_chk.setEnabled(True)
self._filename = file_path
self._update_file_menu()
self._set_window_title()
if file_path in self._recent_files:
self._recent_files.remove(file_path)
self._recent_files.insert(0,file_path)
while len(self._recent_files) > 10:
self._recent_files.pop()
def _new(self, *argv):
"""
Start new reduction
"""
self._interface.reset()
self._filename = None
self._update_file_menu()
self._set_window_title()
"""
File chooser for loading UI parameters
"""
fname_qstr = QtGui.QFileDialog.getOpenFileName(self, "Reduction settings - Choose a settings file",
self._last_directory,
"Settings files (*.xml)")
fname = str(QtCore.QFileInfo(fname_qstr).filePath())
if fname:
# Store the location of the loaded file
self._last_directory = str(QtCore.QFileInfo(fname_qstr).path())
self.open_file(fname)
"""
Present a file dialog to the user and saves the content of the
UI in XML format
"""
if self._filename is None:
self._save_as()
else:
try:
self._interface.save_file(self._filename)
self._update_file_menu()
self.statusBar().showMessage("Saved as %s" % self._filename)
self._set_window_title()
except:
#TODO: put this in a log window, and in a file
self.statusBar().showMessage("Failed to save %s" % self._filename)
"""
Present a file dialog to the user and saves the content of
the UI in XML format.
"""
Gigg, Martyn Anthony
committed
if self._filename is not None:
fname = self._filename
else:
fname = self._instrument + '_'
fname_qstr = QtGui.QFileDialog.getSaveFileName(self, "Reduction settings - Save settings",
self._last_directory + '/' + fname,
"Settings files (*.xml)")
fname = str(QtCore.QFileInfo(fname_qstr).filePath())
if not fname.endswith('.xml'):
fname += ".xml"
Doucet, Mathieu
committed
if fname in self._recent_files:
self._recent_files.remove(fname)
self._recent_files.insert(0,fname)
while len(self._recent_files) > 10:
self._last_directory = str(QtCore.QFileInfo(fname_qstr).path())
self._filename = fname
self._save()
Exports the current content of the UI to a python script that can
if self._interface is None:
return
fname = '.'
if self._filename is not None:
(root, ext) = os.path.splitext(self._filename)
fname = root
fname = unicode(QtGui.QFileDialog.getSaveFileName(self, "Mantid Python script - Save script",
self._last_export_directory,
if len(fname)>0:
if not fname.endswith('.py'):
fname += ".py"
(folder, file_name) = os.path.split(fname)
self._last_export_directory = folder
Doucet, Mathieu
committed
script = self._interface.export(fname)
if script is not None:
self.statusBar().showMessage("Saved as %s" % fname)
else:
self.statusBar().showMessage("Could not save file")
#--------------------------------------------------------------------------------------------------------
def start(argv):
app = QtGui.QApplication(argv)
app.setOrganizationName("Mantid")
app.setOrganizationDomain("mantidproject.org")
app.setApplicationName("Mantid Reduction")
reducer = ReductionGUI()
reducer.setup_layout(load_last=True)
reducer.show()
app.exec_()
if __name__ == '__main__':