Source code for orangecanvas.config

"""
Orange Canvas Configuration

"""
from orangecanvas.localization.si import plsi, plsi_sz, z_besedo
from orangecanvas.localization import Translator  # pylint: disable=wrong-import-order
_tr = Translator("orangecanvas", "biolab.si", "Orange")
del Translator

import os
import sys
import logging
import warnings
import typing
import pkgutil

from typing import Dict, Optional, Tuple, List, Union, Iterable, Any

import packaging.version

from AnyQt.QtGui import (
    QPainter, QFont, QFontMetrics, QColor, QPixmap, QImage, QIcon
)

from AnyQt.QtCore import (
    Qt, QCoreApplication, QPoint, QRect, QSettings, QStandardPaths, QEvent
)

from .gui.utils import windows_set_current_process_app_user_model_id
from .gui.svgiconengine import SvgIconEngine
from .utils.settings import Settings, config_slot
from .utils.pkgmeta import EntryPoint, Distribution, entry_points

if typing.TYPE_CHECKING:
    import requests
    from .scheme import Scheme
    T = typing.TypeVar("T")


log = logging.getLogger(__name__)

__version__ = "0.0"


#: Entry point by which widgets are registered.
WIDGETS_ENTRY = "orangecanvas.widgets"

#: Entry point by which add-ons register with importlib.metadata
ADDONS_ENTRY = "orangecanvas.addon"

#: Parameters for searching add-on packages in PyPi using xmlrpc api.
ADDON_PYPI_SEARCH_SPEC = {"keywords": ["orange", "add-on"]}

EXAMPLE_WORKFLOWS_ENTRY = "orangecanvas.examples"


def standard_location(type):
    warnings.warn(
        "Use QStandardPaths.writableLocation", DeprecationWarning,
        stacklevel=2
    )
    return QStandardPaths.writableLocation(type)


standard_location.DesktopLocation = QStandardPaths.DesktopLocation      # type: ignore
standard_location.DataLocation = QStandardPaths.AppLocalDataLocation    # type: ignore
standard_location.CacheLocation = QStandardPaths.CacheLocation          # type: ignore
standard_location.DocumentsLocation = QStandardPaths.DocumentsLocation  # type: ignore


class Config:
    """
    Application configuration.
    """
    #: Organization domain
    OrganizationDomain = ""  # type: str
    #: The application name
    ApplicationName = ""     # type: str
    #: Version
    ApplicationVersion = ""  # type: str
    #: AppUserModelID as used on windows for grouping in the task bar
    #: (https://docs.microsoft.com/en-us/windows/win32/shell/appids).
    #: This ensures the program does not group with other Python programs
    #: and gets its own task icon.
    AppUserModelID = None    # type: Optional[str]

