DEBUGGING = ('-d' in sys.argv or '--debug' in sys.argv) EXIT_CODE_SUCCESS = 0 KEY_PROGRAM = '_program' PROJECT_NAME = "Argos" VERSION = '0.4.1' KEY_VERSION = '_version' class InspectorRegistry(BaseRegistry):
    """ Class that maintains the collection of registered inspector classes.
        See the base class documentation for more info.
    """
    ITEM_CLASS = InspectorRegItem

    def __init__(self):
        """ Constructor
        """
        super(InspectorRegistry, self).__init__()


    @property
    def registryName(self):
        """ Human readable name for this registry.
        """
        return "Inspector"


    def getDefaultItems(self):
        """ Returns a list with the default plugins in the inspector registry.
        """
        plugins = [
            InspectorRegItem('Line Plot',
                             'argos.inspector.pgplugins.lineplot1d.PgLinePlot1d',
                             shortCut='Ctrl+1'),
            InspectorRegItem('Image Plot',
                             'argos.inspector.pgplugins.imageplot2d.PgImagePlot2d',
                             shortCut='Ctrl+2'),
            InspectorRegItem(DEFAULT_INSPECTOR,
                             'argos.inspector.qtplugins.table.TableInspector',
                             shortCut='Ctrl+3'),
            InspectorRegItem('Text',
                             'argos.inspector.qtplugins.text.TextInspector',
                             shortCut='Ctrl+4'),
            ]
        if DEBUGGING:
            plugins.append(InspectorRegItem('Image Plot (Old)',
                                            'argos.inspector.pgplugins.old_imageplot2d.PgImagePlot2d'))
            plugins.append(InspectorRegItem('Debug Inspector',
                                            'argos.inspector.debug.DebugInspector'))
        return plugins DEFAULT_INSPECTOR = 'Table' API_PYSIDE2 = 'pyside2'
API_PYQT5 = 'pyqt5'
ALL_API = [API_PYQT5, API_PYSIDE2]
QT_API_NAME = os.environ.get('QT_API')
    QT_API_NAME = None
        QT_API_NAME = API_PYQT5
        QT_API_NAME = API_PYSIDE2
        QT_API_NAME = API_PYQT5
        QT_API_NAME = API_PYSIDE2 def handleException(exc_type, exc_value, exc_traceback):

    traceback.format_exception(exc_type, exc_value, exc_traceback)

    logger.critical("Bug: uncaught {}".format(exc_type.__name__),
                    exc_info=(exc_type, exc_value, exc_traceback))
    if info.DEBUGGING:
        logger.info("Quitting application with exit code 1")
        sys.exit(1)
    else:
        # Constructing a QApplication in case this hasn't been done yet.
        if not QtWidgets.qApp:
            _app = QtWidgets.QApplication()

        msgBox = ResizeDetailsMessageBox()
        msgBox.setText("Bug: uncaught {}".format(exc_type.__name__))
        msgBox.setInformativeText(str(exc_value))
        lst = traceback.format_exception(exc_type, exc_value, exc_traceback)
        msgBox.setDetailedText("".join(lst))
        msgBox.setIcon(QtWidgets.QMessageBox.Warning)
        msgBox.exec_()
        logger.info("Quitting application with exit code 1")
        sys.exit(1) def initQApplication():
    """ Initializes the QtWidgets.QApplication instance. Creates one if it doesn't exist.

        Sets Argos specific attributes, such as the OrganizationName, so that the application
        persistent settings are read/written to the correct settings file/winreg. It is therefore
        important to call this function at startup. The ArgosApplication constructor does this.

        Returns the application.
    """
    # PyQtGraph recommends raster graphics system for OS-X.
    if 'darwin' in sys.platform:
        graphicsSystem = "raster" # raster, native or opengl
        os.environ.setdefault('QT_GRAPHICSSYSTEM', graphicsSystem)
        logger.debug("Setting QT_GRAPHICSSYSTEM to: {}".format(graphicsSystem))

    app = QtWidgets.QApplication(sys.argv)
    initArgosApplicationSettings(app)
    return app def nameToIdentifier(fullName):
    """ Constructs the regItem identifier given its full name
    """
    return string_to_identifier(fullName, white_space_becomes='') class CmLibSingleton(CmLib, SingletonMixin):

    def __init__(self, **kwargs):
        super(CmLibSingleton, self).__init__(**kwargs)

        logger.debug("CmLib singleton: {}".format(self))

        cmDataDir = DATA_DIR
        logger.info("Importing color map library from: {}".format(cmDataDir))

        # Don't import from Color Brewer since those are already included in MatPlotLib.
        # With sub-sampling the color maps similar maps can be achieved as the Color Brewer maps.
        excludeList = ['ColorBrewer2']
        for path in listdir(cmDataDir):
            if path in excludeList:
                logger.debug("Not importing catalogue from exlude list: {}".format(excludeList))
                continue

            fullPath = os.path.join(cmDataDir, path)
            if os.path.isdir(fullPath):
                self.load_catalog(fullPath)

        logger.debug("Number of color maps: {}".format(len(self.color_maps))) DEF_FAV_COLOR_MAPS = [
    'SciColMaps/Oleron', 'SciColMaps/Nuuk', 'SciColMaps/Acton', 'CET/CET-CBL2', 'MatPlotLib/Gray',
    'CET/CET-C2', 'CET/CET-R2', 'MatPlotLib/BrBG', 'MatPlotLib/Tab20', 'MatPlotLib/Magma',
    'MatPlotLib/Tab10', 'MatPlotLib/Cubehelix', 'MatPlotLib/Viridis', 'MatPlotLib/Coolwarm'] ICON_COLOR_UNDEF = RtiIconFactory.COLOR_UNDEF
