"""
Module Documentation here
"""
#=========================================================================================
# Licence, Reference and Credits
#=========================================================================================
__copyright__ = "Copyright (C) CCPN project (https://www.ccpn.ac.uk) 2014 - 2022"
__credits__ = ("Ed Brooksbank, Joanna Fox, Victoria A Higman, Luca Mureddu, Eliza Płoskoń",
"Timothy J Ragan, Brian O Smith, Gary S Thompson & Geerten W Vuister")
__licence__ = ("CCPN licence. See https://ccpn.ac.uk/software/licensing/")
__reference__ = ("Skinner, S.P., Fogh, R.H., Boucher, W., Ragan, T.J., Mureddu, L.G., & Vuister, G.W.",
"CcpNmr AnalysisAssign: a flexible platform for integrated NMR analysis",
"J.Biomol.Nmr (2016), 66, 111-124, http://doi.org/10.1007/s10858-016-0060-y")
#=========================================================================================
# Last code modification
#=========================================================================================
__modifiedBy__ = "$modifiedBy: Geerten Vuister $"
__dateModified__ = "$dateModified: 2022-03-08 22:14:25 +0000 (Tue, March 08, 2022) $"
__version__ = "$Revision: 3.1.0 $"
#=========================================================================================
# Created
#=========================================================================================
__author__ = "$Author: Wayne Boucher $"
__date__ = "$Date: 2017-03-16 18:20:01 +0000 (Thu, March 16, 2017) $"
#=========================================================================================
# Start of code
#=========================================================================================
import sys
import typing
import re
from PyQt5 import QtWidgets, QtCore
from ccpn.core import _coreClassMap
from ccpn.core.Project import Project
from ccpn.framework.Application import getApplication
from ccpn.framework.PathsAndUrls import CCPN_EXTENSION
from ccpn.framework.lib.DataLoaders.DataLoaderABC import getDataLoaders, checkPathForDataLoader
from ccpn.core._implementation.AbstractWrapperObject import AbstractWrapperObject
from ccpn.core.lib.ContextManagers import \
notificationEchoBlocking, \
catchExceptions, \
undoBlockWithoutSideBar, \
logCommandManager
from ccpn.ui.Ui import Ui
from ccpn.ui.gui.popups.RegisterPopup import RegisterPopup, NewTermsConditionsPopup
from ccpn.ui.gui.widgets.Application import Application
from ccpn.ui.gui.widgets import MessageDialog
from ccpn.ui.gui.widgets import FileDialog
from ccpn.ui.gui.popups.ImportStarPopup import StarImporterPopup
# This import initializes relative paths for QT style-sheets. Do not remove! GWV ????
from ccpn.ui.gui.guiSettings import FontSettings
from ccpn.ui.gui.widgets.Font import getFontHeight, setWidgetFont
from ccpn.util.Logging import getLogger
from ccpn.util import Logging
from ccpn.util import Register
from ccpn.util.Path import aPath, Path
from ccpn.util.decorators import logCommand
#-----------------------------------------------------------------------------------------
# Subclass the exception hook fpr PyQT
#-----------------------------------------------------------------------------------------
def _ccpnExceptionhook(ccpnType, value, tback):
"""This because PyQT raises and catches exceptions,
but doesn't pass them along instead makes the program crashing miserably.
"""
application = getApplication()
if application and application._isInDebugMode:
sys.stderr.write('_ccpnExceptionhook: type = %s\n' % ccpnType)
sys.stderr.write('_ccpnExceptionhook: value = %s\n' % value)
sys.stderr.write('_ccpnExceptionhook: tback = %s\n' % tback)
if application and application.hasGui:
title = str(ccpnType)[8:-2] + ':'
text = str(value)
MessageDialog.showError(title=title, message=text)
sys.__excepthook__(ccpnType, value, tback)
sys.excepthook = _ccpnExceptionhook
#-----------------------------------------------------------------------------------------
[docs]def qtMessageHandler(*errors):
for err in errors:
Logging.getLogger().warning('QT error: %s' % err)
# un/suppress messages
QtCore.qInstallMessageHandler(qtMessageHandler)
# REMOVEDEBUG = r'\(\w+\.\w+:\d+\)$'
REMOVEDEBUG = r'\(\S+\.\w+:\d+\)$'
MAXITEMLOGGING = 4
class _MyAppProxyStyle(QtWidgets.QProxyStyle):
"""Class to handle resizing icons in menus
"""
def pixelMetric(self, QStyle_PixelMetric, option=None, widget=None):
if QStyle_PixelMetric == QtWidgets.QStyle.PM_SmallIconSize:
# change the size of the icons in menus - overrides checkBoxes in menus
return (getFontHeight(size='SMALL') or 15) + 3
elif QStyle_PixelMetric in (QtWidgets.QStyle.PM_IndicatorHeight,
QtWidgets.QStyle.PM_IndicatorWidth,
QtWidgets.QStyle.PM_ExclusiveIndicatorWidth,
QtWidgets.QStyle.PM_ExclusiveIndicatorHeight,
):
# change the size of checkBoxes and radioButtons
return (getFontHeight(size='SMALL') or 15) - 2
elif QStyle_PixelMetric == QtWidgets.QStyle.PM_MessageBoxIconSize:
# change the icon size in messageDialog
return getFontHeight(size='MAXIMUM') or 18
return super().pixelMetric(QStyle_PixelMetric, option, widget)
[docs]class Gui(Ui):
"""Top class for the GUI interface
"""
# Factory functions for UI-specific instantiation of wrapped graphics classes
_factoryFunctions = {}
def __init__(self, application):
# sets self.mainWindow (None), self.application and self.pluginModules
Ui.__init__(self, application)
# GWV: this is not ideal and needs to move into the Gui class
application._fontSettings = FontSettings(application.preferences)
application._setColourSchemeAndStyleSheet()
application._setupMenus()
self._initQtApp()
def _initQtApp(self):
# On the Mac (at least) it does not matter what you set the applicationName to be,
# it will come out as the executable you are running (e.g. "python3")
# # QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True)
QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True)
# QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_ShareOpenGLContexts, True)
# viewportFormat = QtGui.QSurfaceFormat()
# viewportFormat.setSwapInterval(0) #disable VSync
# viewportFormat.setSwapBehavior(QtGui.QSurfaceFormat.SingleBuffer)
# QtGui.QSurfaceFormat().setDefaultFormat(viewportFormat)
# # self._CcpnGLWidget.setFormat(viewportFormat)
# # viewportFormat = self._CcpnGLWidget.format()
# styles = QtWidgets.QStyleFactory()
# myStyle = _MyAppProxyStyle(styles.create('fusion'))
# QtWidgets.QApplication.setStyle(myStyle)
self.qtApp = Application(self.application.applicationName,
self.application.applicationVersion,
organizationName='CCPN', organizationDomain='ccpn.ac.uk')
# patch for icon sizes in menus, etc.
styles = QtWidgets.QStyleFactory()
myStyle = _MyAppProxyStyle(styles.create('fusion'))
self.qtApp.setStyle(myStyle)
# # original - no patch for icon sizes
# styles = QtWidgets.QStyleFactory()
# self.qtApp.setStyle(styles.create('fusion'))
[docs] def initialize(self, mainWindow):
"""UI operations done after every project load/create
"""
with notificationEchoBlocking():
# Set up mainWindow
self.mainWindow = self._setupMainWindow(mainWindow)
self.application._initGraphics()
self.mainWindow._updateRestoreArchiveMenu()
self.application._updateCheckableMenuItems()
[docs] def startUi(self):
"""Start the UI
"""
self.mainWindow.show()
QtWidgets.QApplication.setActiveWindow(self.mainWindow)
# check whether to skip the execution loop for testing with mainWindow
import builtins
_skip = getattr(builtins, '_skipExecuteLoop', False)
if not _skip:
self.qtApp.start()
def _registerDetails(self, registered=False, acceptedTerms=False):
"""Display registration popup"""
days = Register._graceCounter(Register._fetchGraceFile(self.application))
# check valid internet connection first
if not Register.checkInternetConnection():
msg = 'Could not connect to the registration server, please check your internet connection. ' \
'Register within %s day(s) to continue using the software' % str(days)
MessageDialog.showError('Registration', msg)
else:
if registered and not acceptedTerms:
popup = NewTermsConditionsPopup(self.mainWindow, trial=days, version=self.application.applicationVersion, modal=True)
else:
popup = RegisterPopup(self.mainWindow, trial=days, version=self.application.applicationVersion, modal=True)
self.mainWindow.show()
popup.exec_()
self.qtApp.processEvents()
def _setupMainWindow(self, mainWindow):
# Set up mainWindow
project = self.application.project
mainWindow.sideBar.buildTree(project, clear=True)
mainWindow.raise_()
mainWindow.namespace['current'] = self.application.current
return mainWindow
[docs] def echoCommands(self, commands: typing.List[str]):
"""Echo commands strings, one by one, to logger
and store them in internal list for perusal
"""
logger = Logging.getLogger()
for command in commands:
logger.echoInfo(command)
if self.application.ui is not None and \
self.application.ui.mainWindow is not None and \
self.application._enableLoggingToConsole:
console = self.application.ui.mainWindow.pythonConsole
for command in commands:
command = re.sub(REMOVEDEBUG, '', command)
console._write(command + '\n')
[docs] def getByGid(self, gid):
from ccpn.ui.gui.modules.CcpnModule import PidShortClassName, PidLongClassName
from ccpn.core.lib.Pid import Pid
pid = Pid(gid)
if pid is not None:
if pid.type in [PidLongClassName, PidShortClassName]:
# get the GuiModule object By its Gid
return self.application.mainWindow.moduleArea.modules.get(pid.id)
return self.application.getByGid(gid)
def _execUpdates(self):
"""Use the Update popup to execute any updates
"""
self.application._showUpdatePopup()
#-----------------------------------------------------------------------------------------
# Helper methods
#-----------------------------------------------------------------------------------------
def _queryChoices(self, dataLoader):
"""Query the user about his/her choice to import/new/cancel
"""
choices = ('Import', 'New project', 'Cancel')
choice = MessageDialog.showMulti('Load %s' % dataLoader.dataFormat,
'How do you want to handle "%s":' % dataLoader.path,
choices, parent=self.mainWindow)
if choice == choices[0]: # import
dataLoader.createNewProject = False
createNewProject = False
ignore = False
elif choice == choices[1]: # new project
dataLoader.createNewProject = True
createNewProject = True
ignore = False
else: # cancel
dataLoader = None
createNewProject = False
ignore = True
return (dataLoader, createNewProject, ignore)
def _getDataLoader(self, path, pathFilter=None):
"""Get dataLoader for path (or None if not present), optionally only testing for
dataFormats defined in filter.
Allows for reporting or checking through popups.
Does not do the actual loading.
:param path: the path to get a dataLoader for
:param pathFilter: a list/tuple of optional dataFormat strings; (defaults to all dataFormats)
:returns a tuple (dataLoader, createNewProject, ignore)
"""
# local import here
from ccpn.framework.lib.DataLoaders.CcpNmrV2ProjectDataLoader import CcpNmrV2ProjectDataLoader
from ccpn.framework.lib.DataLoaders.CcpNmrV3ProjectDataLoader import CcpNmrV3ProjectDataLoader
from ccpn.framework.lib.DataLoaders.NefDataLoader import NefDataLoader
from ccpn.framework.lib.DataLoaders.SparkyDataLoader import SparkyDataLoader
from ccpn.framework.lib.DataLoaders.SpectrumDataLoader import SpectrumDataLoader
from ccpn.framework.lib.DataLoaders.StarDataLoader import StarDataLoader
from ccpn.framework.lib.DataLoaders.DirectoryDataLoader import DirectoryDataLoader
if pathFilter is None:
pathFilter = tuple(getDataLoaders().keys())
dataLoader = checkPathForDataLoader(path, pathFilter=pathFilter)
if dataLoader is None:
txt = '_getDataLoader: Loading "%s" unsuccessful; unrecognised type, should be one of %r' % \
(path, pathFilter)
getLogger().debug(txt)
return (None, False, False)
createNewProject = dataLoader.createNewProject
ignore = False
path = dataLoader.path
if dataLoader.dataFormat == CcpNmrV2ProjectDataLoader.dataFormat:
createNewProject = True
dataLoader.createNewProject = True
ok = MessageDialog.showYesNoWarning(f'Load Project',
f'Project "{path.name}" was created with version-2 Analysis.\n'
f'\n'
f'CAUTION:\n'
f'The project will be converted to a version-3 project and saved as a new directory with .ccpn extension.\n'
f'\n'
f'Do you want to continue loading?')
if not ok:
# skip loading so that user can backup/copy project
getLogger().info('==> Cancelled loading ccpn project "%s"' % path)
ignore = True
elif dataLoader.dataFormat == CcpNmrV3ProjectDataLoader.dataFormat and Project._needsUpgrading(path):
createNewProject = True
dataLoader.createNewProject = True
DONT_OPEN = "Don't Open"
CONTINUE = 'Continue'
MAKE_ARCHIVE = 'Make a backup archive (.tgz) of the project'
dataLoader.makeArchive = False
ok = MessageDialog.showMulti(f'Load Project',
f'You are opening an older project (version 3.0.x) - {path.name}\n'
f'\n'
f'When you save, it will be upgraded and will not be readable by version 3.0.4\n',
texts=[DONT_OPEN, CONTINUE],
checkbox=MAKE_ARCHIVE, checked=False,
)
if not any(ss in ok for ss in [DONT_OPEN, MAKE_ARCHIVE, CONTINUE]):
# there was an error from the dialog
getLogger().debug(f'==> Cancelled loading ccpn project "{path}" - error in dialog')
ignore = True
if DONT_OPEN in ok:
# user selection not to load
getLogger().info(f'==> Cancelled loading ccpn project "{path}"')
ignore = True
elif MAKE_ARCHIVE in ok:
# flag to make a backup archive
dataLoader.makeArchive = True
elif dataLoader.dataFormat == NefDataLoader.dataFormat:
(dataLoader, createNewProject, ignore) = self._queryChoices(dataLoader)
if dataLoader and not createNewProject and not ignore:
# we are importing; popup the import window
ok = self.mainWindow._showNefPopup(dataLoader)
if not ok:
ignore = True
elif dataLoader.dataFormat == SparkyDataLoader.dataFormat:
(dataLoader, createNewProject, ignore) = self._queryChoices(dataLoader)
elif dataLoader.dataFormat == SpectrumDataLoader.dataFormat and dataLoader.existsInProject():
ok = MessageDialog.showYesNoWarning('Loading Spectrum',
f'"{dataLoader.path}"\n'
f'already exists in the project\n'
'\n'
'do you want to load?'
)
if not ok:
ignore = True
elif dataLoader.dataFormat == StarDataLoader.dataFormat and dataLoader:
(dataLoader, createNewProject, ignore) = self._queryChoices(dataLoader)
if dataLoader and not createNewProject and not ignore:
dataBlock = dataLoader.dataBlock # this will also read and parse the file
popup = StarImporterPopup(project=self.project,
bmrbFilePath=dataLoader.path,
directory=dataLoader.path.parent,
dataBlock=dataBlock,
size=(700,1000))
popup.exec_()
elif dataLoader.dataFormat == DirectoryDataLoader.dataFormat and len(dataLoader) > MAXITEMLOGGING:
ok = MessageDialog.showYesNoWarning('Directory "%s"\n' %dataLoader.path,
f'\n'
'CAUTION: You are trying to load %d items\n'
'\n'
'Do you want to continue?' % (len(dataLoader,))
)
if not ok:
ignore = True
return (dataLoader, createNewProject, ignore)
#-----------------------------------------------------------------------------------------
# Project and loading data related methods
#-----------------------------------------------------------------------------------------
[docs] @logCommand('application.')
def newProject(self, name:str = 'default') -> typing.Optional[Project]:
"""Create a new project instance with name.
:return a Project instance or None
"""
if not self.project.isTemporary:
message = 'Do you really want to create a new project (current project will be closed %s)?' % \
(' and any changes will be lost' if self.project.isModified else '')
_ok = MessageDialog.showYesNo('New Project', message, parent=self.mainWindow)
if not _ok:
return
with catchExceptions(errorStringTemplate='Error creating new project: %s'):
self.mainWindow.moduleArea._closeAll()
newProject = self.application._newProject(name=name)
if newProject is None:
raise RuntimeError('Unable to create new project')
newProject._mainWindow.show()
QtWidgets.QApplication.setActiveWindow(newProject._mainWindow)
return newProject
def _loadProject(self, dataLoader) -> typing.Union[Project, None]:
"""Helper function, loading project from dataLoader instance
check and query for closing current project
build the project Gui elements
attempts to restore on failure to load a project
:returns project instance or None
"""
from ccpn.framework.lib.DataLoaders.CcpNmrV3ProjectDataLoader import CcpNmrV3ProjectDataLoader
if not dataLoader.createNewProject:
raise RuntimeError('DataLoader %s does not create a new project')
if not self.project.isTemporary:
message = 'Do you really want to open a new project (current project will be closed %s)?' % \
(' and any changes will be lost' if self.project.isModified else '')
_ok = MessageDialog.showYesNo('Load Project', message, parent=self.mainWindow)
if not _ok:
return None
# Some error recovery; store info to re-open the current project (or a new default)
oldProjectLoader = CcpNmrV3ProjectDataLoader(self.project.path)
oldProjectIsTemporary = self.project.isTemporary
try:
with MessageDialog.progressManager(self.mainWindow, 'Loading project %s ... ' % dataLoader.path):
_loaded = dataLoader.load()
if _loaded is None or len(_loaded) == 0:
return None
newProject = _loaded[0]
# # Note that the newProject has its own MainWindow; i.e. it is not self
# newProject._mainWindow.sideBar.buildTree(newProject)
# The next two lines are essential to have the QT main event loop associated
# with the new window; without these, the programs just terminates
newProject._mainWindow.show()
QtWidgets.QApplication.setActiveWindow(newProject._mainWindow)
# if the new project contains invalid spectra then open the popup to see them
self.mainWindow._checkForBadSpectra(newProject)
except RuntimeError as es:
MessageDialog.showError('Error loading Project:', f'{es}', parent=self)
# Try to restore the state
if oldProjectIsTemporary:
newProject = self.application._newProject()
else:
newProject = oldProjectLoader.load()[0] # dataLoaders return a list
# The next two lines are essential to have the QT main event loop associated
# with the new window; without these, the programs just terminates
newProject._mainWindow.show()
QtWidgets.QApplication.setActiveWindow(newProject._mainWindow)
return newProject
# @logCommand('application.') # eventually decorated by _loadData()
[docs] def loadProject(self, path=None) -> typing.Union[Project, None]:
"""Loads project defined by path
:return a Project instance or None
"""
if path is None:
dialog = FileDialog.ProjectFileDialog(parent=self.mainWindow, acceptMode='open')
dialog._show()
if (path := dialog.selectedFile()) is None:
return None
dataLoader, createNewProject, ignore = self._getDataLoader(path)
if ignore or dataLoader is None or not createNewProject:
return None
# load the project using the dataLoader;
# We'll ask framework, who will pass it back to ui._loadProject
newProject = self.application._loadData([dataLoader])
return newProject
[docs] def saveProjectAs(self, newPath=None, overwrite:bool=False) -> bool:
"""Opens save Project to newPath.
Optionally open file dialog.
:param newPath: new path to save project (str | Path instance)
:param overwrite: flag to indicate overwriting of existing path
:return True if successful
"""
oldPath = self.project.path
if newPath is None:
if (newPath := _getSaveDirectory(self.mainWindow)) is None:
return False
newPath = aPath(newPath).assureSuffix(CCPN_EXTENSION)
if ( not overwrite and
newPath.exists() and
(newPath.is_file() or (newPath.is_dir() and len(newPath.listdir()) > 0))
):
# should not really need to check the second and third condition above, only
# the Qt dialog stupidly insists a directory exists before you can select it
# so if it exists but is empty then don't bother asking the question
title = 'Project SaveAs'
msg = 'Path "%s" already exists; overwrite?' % newPath
if not MessageDialog.showYesNo(title, msg):
return False
with logCommandManager('application.', 'saveProjectAs', newPath, overwrite=overwrite):
with catchExceptions(errorStringTemplate='Error saving project: %s'):
with MessageDialog.progressManager(self.mainWindow, f'Saving project {newPath} ... '):
if not self.application._saveProject(newPath=newPath,
createFallback=False,
overwriteExisting=True):
txt = "Saving project to %s aborted" % newPath
getLogger().warning(txt)
MessageDialog.showError("Project SaveAs", txt, parent=self.mainWindow)
return False
self.mainWindow._updateWindowTitle()
self.application._getRecentProjectFiles(oldPath=oldPath) # this will also update the list
self.mainWindow._fillRecentProjectsMenu() # Update the menu
successMessage = 'Project successfully saved to "%s"' % self.project.path
MessageDialog.showInfo("Project SaveAs", successMessage, parent=self.mainWindow)
self.mainWindow.statusBar().showMessage(successMessage)
getLogger().info(successMessage)
return True
[docs] @logCommand('application.')
def saveProject(self) -> bool:
"""Save project.
:return True if successful
"""
with catchExceptions(errorStringTemplate='Error saving project: %s'):
with MessageDialog.progressManager(self.mainWindow, f'Saving project ... '):
if not self.application._saveProject(newPath=None,
createFallback=True,
overwriteExisting=True):
return False
successMessage = '==> Project successfully saved to "%s"' % self.project.path
self.mainWindow.statusBar().showMessage(successMessage)
getLogger().info(successMessage)
return True
def _loadData(self, dataLoader) -> list:
"""Load the data defined by dataLoader instance, catching errors
and suspending sidebar.
:return a list of loaded opjects
"""
result = []
errorStringTemplate = 'Loading "%s" failed:' % dataLoader.path + '\n%s'
with catchExceptions(errorStringTemplate=errorStringTemplate):
result = dataLoader.load()
return result
# @logCommand('application.') # eventually decorated by _loadData()
[docs] def loadData(self, *paths, pathFilter=None) -> list:
"""Loads data from paths; query if none supplied
Optionally filter for dataFormat(s)
:param *paths: argument list of path's (str or Path instances)
:param pathFilter: keyword argument: list/tuple of dataFormat strings
:returns list of loaded objects
"""
if len(paths) == 0:
dialog = FileDialog.DataFileDialog(parent=self.mainWindow, acceptMode='load')
dialog._show()
if (path := dialog.selectedFile()) is None:
return []
paths = [path]
dataLoaders = []
for path in paths:
dataLoader, createNewProject, ignore = self._getDataLoader(path, pathFilter=pathFilter)
if ignore or dataLoader is None:
continue
if dataLoader is None:
getLogger().warning('Unable to load "%s"' % path)
continue
dataLoaders.append(dataLoader)
# load the project using the dataLoaders;
# We'll ask framework who will pass it back as ui._loadData calls
return self.application._loadData(dataLoaders)
[docs] def loadSpectra(self, *paths) -> list:
"""Load all the spectra found in paths.
Query in case path is empty.
:param paths: list of paths
:return a list of Spectra instances
"""
from ccpn.framework.lib.DataLoaders.SpectrumDataLoader import SpectrumDataLoader
from ccpn.framework.lib.DataLoaders.DirectoryDataLoader import DirectoryDataLoader
if len(paths) == 0:
# This only works with non-native file dialog; override the default behavior
dialog = FileDialog.SpectrumFileDialog(parent=self.mainWindow, acceptMode='load',
useNative=False)
dialog._show()
paths = dialog.selectedFiles()
if not paths:
return []
spectrumLoaders = []
count = 0
# Recursively search all paths
for path in paths:
_path = aPath(path)
if _path.is_dir():
dirLoader = DirectoryDataLoader(path, recursive=False,
pathFilter=(SpectrumDataLoader.dataFormat,))
spectrumLoaders.append(dirLoader)
count += len(dirLoader)
elif (sLoader := SpectrumDataLoader.checkForValidFormat(path)) is not None:
spectrumLoaders.append(sLoader)
count += 1
if count > MAXITEMLOGGING:
okToOpenAll = MessageDialog.showYesNo('Load data', 'You selected %d items.'
' Do you want to open all?' % count)
if not okToOpenAll:
return []
with logCommandManager('application.', 'loadSpectra', *paths):
result = self.application._loadData(spectrumLoaders)
return result
#-----------------------------------------------------------------------------------------
# Helper code
#-----------------------------------------------------------------------------------------
def _getSaveDirectory(mainWindow):
"""Opens save Project as dialog box and gets directory specified in
the file dialog.
:return path instance or None
"""
dialog = FileDialog.ProjectSaveFileDialog(parent=mainWindow, acceptMode='save')
dialog._show()
newPath = dialog.selectedFile()
# if not iterable then ignore - dialog may return string or tuple(<path>, <fileOptions>)
if isinstance(newPath, tuple) and len(newPath) > 0:
newPath = newPath[0]
# ignore if empty
if not newPath:
return None
return newPath
#######################################################################################
#
# Ui classes that map ccpn.ui._implementation
#
#######################################################################################
## Window class
_coreClassWindow = _coreClassMap['Window']
from ccpn.ui.gui.lib.GuiMainWindow import GuiMainWindow as _GuiMainWindow
[docs]class MainWindow(_coreClassWindow, _GuiMainWindow):
"""GUI main window, corresponds to OS window"""
def __init__(self, project: Project, wrappedData: 'ApiWindow'):
_coreClassWindow.__init__(self, project, wrappedData)
logger = Logging.getLogger()
logger.debug('MainWindow>> project: %s' % project)
logger.debug('MainWindow>> project.application: %s' % project.application)
application = project.application
_GuiMainWindow.__init__(self, application=application)
# hide the window here and make visible later
self.hide()
# patches for now:
project._mainWindow = self
# logger.debug('MainWindow>> project._mainWindow: %s' % project._mainWindow)
application._mainWindow = self
application.ui.mainWindow = self
# logger.debug('MainWindow>> application: %s' % application)
# logger.debug('MainWindow>> application.project: %s' % application.project)
# logger.debug('MainWindow>> application._mainWindow: %s' % application._mainWindow)
# logger.debug('MainWindow>> application.ui.mainWindow: %s' % application.ui.mainWindow)
setWidgetFont(self, )
from ccpn.ui.gui.lib.GuiWindow import GuiWindow as _GuiWindow
[docs]class SideWindow(_coreClassWindow, _GuiWindow):
"""GUI side window, corresponds to OS window"""
def __init__(self, project: Project, wrappedData: 'ApiWindow'):
_coreClassWindow.__init__(self, project, wrappedData)
_GuiWindow.__init__(self, project.application)
def _factoryFunction(project: Project, wrappedData):
"""create Window, dispatching to subtype depending on wrappedData"""
if wrappedData.title == 'Main':
return MainWindow(project, wrappedData)
else:
return SideWindow(project, wrappedData)
Gui._factoryFunctions[_coreClassWindow.className] = _factoryFunction
## Task class
# There is no special GuiTask, so nothing needs to be done
## Mark class - put in namespace for documentation
Mark = _coreClassMap['Mark']
## SpectrumDisplay class
_coreClassSpectrumDisplay = _coreClassMap['SpectrumDisplay']
from ccpn.ui.gui.modules.SpectrumDisplay1d import SpectrumDisplay1d as _SpectrumDisplay1d
[docs]class StripDisplay1d(_coreClassSpectrumDisplay, _SpectrumDisplay1d):
"""1D bound display"""
def __init__(self, project: Project, wrappedData: 'ApiBoundDisplay'):
"""Local override init for Qt subclass"""
Logging.getLogger().debug('StripDisplay1d>> project: %s, project.application: %s' %
(project, project.application))
_coreClassSpectrumDisplay.__init__(self, project, wrappedData)
# hack for now
self.application = project.application
_SpectrumDisplay1d.__init__(self, mainWindow=self.application.ui.mainWindow)
from ccpn.ui.gui.modules.SpectrumDisplayNd import SpectrumDisplayNd as _SpectrumDisplayNd
#TODO: Need to check on the consequences of hiding name from the wrapper
# NB: GWV had to comment out the name property to make it work
# conflicts existed between the 'name' and 'window' attributes of the two classes
# the pyqtgraph descendents need name(), GuiStripNd had 'window', but that could be replaced with
# mainWindow throughout
[docs]class SpectrumDisplayNd(_coreClassSpectrumDisplay, _SpectrumDisplayNd):
"""ND bound display"""
def __init__(self, project: Project, wrappedData: 'ApiBoundDisplay'):
"""Local override init for Qt subclass"""
Logging.getLogger().debug('SpectrumDisplayNd>> project: %s, project.application: %s' % (project, project.application))
_coreClassSpectrumDisplay.__init__(self, project, wrappedData)
# hack for now;
self.application = project.application
_SpectrumDisplayNd.__init__(self, mainWindow=self.application.ui.mainWindow)
#old name
StripDisplayNd = SpectrumDisplayNd
def _factoryFunction(project: Project, wrappedData):
"""create SpectrumDisplay, dispatching to subtype depending on wrappedData"""
if wrappedData.is1d:
return StripDisplay1d(project, wrappedData)
else:
return StripDisplayNd(project, wrappedData)
Gui._factoryFunctions[_coreClassSpectrumDisplay.className] = _factoryFunction
## Strip class
_coreClassStrip = _coreClassMap['Strip']
from ccpn.ui.gui.lib.GuiStrip1d import GuiStrip1d as _GuiStrip1d
[docs]class Strip1d(_coreClassStrip, _GuiStrip1d):
"""1D strip"""
def __init__(self, project: Project, wrappedData: 'ApiBoundStrip'):
"""Local override init for Qt subclass"""
_coreClassStrip.__init__(self, project, wrappedData)
Logging.getLogger().debug('Strip1d>> spectrumDisplay: %s' % self.spectrumDisplay)
_GuiStrip1d.__init__(self, self.spectrumDisplay)
# cannot add the Frame until fully done
strips = self.spectrumDisplay.orderedStrips
if self in strips:
stripIndex = strips.index(self)
else:
stripIndex = len(strips)
Logging.getLogger().warning('Strip ordering not defined for %s in %s' % (str(self.pid), str(self.spectrumDisplay.pid)))
tilePosition = self.tilePosition
if self.spectrumDisplay.stripArrangement == 'Y':
# strips are arranged in a row
# self.spectrumDisplay.stripFrame.layout().addWidget(self, 0, stripIndex)
if True: #tilePosition is None:
self.spectrumDisplay.stripFrame.layout().addWidget(self, 0, stripIndex)
self.tilePosition = (0, stripIndex)
else:
self.spectrumDisplay.stripFrame.layout().addWidget(self, tilePosition[0], tilePosition[1])
elif self.spectrumDisplay.stripArrangement == 'X':
# strips are arranged in a column
# self.spectrumDisplay.stripFrame.layout().addWidget(self, stripIndex, 0)
if True: #tilePosition is None:
self.spectrumDisplay.stripFrame.layout().addWidget(self, stripIndex, 0)
self.tilePosition = (0, stripIndex)
else:
self.spectrumDisplay.stripFrame.layout().addWidget(self, tilePosition[1], tilePosition[0])
elif self.spectrumDisplay.stripArrangement == 'T':
# NOTE:ED - Tiled plots not fully implemented yet
Logging.getLogger().warning('Tiled plots not implemented for spectrumDisplay: %s' % str(self.spectrumDisplay.pid))
else:
Logging.getLogger().warning('Strip direction is not defined for spectrumDisplay: %s' % str(self.spectrumDisplay.pid))
from ccpn.ui.gui.lib.GuiStripNd import GuiStripNd as _GuiStripNd
[docs]class StripNd(_coreClassStrip, _GuiStripNd):
"""ND strip """
def __init__(self, project: Project, wrappedData: 'ApiBoundStrip'):
"""Local override init for Qt subclass"""
_coreClassStrip.__init__(self, project, wrappedData)
Logging.getLogger().debug('StripNd>> spectrumDisplay=%s' % self.spectrumDisplay)
_GuiStripNd.__init__(self, self.spectrumDisplay)
# cannot add the Frame until fully done
strips = self.spectrumDisplay.orderedStrips
if self in strips:
stripIndex = strips.index(self)
else:
stripIndex = len(strips)
Logging.getLogger().warning('Strip ordering not defined for %s in %s' % (str(self.pid), str(self.spectrumDisplay.pid)))
tilePosition = self.tilePosition
if self.spectrumDisplay.stripArrangement == 'Y':
# strips are arranged in a row
# self.spectrumDisplay.stripFrame.layout().addWidget(self, 0, stripIndex)
if True: #tilePosition is None:
self.spectrumDisplay.stripFrame.layout().addWidget(self, 0, stripIndex)
self.tilePosition = (0, stripIndex)
else:
self.spectrumDisplay.stripFrame.layout().addWidget(self, tilePosition[0], tilePosition[1])
elif self.spectrumDisplay.stripArrangement == 'X':
# strips are arranged in a column
# self.spectrumDisplay.stripFrame.layout().addWidget(self, stripIndex, 0)
if True: #tilePosition is None:
self.spectrumDisplay.stripFrame.layout().addWidget(self, stripIndex, 0)
self.tilePosition = (0, stripIndex)
else:
self.spectrumDisplay.stripFrame.layout().addWidget(self, tilePosition[1], tilePosition[0])
elif self.spectrumDisplay.stripArrangement == 'T':
# NOTE:ED - Tiled plots not fully implemented yet
Logging.getLogger().warning('Tiled plots not implemented for spectrumDisplay: %s' % str(self.spectrumDisplay.pid))
else:
Logging.getLogger().warning('Strip direction is not defined for spectrumDisplay: %s' % str(self.spectrumDisplay.pid))
def _factoryFunction(project: Project, wrappedData):
"""create SpectrumDisplay, dispatching to subtype depending on wrappedData"""
apiSpectrumDisplay = wrappedData.spectrumDisplay
if apiSpectrumDisplay.is1d:
return Strip1d(project, wrappedData)
else:
return StripNd(project, wrappedData)
Gui._factoryFunctions[_coreClassStrip.className] = _factoryFunction
## Axis class - put in namespace for documentation
Axis = _coreClassMap['Axis']
# Any Factory function to _implementation or abstractWrapper
#
## SpectrumView class
_coreClassSpectrumView = _coreClassMap['SpectrumView']
from ccpn.ui.gui.lib.GuiSpectrumView1d import GuiSpectrumView1d as _GuiSpectrumView1d
class _SpectrumView1d(_coreClassSpectrumView, _GuiSpectrumView1d):
"""1D Spectrum View"""
def __init__(self, project: Project, wrappedData: 'ApiStripSpectrumView'):
"""Local override init for Qt subclass"""
_coreClassSpectrumView.__init__(self, project, wrappedData)
# hack for now
self.application = project.application
Logging.getLogger().debug('SpectrumView1d>> %s' % self)
_GuiSpectrumView1d.__init__(self)
from ccpn.ui.gui.lib.GuiSpectrumViewNd import GuiSpectrumViewNd as _GuiSpectrumViewNd
class _SpectrumViewNd(_coreClassSpectrumView, _GuiSpectrumViewNd):
"""ND Spectrum View"""
def __init__(self, project: Project, wrappedData: 'ApiStripSpectrumView'):
"""Local override init for Qt subclass"""
_coreClassSpectrumView.__init__(self, project, wrappedData)
# hack for now
self.application = project.application
Logging.getLogger().debug('SpectrumViewNd>> self=%s strip=%s' % (self, self.strip))
_GuiSpectrumViewNd.__init__(self)
def _factoryFunction(project: Project, wrappedData):
"""create SpectrumView, dispatching to subtype depending on wrappedData"""
if 'intensity' in wrappedData.strip.spectrumDisplay.axisCodes:
# 1D display
return _SpectrumView1d(project, wrappedData)
else:
# ND display
return _SpectrumViewNd(project, wrappedData)
Gui._factoryFunctions[_coreClassSpectrumView.className] = _factoryFunction
## PeakListView class
_coreClassPeakListView = _coreClassMap['PeakListView']
from ccpn.ui.gui.lib.GuiPeakListView import GuiPeakListView as _GuiPeakListView
class _PeakListView(_coreClassPeakListView, _GuiPeakListView):
"""Peak List View for 1D or nD PeakList"""
def __init__(self, project: Project, wrappedData: 'ApiStripPeakListView'):
"""Local override init for Qt subclass"""
_coreClassPeakListView.__init__(self, project, wrappedData)
# hack for now
self.application = project.application
_GuiPeakListView.__init__(self)
self._init()
Gui._factoryFunctions[_coreClassPeakListView.className] = _PeakListView
## IntegralListView class
_coreClassIntegralListView = _coreClassMap['IntegralListView']
from ccpn.ui.gui.lib.GuiIntegralListView import GuiIntegralListView as _GuiIntegralListView
class _IntegralListView(_coreClassIntegralListView, _GuiIntegralListView):
"""Integral List View for 1D or nD IntegralList"""
def __init__(self, project: Project, wrappedData: 'ApiStripIntegralListView'):
"""Local override init for Qt subclass"""
_coreClassIntegralListView.__init__(self, project, wrappedData)
# hack for now
self.application = project.application
_GuiIntegralListView.__init__(self)
self._init()
Gui._factoryFunctions[_coreClassIntegralListView.className] = _IntegralListView
## MultipletListView class
_coreClassMultipletListView = _coreClassMap['MultipletListView']
from ccpn.ui.gui.lib.GuiMultipletListView import GuiMultipletListView as _GuiMultipletListView
class _MultipletListView(_coreClassMultipletListView, _GuiMultipletListView):
"""Multiplet List View for 1D or nD MultipletList"""
def __init__(self, project: Project, wrappedData: 'ApiStripMultipletListView'):
"""Local override init for Qt subclass"""
_coreClassMultipletListView.__init__(self, project, wrappedData)
# hack for now
self.application = project.application
_GuiMultipletListView.__init__(self)
self._init()
Gui._factoryFunctions[_coreClassMultipletListView.className] = _MultipletListView
# Delete what we do not want in namespace
del _factoryFunction
# del coreClass