[docs] def init(self): """ Initialize the QCoreApplication.organizationDomain, applicationName, applicationVersion and the default settings format. Should only be run once at application startup. """ QCoreApplication.setOrganizationDomain(self.OrganizationDomain) QCoreApplication.setApplicationName(self.ApplicationName) QCoreApplication.setApplicationVersion(self.ApplicationVersion) QSettings.setDefaultFormat(QSettings.IniFormat) app = QCoreApplication.instance() if self.AppUserModelID: windows_set_current_process_app_user_model_id(self.AppUserModelID) if app is not None: QCoreApplication.sendEvent(app, QEvent(QEvent.PolishRequest))
[docs] def application_icon(self): # type: () -> QIcon """ Return the main application icon. """ return QIcon()
[docs] def splash_screen(self): # type: () -> Tuple[QPixmap, QRect] """ Return a splash screen pixmap and an text area within it. The text area is used for displaying text messages during application startup. The default implementation returns a bland rectangle splash screen. Returns ------- t : Tuple[QPixmap, QRect] A QPixmap and a rect area within it. """ return QPixmap(), QRect()
def widgets_entry_points(self): # type: () -> Iterable[EntryPoint] """ Return an iterator over entry points defining the set of 'nodes/widgets' available to the workflow model. """ return iter(()) def addon_entry_points(self): # type: () -> Iterable[EntryPoint] return iter(())
[docs] def addon_pypi_search_spec(self): return {}
def addon_defaults_list( self, session=None # type: Optional[requests.Session] ): # type: (...) -> List[Dict[str, Union[str, list, dict, int, float]]] """ Return a list of default add-ons. The return value must be a list with meta description following the `PyPI JSON api`_ specification. At the minimum 'info.name' and 'info.version' must be supplied. e.g. `[{'info': {'name': 'Super Pkg', 'version': '4.2'}}] .. _`PyPI JSON api`: https://warehouse.readthedocs.io/api-reference/json/ """ return [] def core_packages(self): # type: () -> List[str] """ Return a list of core packages. List of packages that are core of the application. Most importantly, if they themselves define add-on/plugin entry points they must not be 'uninstalled' via a package manager, they can only be updated. Return ------ packages : List[str] A list of package names (can also contain PEP-440 version specifiers). """ return ["orange-canvas-core >= 0.1a, < 0.2a"] def examples_entry_points(self): # type: () -> Iterable[EntryPoint] """ Return an iterator over entry points defining example/preset workflows. """ return iter(()) def widget_discovery(self, *args, **kwargs): raise NotImplementedError def workflow_constructor(self, *args, **kwargs): # type: (Any, Any) -> Scheme """ The default workflow constructor. """ raise NotImplementedError #: Standard application urls. If defined to a valid url appropriate actions #: are defined in various contexts APPLICATION_URLS = { #: Submit a bug report action in the Help menu _tr.m[2, "Bug Report"]: None, #: A url quick tour/getting started url _tr.m[3, "Quick Start"]: None, #: An url to the full documentation _tr.m[4, "Documentation"]: None, #: Video screencast/tutorials _tr.m[5, "Screencasts"]: None, #: Used for 'Submit Feedback' action in the help menu _tr.m[6, "Feedback"]: None, } # type: Dict[str, Optional[str]] class Default(Config): OrganizationDomain = "biolab.si" ApplicationName = "Orange Canvas Core" ApplicationVersion = __version__ @staticmethod def application_icon(): """ Return the main application icon. """ data = pkgutil.get_data(__name__, "icons/orange-canvas.svg") return QIcon(SvgIconEngine(data)) @staticmethod def splash_screen(): # type: () -> Tuple[QPixmap, QRect] """ Return a splash screen pixmap and an text area within it. The text area is used for displaying text messages during application startup. The default implementation returns a bland rectangle splash screen. Returns ------- t : Tuple[QPixmap, QRect] A QPixmap and a rect area within it. """ contents = pkgutil.get_data(__name__, "icons/orange-canvas-core-splash.svg") img = QImage.fromData(contents, "svg") pm = QPixmap.fromImage(img) version = QCoreApplication.applicationVersion() if version: version_parsed = packaging.version.Version(version) version_comp = version_parsed.release version = ".".join(map(str, version_comp[:2])) size = 21 if len(version) < 5 else 16 font = QFont() font.setPixelSize(size) font.setBold(True) font.setItalic(True) font.setLetterSpacing(QFont.AbsoluteSpacing, 2) metrics = QFontMetrics(font) br = metrics.boundingRect(version).adjusted(-5, 0, 5, 0) br.moveBottomRight(QPoint(pm.width() - 15, pm.height() - 15)) p = QPainter(pm) p.setRenderHint(QPainter.Antialiasing) p.setRenderHint(QPainter.TextAntialiasing) p.setFont(font) p.setPen(QColor("#231F20")) p.drawText(br, Qt.AlignCenter, version) p.end() textarea = QRect(15, 15, 170, 20) return pm, textarea @staticmethod def widgets_entry_points() -> Iterable[EntryPoint]: """ Return an iterator over entry points defining the set of 'nodes/widgets' available to the workflow model. """ return iter(entry_points(group=WIDGETS_ENTRY)) @staticmethod def addon_entry_points() -> Iterable[EntryPoint]: return iter(entry_points(group=ADDONS_ENTRY)) @staticmethod def addon_pypi_search_spec(): return dict(ADDON_PYPI_SEARCH_SPEC) @staticmethod def addon_defaults_list(session=None): """ Return a list of default add-ons. The return value must be a list with meta description following the `PyPI JSON api`_ specification. At the minimum 'info.name' and 'info.version' must be supplied. e.g. `[{'info': {'name': 'Super Pkg', 'version': '4.2'}}] .. _`PyPI JSON api`: https://warehouse.readthedocs.io/api-reference/json/ """ return [] @staticmethod def core_packages(): # type: () -> List[str] """ Return a list of core packages. List of packages that are core of the product. Most importantly, if they themselves define add-on/plugin entry points they must not be 'uninstalled' via a package manager, they can only be updated. Return ------ packages : List[str] A list of package names (can also contain PEP-440 version specifiers). """ return ["orange-canvas-core >= 0.0, < 0.1a"] @staticmethod def examples_entry_points(): return iter(entry_points(group=EXAMPLE_WORKFLOWS_ENTRY)) @staticmethod def widget_discovery(*args, **kwargs): from . import registry return registry.WidgetDiscovery(*args, **kwargs) @staticmethod def workflow_constructor(*args, **kwargs): from . import scheme return scheme.Scheme(*args, **kwargs) default = Default() def init(): """ Initialize the QCoreApplication.organizationDomain, applicationName, applicationVersion and the default settings format. Will only run once. .. note:: This should not be run before QApplication has been initialized. Otherwise it can break Qt's plugin search paths. """ default.init() # Make consecutive calls a null op. global init log.debug("Activating configuration for {}".format(default)) init = lambda: None rc = {} # type: ignore spec = \ [("startup/show-splash-screen", bool, True, "Show splash screen on startup"), ("startup/show-welcome-screen", bool, True, "Show Welcome screen on startup"), ("startup/load-crashed-workflows", bool, True, "Load crashed scratch workflows on startup"), ("application/language", str, "English", _tr.m[7, "Application language"]), ("application/last-used-language", str, "English", "If different from application/language, widget discovery is forced"), ("stylesheet", str, "orange", "QSS stylesheet to use"), ("schemeinfo/show-at-new-scheme", bool, False, "Show Workflow Properties when creating a new Workflow"), ("mainwindow/scheme-margins-enabled", bool, False, "Show margins around the workflow view"), ("mainwindow/show-scheme-shadow", bool, True, "Show shadow around the workflow view"), ("mainwindow/toolbox-dock-exclusive", bool, False, "Should the toolbox show only one expanded category at the time"), ("mainwindow/toolbox-dock-floatable", bool, False, "Is the canvas toolbox floatable (detachable from the main window)"), ("mainwindow/toolbox-dock-movable", bool, True, "Is the canvas toolbox movable (between left and right edge)"), ("mainwindow/toolbox-dock-use-popover-menu", bool, True, ("Use a popover menu to select a widget when clicking on a category " + "button")), ("mainwindow/widgets-float-on-top", bool, False, "Float widgets on top of other windows"), ("mainwindow/number-of-recent-schemes", int, 15, "Number of recent workflows to keep in history"), ("schemeedit/show-channel-names", bool, True, "Show channel names"), ("schemeedit/show-link-state", bool, True, "Show link state hints."), ("schemeedit/enable-node-animations", bool, True, "Enable node animations."), ("schemeedit/freeze-on-load", bool, False, "Freeze signal propagation when loading a workflow."), ("quickmenu/trigger-on-double-click", bool, True, "Show quick menu on double click."), ("quickmenu/trigger-on-right-click", bool, True, "Show quick menu on right click."), ("quickmenu/trigger-on-space-key", bool, True, "Show quick menu on space key press."), ("quickmenu/trigger-on-any-key", bool, False, "Show quick menu on double click."), ("quickmenu/show-categories", bool, False, "Show categories in quick menu."), ("logging/level", int, 1, "Logging level"), ("logging/show-on-error", bool, True, "Show log window on error"), ("logging/dockable", bool, True, "Allow log window to be docked"), ("help/open-in-external-browser", bool, True, "Open help in an external browser"), ("add-ons/allow-conda", bool, True, "Install add-ons with conda"), ("add-ons/pip-install-arguments", str, '', 'Arguments to pass to "pip install" when installing add-ons.'), ("network/http-proxy", str, '', 'HTTP proxy.'), ("network/https-proxy", str, '', 'HTTPS proxy.'), ("network/use-certs", bool, False, "Use system certificates."), ] spec = [config_slot(*t) for t in spec] def register_setting(key, type, default, doc=""): # type: (str, typing.Type[T], T, str) -> None """ Register an application setting. This only affects the `Settings` instance as returned by `settings`. Parameters ---------- key : str The setting key path type : Type[T] Type of the setting. One of `str`, `bool` or `int` default : T Default value for setting. doc : str Setting description string. """ spec.append(config_slot(key, type, default, doc)) def settings(): init() store = QSettings() settings = Settings(defaults=spec, store=store) return settings def data_dir(): """ Return the application data directory. If the directory path does not yet exists then create it. """ init() datadir = QStandardPaths.writableLocation(QStandardPaths.AppLocalDataLocation) version = QCoreApplication.applicationVersion() datadir = os.path.join(datadir, version) if not os.path.isdir(datadir): try: os.makedirs(datadir, exist_ok=True) except OSError: pass return datadir def cache_dir(): """ Return the application cache directory. If the directory path does not yet exists then create it. """ init() cachedir = QStandardPaths.writableLocation(QStandardPaths.CacheLocation) version = QCoreApplication.applicationVersion() cachedir = os.path.join(cachedir, version) if not os.path.exists(cachedir): os.makedirs(cachedir) return cachedir def log_dir(): """ Return the application log directory. """ init() if sys.platform == "darwin": name = str(QCoreApplication.applicationName()) logdir = os.path.join(os.path.expanduser("~/Library/Logs"), name) else: logdir = data_dir() if not os.path.exists(logdir): os.makedirs(logdir) return logdir def widget_settings_dir(): """ Return the widget settings directory. """ warnings.warn( "'widget_settings_dir' is deprecated.", DeprecationWarning, stacklevel=2 ) return os.path.join(data_dir(), 'widgets') def open_config(): warnings.warn( "open_config was never used and will be removed in the future", DeprecationWarning, stacklevel=2 ) return def save_config(): warnings.warn( "save_config was never used and will be removed in the future", DeprecationWarning, stacklevel=2 ) def widgets_entry_points(): """ Return an `EntryPoint` iterator for all 'orange.widget' entry points plus the default Orange Widgets. """ return default.widgets_entry_points() def splash_screen(): """ """ return default.splash_screen() def application_icon(): """ Return the main application icon. """ return default.application_icon() def widget_discovery(*args, **kwargs): return default.widget_discovery(*args, **kwargs) def workflow_constructor(*args, **kwargs): # type: (Any, Any) -> Scheme return default.workflow_constructor(*args, **kwargs) def set_default(conf): global default default = conf