ICON_COLOR_UNKNOWN = RtiIconFactory.COLOR_UNKNOWN
ICON_COLOR_ERROR = RtiIconFactory.COLOR_ERROR
ICON_COLOR_MEMORY = RtiIconFactory.COLOR_MEMORY
ICON_COLOR_EXDIR = '#00BBFF'
ICON_COLOR_H5PY = '#00EE88'
ICON_COLOR_NCDF4 = '#0088FF'
ICON_COLOR_NUMPY = '#987456'
ICON_COLOR_PANDAS = '#FB9A99'
ICON_COLOR_PILLOW = '#880088'
ICON_COLOR_SCIPY = ICON_COLOR_NUMPY
    FIELDS  = BaseRegItem.FIELDS[:1] + ['iconColor', 'globs'] + BaseRegItem.FIELDS[1:]
    TYPES   = BaseRegItem.TYPES[:1] + [RegType.ColorStr, RegType.String] + BaseRegItem.TYPES[1:]
    LABELS  = BaseRegItem.LABELS[:1] + ['Icon Color', 'Globs'] + BaseRegItem.LABELS[1:]
    STRETCH = BaseRegItem.STRETCH[:1] + [False, True] + BaseRegItem.STRETCH[1:]
    COL_DECORATION = 0  # Display Icon in the main column
    ITEM_CLASS = RtiRegItem
    DIRECTORY_REG_ITEM = RtiRegItem('Directory', 'argos.repo.filesytemrtis.DirectoryRti',
                                    iconColor=ICON_COLOR_UNKNOWN)
class RtiRegItem(BaseRegItem):
class RtiRegistry(BaseRegistry):
    def __init__(self, name='', absClassName='', pythonPath='', iconColor=ICON_COLOR_UNDEF, globs=''):
    def __str__(self):
    def iconColor(self):
    def globList(self):
    def pathNameMatchesGlobs(self, path):
    def getFileDialogFilter(self):
    def decoration(self):
    def __init__(self):
    def getRtiRegItemByExtension(self, filePath):
    def getFileDialogFilter(self):
    def extraItemsForOpenAsMenu(self):
    def getDefaultItems(self):
