"""
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 = {
#: FAQ
_tr.m[2, "FAQ"]: None,
#: Submit a bug report action in the Help menu
_tr.m[3, "Bug Report"]: None,
#: A url quick tour/getting started url
_tr.m[4, "Quick Start"]: None,
#: An url to the full documentation
_tr.m[5, "Documentation"]: None,
#: Video screencast/tutorials
_tr.m[6, "Screencasts"]: None,
#: Used for 'Submit Feedback' action in the help menu
_tr.m[7, "Feedback"]: None,
#: A Donate action in the help menu
_tr.m[8, "Donate"]: 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[9, "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, _tr.m[10, "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