def createGlobalRegistryFunction():
    def accessGlobalRegistry(): class RepoTreeModel(BaseTreeModel):
    """ An implementation QAbstractItemModel that offers read-only access of the application data
        for QTreeViews. The underlying data is stored as repository tree items (BaseRti
        descendants).
    """
    HEADERS = ["Name", "Path", "Dimensions", "Shape", "Kind",
               "Element Type", "Type", "Unit", "Missing data", "Chunking", "Summary",
               "File name", "Item class", "Is open", "Error"]

    (COL_NODE_NAME, COL_NODE_PATH, COL_DIMS, COL_SHAPE, COL_KIND,
     COL_ELEM_TYPE, COL_TYPE, COL_UNIT, COL_MISSING_DATA, COL_CHUNKING, COL_SUMMARY,
     COL_FILE_NAME, COL_RTI_TYPE, COL_IS_OPEN, COL_EXCEPTION) = range(len(HEADERS))

    COL_DECORATION = COL_NODE_NAME  # Column number that contains the icon. None for no icons


    def __init__(self, parent=None):
        """ Constructor
        """
        super(RepoTreeModel, self).__init__(parent=parent)
        self._invisibleRootTreeItem = BaseRti(nodeName='<invisible-root>', iconColor='#FFFFFF')
        self._invisibleRootTreeItem.model = self
        self._isEditable = False


    def itemData(self, treeItem, column, role=Qt.DisplayRole):
        """ Returns the data stored under the given role for the item. O
        """
        if role == Qt.DisplayRole:
            if column == self.COL_NODE_NAME:
                return treeItem.nodeName
            elif column == self.COL_NODE_PATH:
                return treeItem.nodePath
            elif column == self.COL_DIMS:
                if treeItem.isSliceable:
                    return " × ".join(str(elem) for elem in treeItem.dimensionNames)
                else:
                    return ""
            elif column == self.COL_SHAPE:
                if treeItem.isSliceable:
                    return " × ".join(str(elem) for elem in treeItem.arrayShape)
                else:
                    return ""
            elif column == self.COL_IS_OPEN:
                # Only show for RTIs that actually open resources.
                # TODO: this must be clearer. Use CanFetchChildren? Set is Open to None by default?
                if treeItem.hasChildren():
                    return str(treeItem.isOpen)
                else:
                    return ""
            elif column == self.COL_KIND:
                return treeItem.dimensionality
            elif column == self.COL_ELEM_TYPE:
                return treeItem.elementTypeName
            elif column == self.COL_TYPE:
                return treeItem.typeName
            elif column == self.COL_FILE_NAME:
                return treeItem.fileName if hasattr(treeItem, 'fileName') else ''
            elif column == self.COL_UNIT:
                return treeItem.unit
            elif column == self.COL_CHUNKING:
                return treeItem.chunksString
            elif column == self.COL_MISSING_DATA:
                return to_string(treeItem.missingDataValue, noneFormat='') # empty str for Nones
            elif column == self.COL_RTI_TYPE:
                return type_name(treeItem)
            elif column == self.COL_EXCEPTION:
                return str(treeItem.exception) if treeItem.exception else ''
            elif column == self.COL_SUMMARY:
                return treeItem.summary
            else:
                raise ValueError("Invalid column: {}".format(column))

        elif role == Qt.ToolTipRole:
            if treeItem.exception:
                return str(treeItem.exception)
            if column == self.COL_NODE_NAME:
                return treeItem.nodePath # Also path when hovering over the name
            elif column == self.COL_NODE_PATH:
                return treeItem.nodePath
            elif column == self.COL_DIMS:
                if treeItem.isSliceable:
                    if treeItem.dimensionPaths is not None:
                        return " ×\n".join(str(elem) for elem in treeItem.dimensionPaths)
                    else:
                        return " × ".join(str(elem) for elem in treeItem.dimensionNames)
                else:
                    return ""
            elif column == self.COL_SHAPE:
                if treeItem.isSliceable:
                    return " × ".join(str(elem) for elem in treeItem.arrayShape)
                else:
                    return ""
            elif column == self.COL_UNIT:
                return treeItem.unit
            elif column == self.COL_CHUNKING:
                return treeItem.chunksString
            elif column == self.COL_MISSING_DATA:
                return to_string(treeItem.missingDataValue, noneFormat='') # empty str for Nones
            elif column == self.COL_RTI_TYPE:
                return type_name(treeItem)
            elif column == self.COL_KIND:
                return treeItem.dimensionality
            elif column == self.COL_ELEM_TYPE:
                return treeItem.elementTypeName
            elif column == self.COL_TYPE:
                return treeItem.typeName
            elif column == self.COL_FILE_NAME:
                return treeItem.fileName if hasattr(treeItem, 'fileName') else ''
            elif column == self.COL_SUMMARY:
                return treeItem.summary
            else:
                return None

        elif role == Qt.TextAlignmentRole:
            if column == self.COL_SUMMARY:
                return ALIGN_RIGHT
            else:
                return ALIGN_LEFT

        else:
            return super(RepoTreeModel, self).itemData(treeItem, column, role=role)


    def canFetchMore(self, parentIndex):
        """ Returns true if there is more data available for parent; otherwise returns false.
        """
        parentItem = self.getItem(parentIndex)
        if not parentItem:
            return False

        return parentItem.canFetchChildren()


    def fetchMore(self, parentIndex):
        """ Fetches any available data for the items with the parent specified by the parent index.
        """
        parentItem = self.getItem(parentIndex)
        if not parentItem:
            return

        if not parentItem.canFetchChildren():
            return

        for childItem in parentItem.fetchChildren():
            self.insertItem(childItem, parentIndex=parentIndex)

        # Check that Rti implementation correctly sets canFetchChildren
        assert not parentItem.canFetchChildren(), \
            "not all children fetched: {}".format(parentItem)


    def findFileRtiIndex(self, childIndex):
        """ Traverses the tree upwards from the item at childIndex until the tree
            item is found that represents the file the item at childIndex
        """
        parentIndex = childIndex.parent()
        if not parentIndex.isValid():
            return childIndex
        else:
            parentItem = self.getItem(parentIndex)
            childItem = self.getItem(childIndex)
            if parentItem.fileName == childItem.fileName:
                return self.findFileRtiIndex(parentIndex)
            else:
                return childIndex


    def reloadFileAtIndex(self, itemIndex, rtiRegItem=None):
        """ Reloads the item at the index by removing the repo tree item and inserting a new one.

            The new item will have by of type rtiClass. If rtiRegItem is None (the default), the
            new rtiClass will be the same as the old one.
        """
        fileRtiParentIndex = itemIndex.parent()
        fileRti = self.getItem(itemIndex)
        position = fileRti.childNumber()
        fileName = fileRti.fileName

        # Delete old RTI and Insert a new one instead.
        self.deleteItemAtIndex(itemIndex) # this will close the items resources.

        if rtiRegItem is None:
            # Do NOT autodetect but use the class from the the RTI that's being replaced.
            rtiClass = type(fileRti)
            logger.debug("Recreating class from previous RTI class: {}".format(rtiClass))
            repoTreeItem = rtiClass.createFromFileName(fileName, fileRti.iconColor)

            assert repoTreeItem.parentItem is None, "repoTreeItem {!r}".format(repoTreeItem)
            return self.insertItem(repoTreeItem, position=position, parentIndex=fileRtiParentIndex)

        else:
            return self.loadFile(fileName, rtiRegItem, position=position,
                                 parentIndex=fileRtiParentIndex)


    def loadFile(self, fileName, rtiRegItem,
                 position=None, parentIndex=QtCore.QModelIndex()):
        """ Loads a file in the repository as a repo tree item of class rtiClass.
            Autodetects the RTI type if rtiClass is None.
            If position is None the child will be appended as the last child of the parent.
            Returns the index of the newly inserted RTI
        """
        check_class(rtiRegItem, RtiRegItem, allow_none=True)
        fileName = normRealPath(fileName)
        logger.info("Loading data from: {!r}".format(fileName))

        rtiClass = rtiRegItem.getClass(tryImport=True) if rtiRegItem else None
        if rtiClass is None:
            repoTreeItem = createRtiFromFileName(fileName)
        else:
            repoTreeItem = rtiClass.createFromFileName(fileName, rtiRegItem.iconColor)

        assert repoTreeItem.parentItem is None, "repoTreeItem {!r}".format(repoTreeItem)
        return self.insertItem(repoTreeItem, position=position, parentIndex=parentIndex) def getConfigParameter(cfg, path, alt=None):
    """ Finds the findConfigParameter. Returns alternative value if not found.

        Will still log a warning if the parameter is not found.

        :param cfg: config dictionary. Can be a recursive dict (a dict of dicts, etc)
        :param path: Slash separated parameter path. E.g.: '/dict1/dict2/parameter'
    """
    try:
        return findConfigParameter(cfg, path)
    except KeyError:
        logger.warning("Could not find path in config: {!r}".format(path))
        return alt def deleteParameter(cfg, parentPath, parName):
    """ Deletes a parameter from the config dict

        :param cfg: config dictionary
        :param parentPath: the path of the parent dict that contains the paremeter
        :param parName: name of the element that will be removed
    """
    logger.debug("Deleting {!r} from parent dict: {!r}".format(parName, parentPath))
    try:
        parentCfg = findConfigParameter(cfg, parentPath)
    except KeyError:
        logger.warning("Could not find path in configur: {!r}".format(parentPath))
        return

    try:
        del parentCfg[parName]
    except KeyError:
        logger.warning("Could not find {!r} in parent dict: {!r}".format(parName, parentPath))
        return
    except Exception as ex:
        # The element might not be a dict
        logger.warning("Error while deleting {!r} from parent dict {!r}: {}"
                       .format(parName, parentPath, ex))
        return def argosConfigDirectory():
    r""" Gets the Argos configuration directory.

        The config directory is platform dependent. (See the module doc string at the top).
    """
    return os.path.join(baseConfigLocation(), ORGANIZATION_NAME, SCRIPT_NAME) def normRealPath(path):
    """ Returns the normalized real path.

        If the path is empty or None it is returned as-is. This is to prevent expanding to the
        current directory in case of undefined paths.
    """
    if path:
        return os.path.normpath(os.path.realpath(path))
    else:
        return path def ensureFileExists(pathName):
    """ Creates an empty file file if it doesn't yet exist. Also creates necessary directory path.

        :returns: the normRealPath of the path name.
    """
    pathName = normRealPath(pathName)
    dirName, fileName = os.path.split(pathName)
    ensureDirectoryExists(dirName)

    if not os.path.exists(pathName):
        logger.info("Creating empty file: {}".format(pathName))
        with open(pathName, 'w') as f:
            f.write('')

    # Check file exists and is not a directory.
    assert os.path.isfile(pathName), "File does not exist or is a directory: {!r}".format(pathName)
    return pathName def versionStrToTuple(versionStr):
    """ Converts a version string to tuple

        Version string has format <major>.<minor>.<patch><postfix> where postfix can be empty.
            E.g. versionStrToTuple('0.3.1rc1') returns (0, 3, 1, 'rc1')
    """
    matchObj = re.match(r"(\d+)\.(\d+)\.(\d+)(\w*)", versionStr)

    major = int(matchObj.group(1))
    minor = int(matchObj.group(2))
    patch = int(matchObj.group(3))
    postfix = matchObj.group(4)

    return (major, minor, patch, postfix) class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, argosApplication):
    def finalize(self):
    def __setupViews(self):
    def __addTableHeadersSubMenu(self, menuTitle, treeView):
    def __setupMenus(self):
    def __createInspectorActionGroup(self, parent):
    def __setupDockWidgets(self):
    def windowNumber(self):
    def inspectorRegItem(self):
    def inspectorId(self):
    def inspectorName(self):
    def inspectorFullName(self):
    def inspector(self):
    def collector(self):
    def argosApplication(self):
    def _repopulateOpenRecentMenu(self, *args, **kwargs):
            def createTrigger():
    def _repopulateOpenAsMenu(self, *args, **kwargs):
            def createTrigger():
    def repopulateWindowMenu(self, actionGroup):
    def dockWidget(self, widget, title, area):
    def updateWindowTitle(self):
    def getInspectorActionById(self, identifier):
    def setAndDrawInspectorById(self, identifier):
    def setInspectorById(self, identifier):
    def _storeInspectorState(self, inspectorRegItem, inspector):
    def execPluginsDialog(self, label, registry):
    def collectorContentsChanged(self, reason):
    def configContentsChanged(self, configTreeItem):
    def showMessage(self, msg):
    def drawInspectorContents(self, reason, origin=None):
    def openFiles(self, fileNames=None, rtiRegItem=None, caption=None, fileMode=None):
    def selectRtiByPath(self, path):
    def trySelectRtiByPath(self, path):
    def openInWebBrowser(self, url):
    def openInExternalApp(self, fileName):
    def marshall(self):
    def unmarshall(self, cfg):
    def cloneWindow(self):
    def activateAndRaise(self):
    def event(self, ev):
    def closeEvent(self, event):
    def about(self):
    def myTest(self):
    def __not__used(self, rootNodes):
    def addTestData(self):
    def appendFailedTest(self):
    def walkCurrentRepoNode(self, allInspectors: bool, allRepoTabs: bool):
    def walkAllRepoNodes(self, allInspectors: bool, allRepoTabs: bool):
    def walkRepoNodes(self, nodePaths, allInspectors: bool, allRepoTabs: bool):
        def visitNodes(index):
import copy
import glob
import json
import logging
import os.path
import pprint
import shutil
import sys
from datetime import datetime
from argos.info import DEBUGGING, EXIT_CODE_SUCCESS, KEY_PROGRAM, PROJECT_NAME, VERSION, KEY_VERSION
from argos.inspector.registry import InspectorRegistry, DEFAULT_INSPECTOR
from argos.qt import QtCore, QtWidgets, QtSlot
from argos.qt.misc import handleException, initQApplication
from argos.reg.basereg import nameToIdentifier
from argos.repo.colors import CmLibSingleton, DEF_FAV_COLOR_MAPS
from argos.repo.registry import globalRtiRegistry
from argos.repo.repotreemodel import RepoTreeModel
from argos.utils.config import getConfigParameter, deleteParameter
from argos.utils.dirs import argosConfigDirectory, normRealPath, ensureFileExists
from argos.utils.moduleinfo import versionStrToTuple
from argos.widgets.mainwindow import MainWindow, UpdateReason
# -*- coding: utf-8 -*-

# This file is part of Argos.
#
# Argos is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Argos is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Argos. If not, see <http://www.gnu.org/licenses/>.

""" Version and other info for this program
"""
from __future__ import print_function




logger = logging.getLogger(__name__)


def _updateConfig(cfg):
    """ Updates the Argos config dict for the new version after an update.

        Best to make sure that a backup has been made before.
    """
    logger.debug("_updateConfig called")

    if not cfg: # The config may be empty, e.g. when the first time the application is started
        logger.debug("Empty config file in _updateConfig. Returning.")
        return cfg

    cfg = copy.deepcopy(cfg)

    # Version is written to config from 0.4.0, so we assume version <= 0.3.x if no version was found
    # We drop the postfix (e.g. 'rc1') from the version tuples so that version tuples can be
    # compared directly with logical operators.

    cfgVersion = versionStrToTuple(cfg.get(KEY_VERSION, '0.3.99'))[0:3]